尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Spring核心原理与实战:从IoC容器到AOP、事务与自动配置

Spring核心原理与实战:从IoC容器到AOP、事务与自动配置 1. 为什么Spring能成为Java开发的默认选择先搞懂它到底解决了什么很多新手学Spring上来就背IoC、AOP、Bean生命周期这些概念背完了还是不知道这东西到底干嘛用的。我当年也是这样直到有一次被一个十年经验的架构师问了一句你说说看如果没有Spring你现在写这个项目会遇到什么问题我愣住了。这个问题其实才是理解Spring的钥匙。在没有Spring的年代我们写JavaWeb项目对象和对象之间的依赖关系是靠new关键字硬编码在代码里的。Service要调Dao那就Dao dao new DaoImpl()Controller要调Service那就Service service new ServiceImpl()。听起来也没啥对吧但你仔细想想只要Dao层的实现类换了一个你所有写过new DaoImpl()的地方全都要改。而且测试的时候你想给Service层注入一个Mock的Dao你做不到因为依赖是写死的。这还不是最头疼的。真正麻烦的是对象之间的协作关系复杂了以后谁先创建谁、谁依赖谁的创建顺序全靠开发人员手工保证。像事务管理这种横跨多个对象的逻辑你只能在每个业务方法里手动打开事务、提交、回滚、处理异常代码里全是样板代码真正的业务逻辑反而被淹没了。Spring做的核心事情其实就是三件把对象创建和对象之间的依赖关系管理起来把横跨多个对象的通用逻辑抽取出来把JavaEE各种繁琐的配置标准化。这三件事分别对应IoC容器、AOP和Spring的集成能力。从2002年Rod Johnson写《Expert One-on-One J2EE Design and Development》这本书开始到Spring 6.x的时代这套核心思想从来没有变过只是实现方式越来越完善。这就是为什么Spring在MyBatis、MyBatis-Plus、Dubbo、Spring Cloud这些生态里都能作为地基存在——因为它解决的是最底层、最通用的那部分问题。所以别急着去背那些面试八股。先把这个为什么要存在的逻辑想清楚后面所有知识点都能串起来。2. IoC容器的本质Bean被谁创建、怎么被管理、生命周期是怎么回事IoC全称Inversion of Control控制反转。这名字起得抽象实际上用大白话说就是对象的创建权和控制权从程序员手里交给了Spring容器。你不用new了你只需要告诉Spring我需要一个什么东西Spring容器在启动的时候会帮你创建好然后在你需要的地方注入给你。2.1 DI依赖注入与IoC的关系同一个问题的两面这里有个经常被混淆的点面试也常考。IoC是一种设计思想而DIDependency Injection依赖注入是它的一种实现方式。说Spring是基于IoC思想的框架和Spring是通过DI来实现IoC的这两种说法都是对的但是前者说的是理念后者说的是落地手段。依赖注入有几种常见方式构造器注入Spring官方推荐的注入方式依赖通过构造函数传入。好处是依赖不可变、不易产生循环依赖、测试时也容易构造。Setter注入通过setter方法注入灵活性高但对象可能处于依赖未完全设置的中间状态。字段注入用Autowired直接打在字段上写起来最省事但IDEA会报警告因为它的依赖关系在类外部不可见且无法用final修饰隐患比较多。我个人的建议是在新项目里优先用构造器注入。注意我说的不是构造器注入比字段注入先进而是它的约束力更强。字段注入写多了一个Service里塞七八个依赖你根本看不出来这个类到底依赖了什么重构的时候一改全炸。构造器注入会把所有依赖明明白白摆在构造函数里逼迫你审视这个类是不是承担了太多职责。2.2 Bean的注册方式演进从XML到注解到JavaConfig最早的Spring是纯XML配置一个applicationContext.xml里写满bean标签每个Bean要声明id、class、property。那时候最痛苦的事情不是写代码而是写配置文件一个中大型项目里XML文件能有好几兆。后来Spring 2.5引入了注解Component、Service、Repository、Controller这组注解把让Spring管理这个类这件事从XML搬到了Java代码里配合context:component-scan开启包扫描开发效率一下子提升了很多。再后来Spring 3.0推出了JavaConfig也就是Configuration加Bean的方式。JavaConfig的好处是类型安全——配置写错了编译器就能发现而不是等到启动时才报错。比如Configuration public class DataSourceConfig { Bean public DataSource dataSource() { HikariDataSource dataSource new HikariDataSource(); dataSource.setJdbcUrl(jdbc:mysql://localhost:3306/test); dataSource.setUsername(root); dataSource.setPassword(123456); return dataSource; } }现在主流的新项目基本都是注解加JavaConfig混合使用。自动装配用ComponentScan帮助扫描第三方库的Bean用Bean手动声明。这几种方式不是互斥的它们可以同时存在于一个项目里这也是Spring灵活的地方。2.3 Bean的作用域singleton、prototype以及Web相关作用域Bean的作用域决定了Spring容器会创建多少个该类的实例。这个知识点单看很简单但实际项目里踩坑的人非常多。singleton单例Spring容器中同一个Bean定义只创建一个实例容器启动时创建默认懒加载策略下也可以延迟到第一次使用时创建。Spring默认就是这个但要注意单例不等于线程安全无状态的Service没问题有状态的Bean就需要自己保证线程安全。prototype原型每次获取都创建一个新实例。适合有状态的场景比如一个不能被多个线程共享的组件。request / session / application / websocket这些是Web环境下的作用域分别对应一次请求、一个会话、整个应用上下文和一次WebSocket会话。它们需要WebApplicationContext才行普通容器不适用。我见过一个真实的事故有个同事把UserContext这个类标成了Component默认singleton里面存了当前登录用户的信息结果上线后所有用户的订单里都是同一个用户ID。原因就是singleton实例被所有线程共享A用户的请求把对象里的用户信息改了B用户读到的就是A的。这种问题的排查往往很隐蔽因为你本地测试的时候只有一个用户在操作根本暴露不出来。后来改成Scope(request)或者用ThreadLocal存上下文问题才解决。所以说面试问作用域的深层目的不是考记忆而是考察你是否清楚对象在什么范围内被共享带来的线程安全影响。2.4 Bean的生命周期从定义到销毁的完整链路Bean生命周期是Spring面试的经典八股也是理解Spring容器工作原理的关键。完整的生命周期可以这样理解Bean从诞生到销毁经历了实例化、属性填充、初始化、销毁四个大的阶段。我在实际面试别人时不会让候选者死背那张完整流程图而是会问一个切入口如果你有一个Bean希望在容器启动时检查某些配置是否合法在容器关闭时释放资源你有哪些手段能答出PostConstruct和PreDestroy的说明基本掌握能再答出InitializingBean和DisposableBean接口的算是有过实践能进一步说出BeanPostProcessor可以在初始化前后对Bean做增强处理的这才算真正理解容器的扩展点。一个典型的、带BeanPostProcessor参与的完整生命周期链路是这样的Spring根据配置或注解扫描得到BeanDefinition然后基于它实例化对象构造函数。属性填充即依赖注入把Autowired、Resource标注的依赖注入进去。如果Bean实现了Aware接口比如BeanNameAware、ApplicationContextAwareSpring会回调相应方法让Bean感知到容器相关信息。每个BeanPostProcessor的postProcessBeforeInitialization方法执行。执行初始化先执行PostConstruct标注的方法然后如果实现了InitializingBean则调用afterPropertiesSet()最后执行init-method指定的方法。每个BeanPostProcessor的postProcessAfterInitialization方法执行——AOP动态代理的生成通常就发生在这个阶段。Bean就绪可以被使用了。容器关闭时先执行PreDestroy标注的方法然后如果实现了DisposableBean则调用destroy()最后执行自定义的destroy-method。这里面最值得展开的是第6步。Spring AOP为什么能在你完全无感的情况下给Service方法织入事务靠的就是第6步的postProcessAfterInitialization——容器在这个阶段发现当前Bean匹配了某个切面就会生成一个动态代理对象返回给调用方而不是返回原始Bean。理解了这一点你才算真正把IoC和AOP这两个核心概念打通了。3. AOP的底层原理与实战场景代理机制怎么看、切面怎么写AOPAspect Oriented Programming面向切面编程这个名字对新手很不友好听起来像是又要学一门新编程范式。其实它解决的就是一件事把散落在各个业务方法里的横切逻辑日志、事务、权限、性能监控集中起来统一管理。3.1 为什么需要AOP横切逻辑的复用困境你想想一个典型的业务类里每个方法都可能有这样的代码记录开始时间、执行日志、调用业务逻辑、事务提交或回滚、异常捕获、记录结束时间。如果每个方法都手写一份一方面代码大量重复另一方面一旦要加新的横切逻辑你得把所有方法都改一遍。AOP的思路是把执行某段业务逻辑这件事拆解成方法的调用链然后在调用链的特定位置上插入切面逻辑。你不需要修改核心业务代码只需要告诉Spring在哪些方法执行前做什么、执行后做什么、异常时做什么即可。3.2 核心术语对照切面、连接点、切入点、通知、引入面试时AOP术语经常是连环问我列个表对照着记会轻松很多术语一句话理解类比切面Aspect横切逻辑的模块化封装通常是一个类一整套安检流程连接点Join Point程序执行过程中能插入切面的点Spring中通常是方法执行安检站每个入口切入点Pointcut通过表达式匹配哪些连接点需要被切只对VIP通道做安检通知Advice切面在特定时机执行的具体逻辑安检动作本身目标对象Target被代理的真实Bean过安检的乘客引入Introduction给目标类动态添加新的方法或字段给乘客临时加一个贵宾身份织入Weaving把切面应用到目标对象并创建代理的过程把安检流程接入入口切入点表达式的写法在项目里用得最多比如execution(* com.example.service.*.*(..))这个写法要拆开理解*代表返回值任意com.example.service.*代表service包下所有类第二个*代表所有方法(..)代表任意参数列表。如果需要匹配子包用com.example.service..*。3.3 Spring AOP用的是动态代理不是字节码增强这决定了它的边界Spring AOP和AspectJ最核心的区别其实是实现机制。Spring AOP基于动态代理在运行时生成代理对象AspectJ则是编译期/加载期字节码增强直接修改字节码。两者各有优劣。如果目标类实现了接口Spring默认使用JDK动态代理代理对象和目标对象是兄弟关系都实现了同一接口。如果目标类没有实现接口Spring则使用CGLIB代理通过继承目标类生成子类来代理。这个区别带来的经典坑几乎所有做Spring开发的都遇过坑一this调用不被拦截。在一个类内部方法A调用同类的方法B在B上标注的Transactional不生效。因为动态代理是外部调用代理对象的方法才会触发拦截而this.methodB()调用的是目标对象自己的方法不经过代理。解决方案是注入自身代理、拆分到不同Bean或用AopContext.currentProxy()需要开启exposeProxy。坑二JDK动态代理下Autowired注入的是接口类型。如果一个Service实现了接口那么注入时可以声明接口类型。但如果你把字段类型声明成实现类而Spring创建的是JDK动态代理它是一个Proxy实现不是你的实现类的子类运行时就会类型不匹配报错。这个在Spring Boot 2.x之后默认使用CGLIB代理情况缓解了不少。3.4 通知类型与执行顺序别在事务边界上踩坑Spring AOP支持五种通知Before方法执行前AfterReturning方法正常返回后AfterThrowing方法抛出异常后After方法结束后无论正常还是异常类似finallyAround环绕通知最强大也最危险可以完全控制方法执行After和AfterReturning的区别是高频面试题。After相当于finally不管方法成功还是抛异常都会执行AfterReturning只在方法成功返回后执行它可以访问返回值。多个切面的执行顺序默认不保证或者说有顺序但不可依赖需要显示指定时可以用Order注解数值越小越先执行。前置通知按Order升序执行后置通知按Order降序执行这个顺序配合事务切面时尤其重要——如果你的自定义切面Order排在事务切面之前那么你的环绕逻辑在事务内部如果排在后面可能就在事务边界之外了。4. 事务管理注解驱动背后发生了什么、为什么有时候不生效Spring事务管理是AOP最典型的应用也是项目里问题最多、排查起来最费劲的模块。先说结论Spring事务的本质是AOP拦截不是数据库自带的能力——你在方法上标注TransactionalSpring会通过代理在方法执行前开启事务、执行后提交或回滚。4.1 事务传播行为嵌套调用时事务怎么走事务传播行为Propagation定义了当一个事务方法调用另一个事务方法时事务如何传播。这个知识点面试必考项目里也真的会遇到。常用的几种REQUIRED默认如果当前存在事务就加入不存在则新建。大部分业务都适用。REQUIRES_NEW无论当前有没有事务都新开一个挂起当前事务。适合日志记录必须独立提交不能受到主事务回滚影响的场景。NESTED嵌套事务内层事务回滚不影响外层已提交的部分基于Savepoint实现。适合某一步失败但不想全盘回滚的场景。SUPPORTS有事务就加入没有就非事务执行。适合查询方法。NOT_SUPPORTED非事务执行挂起当前事务。MANDATORY必须在事务中否则抛异常。NEVER必须不在事务中否则抛异常。我重点说说REQUIRES_NEW和NESTED的实际差别。REQUIRES_NEW是完全独立的事务内层事务提交了外层之后回滚内层已提交的也不会回滚了。NESTED用的是数据库Savepoint内层回滚只回滚到Savepoint位置外层可以继续执行也可以整体回滚。如果你的场景是主流程失败时某些子操作也必须撤销用NESTED更合适如果是子操作独立成功、独立失败不影响主流程结果用REQUIRES_NEW。4.2 事务失效的常见原因八个小细节踩一个就够你排查半天网上流传的事务失效场景有好几十种但真正高频率出现在项目里的其实就那么几个。我按实际踩坑率排个序1. 方法自调用最常见。同类中方法A调用方法BB上有Transactional但B的事务不会生效。原因前面说过this调用不经过代理。解决把B移到另一个Bean或者用((Service) AopContext.currentProxy()).methodB()。2. 方法不是public。Spring事务代理默认只拦截public方法AbstractFallbackTransactionAttributeSource只检查public方法protected/private直接不生效。Spring Boot 2.0之后非public方法上标注Transactional会在启动时直接报错提示。3. 异常被捕获了。这是最常见的我的事务为什么没回滚事故。代码里try-catch把异常吞掉事务拦截器根本不知道有异常发生自然不回滚。正确做法是在catch里抛出RuntimeException或者在catch块中手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。4. 抛出的是受检异常。Spring事务默认只对RuntimeException运行时异常和Error回滚对受检异常CheckedException比如IOException不回滚。因为Spring认为受检异常是业务可预期、可以恢复的不需要强制回滚。如果你确实想让受检异常也回滚需要rollbackFor Exception.class。Transactional(rollbackFor Exception.class) public void createOrder(Order order) throws IOException { // ... }5. 数据库引擎不支持事务。比如MySQL的MyISAM引擎它压根不支持事务你加再多注解也没用。排查办法是检查建表语句里ENGINEInnoDB。6. 传播行为设置不当。比如Propagation.NOT_SUPPORTED下加Transactional显然没意义。7. 类没有被Spring管理。最简单的错误——类上忘了加Service、Component这类注解Spring根本不认识它何来代理。8. 事务切面的Order不对。如果你的AOP切面的Order值比事务切面小优先级更高且切面中有异常被吞掉可能导致事务拦截器看不到异常。4.3 事务隔离级别别只会背五种要知道选型的依据事务隔离级别解决的是并发事务下的数据一致性问题。数据库标准定义了四种隔离级别从低到高分别是READ UNCOMMITTED读未提交能读到别的事务未提交的数据脏读。READ COMMITTED读已提交只能读到已提交的数据解决脏读但可能不可重复读。Oracle默认。REPEATABLE READ可重复读同一事务内多次读取结果一致解决不可重复读但可能幻读。MySQL InnoDB默认且通过MVCC和间隙锁解决了大部分幻读问题。SERIALIZABLE串行化事务完全串行执行最安全但性能最差。实际项目中互联网高并发场景一般用MySQL默认的REPEATABLE READ就够了。在Spring里通过Transactional(isolation Isolation.REPEATABLE_READ)指定。不过说实话除非是金融、账务相关的核心链路大多数业务用数据库默认级别就可以。盲目调高隔离级别导致锁竞争加剧、吞吐量下降这才是不懂原理的表现。5. Spring MVC的请求处理链路一个请求从进入容器到返回响应的完整旅程Spring MVC是Spring家族里负责Web层的老将。不管你是做传统单体应用还是用Spring Boot写接口理解Spring MVC的请求处理链路对排查问题都非常有帮助。5.1 一次HTTP请求经历了什么一个HTTP请求进入Spring MVC后大致走这条链路请求先到达DispatcherServlet它是前端控制器所有请求的入口。DispatcherServlet通过HandlerMapping找到处理该请求的Controller方法HandlerExecutionChain。找到了之后通过HandlerAdapter去执行这个Controller方法。执行之前会先经过一系列HandlerInterceptor拦截器的preHandle方法。Controller方法执行返回数据或视图名。拦截器的postHandle方法执行。返回的数据经过HandlerMethodReturnValueHandler处理比如ResponseBody时用HttpMessageConverter把对象序列化成JSON。最后DispatcherServlet渲染视图或直接写出响应体请求结束时会执行拦截器的afterCompletion。整个流程的核心就是DispatcherServlet这个中央调度员。它把请求接收、参数解析、数据绑定、校验、响应转换这些事情分工给了各个组件。理解了这个链路的顺序你做接口性能排查的时候就能准确判断耗时出在哪个环节是拦截器里慢还是Controller方法本身慢还是JSON序列化慢。5.2RequestBody、RequestParam、PathVariable的解析区别这三个注解是开发中最常用的但它们的底层处理方式不一样。PathVariable从URL路径模板中取值比如/user/{id}中的id。由PathVariableMethodArgumentResolver处理。RequestParam从查询参数或表单字段取值由RequestParamMethodArgumentResolver处理。RequestBody从请求体中读取数据用HttpMessageConverter反序列化为对象由RequestResponseBodyMethodProcessor处理。把RequestBody用成RequestParam是新手特别容易犯的错误——前端POST了一个JSON体后端却用RequestParam去接结果参数永远是null。反过来如果前端是表单提交application/x-www-form-urlencoded你却用RequestBody接同样接不到。所以接口联调时第一件事看Content-Type。5.3 拦截器 vs 过滤器面试必问的那点区别过滤器Filter是Servlet规范里的组件在Servlet容器如Tomcat层面生效早于DispatcherServlet可以拦截一切请求包括静态资源。Web应用里通过WebFilter或FilterRegistrationBean注册。拦截器Interceptor是Spring MVC的组件在DispatcherServlet之后生效只能拦截进入Controller的请求。可以拿到HandlerMethod所以可以做更精细的权限控制。一个典型的应用是登录状态校验用拦截器需要知道目标方法是哪个、是否需要放行字符编码、跨域CORS、XSS过滤这种和Spring MVC无关的通用处理放在Filter里更合理。两者不是替代关系而是不同层面的协同。6. Spring Boot的自动配置原理为什么一个空的Spring Boot项目就能直接跑起来如果你认真用记事本不借助IDEA的初始化向导建过一个Spring Boot项目你一定会好奇我就引入了spring-boot-starter-web依赖写了一个SpringBootApplication标记的启动类配置了application.yml什么都没配为什么Tomcat就跑起来了、JSON序列化就能用了、异常处理也自动有了这个魔法背后是Spring Boot的自动配置机制。6.1SpringBootApplication的三合一本质SpringBootApplication是一个组合注解它由三个核心注解组成SpringBootConfiguration本质是Configuration标记这是一个配置类。EnableAutoConfiguration开启自动配置这是Spring Boot最关键的能力。ComponentScan自动扫描启动类所在包及其子包下的组件。其中EnableAutoConfiguration的底层用到了Import(AutoConfigurationImportSelector.class)。这个Selector会在启动时扫描classpath下所有依赖jar包里的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件Spring Boot 2.7之前是spring.factories文件读取出所有的自动配置类然后按照条件注解逐一判断是否需要生效。6.2 条件注解自动配置的开关在哪自动配置类之所以不会乱生效是因为它们上面有一堆条件注解。最常见的几个ConditionalOnClassclasspath下有某个类才生效。比如ServletWebServerFactoryAutoConfiguration上标注了ConditionalOnClass(ServletRequest.class)你引入了web-starter才会有这个类。ConditionalOnMissingBean容器中不存在某个Bean才生效。这意味着你可以自定义一个同类型Bean来覆盖自动配置。ConditionalOnProperty配置文件中存在指定属性才生效。ConditionalOnWebApplication是Web应用才生效。这套条件注解机制让Spring Boot既能开箱即用又能按需覆盖。你在application.yml里改server.port本质上也是通过ConfigurationProperties把这些配置绑定到ServerProperties这个Bean上然后被自动配置类读取使用的。6.3 自定义starter从会用到会造的分水岭理解自动配置原理后有个很能体现水平的实践——自己写一个starter。比如你公司内部有一个统一的日志组件、统一的安全校验组件你可以把它封装成一个starter让其他团队引入依赖就能用。自定义starter的核心步骤创建一个Maven模块一般包含autoconfigure和starter两个模块也可以合成一个。在autoconfigure模块中写自动配置类用Configuration加条件注解约束生效范围。在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中注册自动配置类。定义好ConfigurationProperties前缀让使用者通过配置文件调整。AutoConfiguration ConditionalOnClass(MyService.class) EnableConfigurationProperties(MyProperties.class) public class MyAutoConfiguration { Bean ConditionalOnMissingBean public MyService myService(MyProperties properties) { return new MyService(properties); } }到了这一步你对Spring Boot的理解就不是停留在会用注解了而是真正理解了自动配置是怎么被加载、怎么被覆盖的机制。7. 从Spring到Spring Boot再到Spring Cloud生态演进中的核心主线最后聊聊生态。很多人把Spring、Spring Boot、Spring Cloud混为一谈面试时也说不清楚各自的定位。用一句话概括Spring是地基Spring Boot是装修好的房子Spring Cloud是小区里的基础设施。Spring Framework提供IoC、AOP、MVC、事务等核心能力是整个家族的最小内核。Spring Boot构建在Spring之上通过自动配置和starter机制解决了配置地狱问题让Spring应用可以快速启动和部署。它并没有替代Spring而是让Spring更好用。Spring Cloud是一套微服务解决方案的集合基于Spring Boot构建提供服务发现、配置中心、网关、熔断、负载均衡、分布式链路追踪等能力。从学习路径来说我的建议是先把Spring Framework的IoC和AOP吃透再用Spring Boot做项目练手最后根据自己的方向决定是否深入Spring Cloud。不要一上来就追Spring Cloud Alibaba的各种组件地基不稳上面盖的楼层再花哨也没用。还有一点关于新版本的变化值得关注Spring Framework 6 / Spring Boot 3.0之后整个技术栈基于Java 17和Jakarta EE命名空间javax.*改成了jakarta.*同时原生镜像GraalVM Native Image支持也变成了重要特性。如果面试官问到能说出Spring 6开始原生支持GraalVM、AOT编译提前做类型推断这些内容会显得你对技术趋势有持续跟踪。8. 我自己在项目里总结的几个Spring排查经验和一条建议最后分享几条实打实的经验都是从项目事故里换来的。第一个经验遇到Bean相关启动报错先看完整堆栈不要只看循环依赖这行字。Spring Boot 2.6之后默认禁止循环依赖启动报错时很多人看到The dependencies of some of the beans in the application context form a cycle就慌直接去搜怎么开启循环依赖其实更合理的做法是先审视设计——是不是把不该放在一个模块里的逻辑强塞在一起了。循环依赖很多时候是设计问题的提示信号而不是需要绕过去的障碍。第二个经验排查事务不生效按代理是否存在来定位。我常用的排查清单是这个类是不是被Spring管理有没有Component系注解调用的方法是否经过了代理对象不是this调用方法是不是public异常是不是被捕获或者用了受检异常数据库引擎是不是InnoDB照着这个顺序查绝大多数问题十分钟内能定位。第三个经验配置优先用application.yml但复杂Bean的创建用Configuration加Bean别硬塞进yml。yml适合配置简单属性和数据源地址这类信息适合声明周期复杂、需要AOP增强、需要自定义初始化逻辑的Bean最好用JavaConfig显式创建可读性和可调试性都更好。最后一个建议背八股的时候把所有知识点都想成为了解决什么问题而存在。一个面试官问你Spring他真正想知道的是你能否在写代码时做出正确的技术决策。你如果能说清楚我用构造器注入是因为它让依赖关系显式化、我用REQUIRES_NEW是因为日志不能随主事务回滚、我用ConditionalOnMissingBean是为了让使用方可以覆盖默认实现你就从背概念的人变成了懂设计的人。这在面试里的价值远高于把生命周期流程图倒背如流。
返回列表