[{"data":1,"prerenderedAt":467},["ShallowReactive",2],{"\u002Fjava-interview\u002Fspring":3,"java-interview-search:\u002Fjava-interview\u002Fspring":422},{"id":4,"title":5,"body":6,"category":408,"cover":409,"date":410,"description":411,"extension":412,"group":413,"meta":414,"navigation":415,"order":416,"path":417,"seo":418,"sitemap":419,"stem":420,"__hash__":421},"javaInterview\u002Fjava-interview\u002Fspring.md","Spring：Bean 生命周期、AOP 与事务",{"type":7,"value":8,"toc":392},"minimark",[9,13,27,30,42,45,49,52,82,96,108,112,115,118,132,136,139,150,160,164,178,185,196,204,222,237,240,244,266,269,280,284,298,311,326,329,342,353,356],[10,11,12],"p",{},"Spring 题有一条很自然的链路：容器怎么创建 Bean、怎么给 Bean 加能力、事务为什么依赖代理、Boot 又怎么把这一切自动化。按这条线准备，比孤立背注解有效。以下默认 Spring Framework 6 \u002F Spring Boot 3。",[14,15,17,18,22,23,26],"h2",{"id":16},"_1-怎么理解-ioc-和-dibeanfactory-与-applicationcontext-有什么区别","1. 怎么理解 IoC 和 DI？",[19,20,21],"code",{},"BeanFactory"," 与 ",[19,24,25],{},"ApplicationContext"," 有什么区别？",[10,28,29],{},"IoC 是控制反转：对象的创建、依赖装配和生命周期交给容器，业务代码只声明需要什么。DI 是它的实现方式，通过构造器、setter 或字段注入把依赖传进来。",[10,31,32,34,35,38,39,41],{},[19,33,21],{}," 是最基础的容器接口，提供 ",[19,36,37],{},"getBean"," 和 Bean 生命周期管理，默认懒加载。",[19,40,25],{}," 继承它，额外提供资源加载、事件发布、国际化、AOP 集成和注解支持，单例默认在启动时实例化。",[10,43,44],{},"实践上优先构造器注入：依赖不可变、便于单元测试、能暴露循环依赖问题。字段注入写法简单，但会让类难以脱离容器测试，也掩盖了职责过多的信号。",[14,46,48],{"id":47},"_2-bean-的生命周期是怎样的","2. Bean 的生命周期是怎样的？",[10,50,51],{},"大致分四段：实例化、属性填充、初始化、销毁。",[10,53,54,55,58,59,62,63,66,67,70,71,62,74,77,78,81],{},"实例化通过构造器或工厂方法完成；随后注入依赖并回调各类 ",[19,56,57],{},"Aware"," 接口，例如 ",[19,60,61],{},"BeanNameAware","、",[19,64,65],{},"ApplicationContextAware","。接着执行 ",[19,68,69],{},"BeanPostProcessor"," 的前置处理，再调用 ",[19,72,73],{},"@PostConstruct",[19,75,76],{},"InitializingBean.afterPropertiesSet"," 和自定义 ",[19,79,80],{},"init-method","，最后执行后置处理——AOP 代理通常就在这一步生成。",[10,83,84,85,62,88,91,92,95],{},"使用阶段结束后，容器关闭时依次调用 ",[19,86,87],{},"@PreDestroy",[19,89,90],{},"DisposableBean.destroy"," 和 ",[19,93,94],{},"destroy-method","。注意原型 Bean 的销毁方法不会由容器调用，需要调用方自己负责。",[10,97,98,100,101,62,104,107],{},[19,99,69],{}," 是理解 Spring 扩展性的关键：它能在初始化前后改写甚至替换 Bean，",[19,102,103],{},"@Autowired",[19,105,106],{},"@Async","、事务代理都依赖这一类扩展点。",[14,109,111],{"id":110},"_3-循环依赖是怎么解决的为什么构造器注入不行","3. 循环依赖是怎么解决的？为什么构造器注入不行？",[10,113,114],{},"单例 Bean 的字段或 setter 循环依赖，靠三级缓存解决：一级放成品 Bean，二级放提前暴露的早期引用，三级放能生成早期引用的工厂。",[10,116,117],{},"创建 A 时先实例化并放入三级缓存，填充属性发现依赖 B，转去创建 B；B 依赖 A 时从缓存拿到 A 的早期引用，B 完成后 A 继续填充并最终完成。三级缓存的意义在于：如果 A 需要被 AOP 代理，工厂能在暴露早期引用时就生成代理，而不是暴露原始对象。",[10,119,120,121,124,125,62,128,131],{},"构造器注入要求在实例化阶段就拿到依赖，此时对象还不存在，无法提前暴露，因此会抛 ",[19,122,123],{},"BeanCurrentlyInCreationException","。Spring Boot 2.6 起默认禁止循环依赖，需要显式开启才恢复旧行为；更好的做法是重构依赖或使用 ",[19,126,127],{},"@Lazy",[19,129,130],{},"ObjectProvider"," 延迟获取。",[14,133,135],{"id":134},"_4-bean-是线程安全的吗单例要注意什么","4. Bean 是线程安全的吗？单例要注意什么？",[10,137,138],{},"Spring 不保证 Bean 线程安全，它只负责创建和管理。单例 Bean 被所有请求共享，只要存在可变的成员状态，就可能出现并发问题。",[10,140,141,142,145,146,149],{},"无状态的 Service、DAO 天然安全；有状态的对象要么改成方法内局部变量，要么用 ",[19,143,144],{},"ThreadLocal","（并记得清理），要么把作用域改成原型。给单例加 ",[19,147,148],{},"synchronized"," 通常只是把瓶颈集中到一个点上。",[10,151,152,153,156,157,159],{},"常见误区是把 ",[19,154,155],{},"@Scope(\"prototype\")"," 注到单例里就以为每次调用都会新建：注入只发生一次，单例持有的是同一个原型实例。需要每次获取新对象时应注入 ",[19,158,130],{}," 或使用作用域代理。",[14,161,163],{"id":162},"_5-spring-aop-的原理是什么为什么自调用会失效","5. Spring AOP 的原理是什么？为什么自调用会失效？",[10,165,166,167,170,171,174,175,177],{},"Spring AOP 基于动态代理：目标类实现了接口时可用 JDK 代理，否则用 CGLIB 生成子类。Spring Boot 默认 ",[19,168,169],{},"proxyTargetClass = true","，即优先 CGLIB，所以 ",[19,172,173],{},"final"," 类和 ",[19,176,173],{}," 方法无法被代理。",[10,179,180,181,184],{},"调用方拿到的是代理对象，代理在方法前后织入通知。所谓自调用失效，是指同类中 ",[19,182,183],{},"this.otherMethod()"," 绕过了代理，通知自然不会执行；事务、缓存、异步注解都会遇到这个问题。",[10,186,187,188,191,192,195],{},"解决方式：拆到另一个 Bean、注入自身代理、使用 ",[19,189,190],{},"AopContext.currentProxy()","（需开启 ",[19,193,194],{},"exposeProxy","），或者改用 AspectJ 编译期\u002F加载期织入。生产上更推荐前两种，侵入性小。",[14,197,199,200,203],{"id":198},"_6-transactional-在哪些情况下会失效传播行为怎么选","6. ",[19,201,202],{},"@Transactional"," 在哪些情况下会失效？传播行为怎么选？",[10,205,206,207,210,211,214,215,91,218,221],{},"失效的常见原因：自调用绕过代理；方法不是 ",[19,208,209],{},"public","（Spring 6 起类代理支持 protected 和包可见方法，但仍有边界）；Bean 不是由容器管理；异常被 ",[19,212,213],{},"catch"," 后没有抛出；抛出的是受检异常而默认只回滚 ",[19,216,217],{},"RuntimeException",[19,219,220],{},"Error","；数据库表本身不支持事务；在另一个线程里执行。",[10,223,224,225,228,229,232,233,236],{},"传播行为中，",[19,226,227],{},"REQUIRED"," 是默认值，加入现有事务；",[19,230,231],{},"REQUIRES_NEW"," 挂起当前事务、独立提交，适合记日志等「必须落库」的操作；",[19,234,235],{},"NESTED"," 使用保存点，只对 JDBC 等支持保存点的资源有效。",[10,238,239],{},"还要注意只读事务和超时设置，以及事务内不要做远程调用——长事务会长期占用连接和锁。",[14,241,243],{"id":242},"_7-spring-boot-自动配置的原理","7. Spring Boot 自动配置的原理？",[10,245,246,249,250,253,254,257,258,261,262,265],{},[19,247,248],{},"@SpringBootApplication"," 组合了配置类、组件扫描和 ",[19,251,252],{},"@EnableAutoConfiguration","。后者通过 ",[19,255,256],{},"AutoConfigurationImportSelector"," 读取候选配置类：Boot 2.7 起使用 ",[19,259,260],{},"META-INF\u002Fspring\u002Forg.springframework.boot.autoconfigure.AutoConfiguration.imports","，更早的版本用 ",[19,263,264],{},"spring.factories","。",[10,267,268],{},"候选类并不会全部生效，而是由条件注解筛选：类路径上是否存在某个类、容器里是否已有同类型 Bean、配置项是否开启。这就是「引入 starter 就自动装配，同时允许自定义覆盖」的机制。",[10,270,271,272,275,276,279],{},"自定义覆盖之所以生效，是因为自动配置类通常带 ",[19,273,274],{},"@ConditionalOnMissingBean","，且用户配置先于自动配置处理。排查自动配置问题时，可以开启 ",[19,277,278],{},"debug=true"," 查看条件评估报告。",[14,281,283],{"id":282},"_8-一个-http-请求在-spring-mvc-里是怎么走完的","8. 一个 HTTP 请求在 Spring MVC 里是怎么走完的？",[10,285,286,287,290,291,294,295,265],{},"请求先经过 Servlet 容器的过滤器链，进入 ",[19,288,289],{},"DispatcherServlet","。它向 ",[19,292,293],{},"HandlerMapping"," 询问由哪个处理器处理，拿到处理器和拦截器链后，交给匹配的 ",[19,296,297],{},"HandlerAdapter",[10,299,300,302,303,306,307,310],{},[19,301,297],{}," 调用参数解析器把请求参数、路径变量、请求体转换成方法参数，执行 Controller 方法；返回值再由返回值处理器处理，",[19,304,305],{},"@ResponseBody"," 走 ",[19,308,309],{},"HttpMessageConverter"," 序列化，否则交给视图解析器渲染。",[10,312,313,314,317,318,321,322,325],{},"拦截器的 ",[19,315,316],{},"preHandle"," 在处理器执行前调用，",[19,319,320],{},"postHandle"," 在渲染前调用，",[19,323,324],{},"afterCompletion"," 在完成后调用。过滤器属于 Servlet 规范，能拿到原始请求但不能感知处理器；拦截器属于 Spring MVC，能访问处理器信息。",[14,327,328],{"id":328},"易错点与自测",[330,331,332,336,339],"ul",{},[333,334,335],"li",{},"三级缓存只解决单例的字段\u002Fsetter 循环依赖，不解决构造器注入和原型作用域。",[333,337,338],{},"AOP 的一切「注解不生效」几乎都可以归结为「没走代理」。",[333,340,341],{},"自动配置不是「无条件装配」，条件注解和加载顺序才是重点。",[10,343,344,345,347,348,22,350,352],{},"自测：① 画出 Bean 从实例化到销毁的关键回调顺序；② 说出三种让 ",[19,346,202],{}," 恢复生效的改法；③ 解释 ",[19,349,231],{},[19,351,235],{}," 在回滚行为上的差异。",[14,354,355],{"id":355},"参考资料",[330,357,358,368,376,384],{},[333,359,360,367],{},[361,362,366],"a",{"href":363,"rel":364},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Fbeans.html",[365],"nofollow","Spring Framework：IoC 容器与 Bean 生命周期"," — 容器、作用域与扩展点",[333,369,370,375],{},[361,371,374],{"href":372,"rel":373},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fdata-access\u002Ftransaction.html",[365],"Spring Framework：事务管理"," — 传播行为与回滚规则",[333,377,378,383],{},[361,379,382],{"href":380,"rel":381},"https:\u002F\u002Fdocs.spring.io\u002Fspring-boot\u002Freference\u002Fusing\u002Fauto-configuration.html",[365],"Spring Boot：自动配置"," — 条件注解与加载顺序",[333,385,386,391],{},[361,387,390],{"href":388,"rel":389},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Faop.html",[365],"Spring Framework：AOP 编程"," — 代理机制与自调用问题",{"title":393,"searchDepth":394,"depth":394,"links":395},"",2,[396,398,399,400,401,402,404,405,406,407],{"id":16,"depth":394,"text":397},"1. 怎么理解 IoC 和 DI？BeanFactory 与 ApplicationContext 有什么区别？",{"id":47,"depth":394,"text":48},{"id":110,"depth":394,"text":111},{"id":134,"depth":394,"text":135},{"id":162,"depth":394,"text":163},{"id":198,"depth":394,"text":403},"6. @Transactional 在哪些情况下会失效？传播行为怎么选？",{"id":242,"depth":394,"text":243},{"id":282,"depth":394,"text":283},{"id":328,"depth":394,"text":328},{"id":355,"depth":394,"text":355},"java-interview",null,"2026-10-24","从 IoC 容器、三级缓存到 AOP 代理和事务失效，梳理 Spring 与 Spring Boot 的高频面试链路","md","框架与数据库",{},true,"60","\u002Fjava-interview\u002Fspring",{"title":5,"description":411},{"loc":417},"java-interview\u002Fspring","AmlsYLEuEzrSk7bwJoj1_y8H6mOO3Py3-eIjBxU1XJE",[423,427,431,435,439,443,447,451,455,459,463],{"id":417,"title":5,"titles":424,"content":425,"level":426},[],"从 IoC 容器、三级缓存到 AOP 代理和事务失效，梳理 Spring 与 Spring Boot 的高频面试链路 Spring 题有一条很自然的链路：容器怎么创建 Bean、怎么给 Bean 加能力、事务为什么依赖代理、Boot 又怎么把这一切自动化。按这条线准备，比孤立背注解有效。以下默认 Spring Framework 6 \u002F Spring Boot 3。",1,{"id":428,"title":397,"titles":429,"content":430,"level":394},"\u002Fjava-interview\u002Fspring#_1-怎么理解-ioc-和-dibeanfactory-与-applicationcontext-有什么区别",[5],"IoC 是控制反转：对象的创建、依赖装配和生命周期交给容器，业务代码只声明需要什么。DI 是它的实现方式，通过构造器、setter 或字段注入把依赖传进来。 BeanFactory 是最基础的容器接口，提供 getBean 和 Bean 生命周期管理，默认懒加载。ApplicationContext 继承它，额外提供资源加载、事件发布、国际化、AOP 集成和注解支持，单例默认在启动时实例化。 实践上优先构造器注入：依赖不可变、便于单元测试、能暴露循环依赖问题。字段注入写法简单，但会让类难以脱离容器测试，也掩盖了职责过多的信号。",{"id":432,"title":48,"titles":433,"content":434,"level":394},"\u002Fjava-interview\u002Fspring#_2-bean-的生命周期是怎样的",[5],"大致分四段：实例化、属性填充、初始化、销毁。 实例化通过构造器或工厂方法完成；随后注入依赖并回调各类 Aware 接口，例如 BeanNameAware、ApplicationContextAware。接着执行 BeanPostProcessor 的前置处理，再调用 @PostConstruct、InitializingBean.afterPropertiesSet 和自定义 init-method，最后执行后置处理——AOP 代理通常就在这一步生成。 使用阶段结束后，容器关闭时依次调用 @PreDestroy、DisposableBean.destroy 和 destroy-method。注意原型 Bean 的销毁方法不会由容器调用，需要调用方自己负责。 BeanPostProcessor 是理解 Spring 扩展性的关键：它能在初始化前后改写甚至替换 Bean，@Autowired、@Async、事务代理都依赖这一类扩展点。",{"id":436,"title":111,"titles":437,"content":438,"level":394},"\u002Fjava-interview\u002Fspring#_3-循环依赖是怎么解决的为什么构造器注入不行",[5],"单例 Bean 的字段或 setter 循环依赖，靠三级缓存解决：一级放成品 Bean，二级放提前暴露的早期引用，三级放能生成早期引用的工厂。 创建 A 时先实例化并放入三级缓存，填充属性发现依赖 B，转去创建 B；B 依赖 A 时从缓存拿到 A 的早期引用，B 完成后 A 继续填充并最终完成。三级缓存的意义在于：如果 A 需要被 AOP 代理，工厂能在暴露早期引用时就生成代理，而不是暴露原始对象。 构造器注入要求在实例化阶段就拿到依赖，此时对象还不存在，无法提前暴露，因此会抛 BeanCurrentlyInCreationException。Spring Boot 2.6 起默认禁止循环依赖，需要显式开启才恢复旧行为；更好的做法是重构依赖或使用 @Lazy、ObjectProvider 延迟获取。",{"id":440,"title":135,"titles":441,"content":442,"level":394},"\u002Fjava-interview\u002Fspring#_4-bean-是线程安全的吗单例要注意什么",[5],"Spring 不保证 Bean 线程安全，它只负责创建和管理。单例 Bean 被所有请求共享，只要存在可变的成员状态，就可能出现并发问题。 无状态的 Service、DAO 天然安全；有状态的对象要么改成方法内局部变量，要么用 ThreadLocal（并记得清理），要么把作用域改成原型。给单例加 synchronized 通常只是把瓶颈集中到一个点上。 常见误区是把 @Scope(\"prototype\") 注到单例里就以为每次调用都会新建：注入只发生一次，单例持有的是同一个原型实例。需要每次获取新对象时应注入 ObjectProvider 或使用作用域代理。",{"id":444,"title":163,"titles":445,"content":446,"level":394},"\u002Fjava-interview\u002Fspring#_5-spring-aop-的原理是什么为什么自调用会失效",[5],"Spring AOP 基于动态代理：目标类实现了接口时可用 JDK 代理，否则用 CGLIB 生成子类。Spring Boot 默认 proxyTargetClass = true，即优先 CGLIB，所以 final 类和 final 方法无法被代理。 调用方拿到的是代理对象，代理在方法前后织入通知。所谓自调用失效，是指同类中 this.otherMethod() 绕过了代理，通知自然不会执行；事务、缓存、异步注解都会遇到这个问题。 解决方式：拆到另一个 Bean、注入自身代理、使用 AopContext.currentProxy()（需开启 exposeProxy），或者改用 AspectJ 编译期\u002F加载期织入。生产上更推荐前两种，侵入性小。",{"id":448,"title":403,"titles":449,"content":450,"level":394},"\u002Fjava-interview\u002Fspring#_6-transactional-在哪些情况下会失效传播行为怎么选",[5],"失效的常见原因：自调用绕过代理；方法不是 public（Spring 6 起类代理支持 protected 和包可见方法，但仍有边界）；Bean 不是由容器管理；异常被 catch 后没有抛出；抛出的是受检异常而默认只回滚 RuntimeException 和 Error；数据库表本身不支持事务；在另一个线程里执行。 传播行为中，REQUIRED 是默认值，加入现有事务；REQUIRES_NEW 挂起当前事务、独立提交，适合记日志等「必须落库」的操作；NESTED 使用保存点，只对 JDBC 等支持保存点的资源有效。 还要注意只读事务和超时设置，以及事务内不要做远程调用——长事务会长期占用连接和锁。",{"id":452,"title":243,"titles":453,"content":454,"level":394},"\u002Fjava-interview\u002Fspring#_7-spring-boot-自动配置的原理",[5],"@SpringBootApplication 组合了配置类、组件扫描和 @EnableAutoConfiguration。后者通过 AutoConfigurationImportSelector 读取候选配置类：Boot 2.7 起使用 META-INF\u002Fspring\u002Forg.springframework.boot.autoconfigure.AutoConfiguration.imports，更早的版本用 spring.factories。 候选类并不会全部生效，而是由条件注解筛选：类路径上是否存在某个类、容器里是否已有同类型 Bean、配置项是否开启。这就是「引入 starter 就自动装配，同时允许自定义覆盖」的机制。 自定义覆盖之所以生效，是因为自动配置类通常带 @ConditionalOnMissingBean，且用户配置先于自动配置处理。排查自动配置问题时，可以开启 debug=true 查看条件评估报告。",{"id":456,"title":283,"titles":457,"content":458,"level":394},"\u002Fjava-interview\u002Fspring#_8-一个-http-请求在-spring-mvc-里是怎么走完的",[5],"请求先经过 Servlet 容器的过滤器链，进入 DispatcherServlet。它向 HandlerMapping 询问由哪个处理器处理，拿到处理器和拦截器链后，交给匹配的 HandlerAdapter。 HandlerAdapter 调用参数解析器把请求参数、路径变量、请求体转换成方法参数，执行 Controller 方法；返回值再由返回值处理器处理，@ResponseBody 走 HttpMessageConverter 序列化，否则交给视图解析器渲染。 拦截器的 preHandle 在处理器执行前调用，postHandle 在渲染前调用，afterCompletion 在完成后调用。过滤器属于 Servlet 规范，能拿到原始请求但不能感知处理器；拦截器属于 Spring MVC，能访问处理器信息。",{"id":460,"title":328,"titles":461,"content":462,"level":394},"\u002Fjava-interview\u002Fspring#易错点与自测",[5],"三级缓存只解决单例的字段\u002Fsetter 循环依赖，不解决构造器注入和原型作用域。 AOP 的一切「注解不生效」几乎都可以归结为「没走代理」。 自动配置不是「无条件装配」，条件注解和加载顺序才是重点。 自测：① 画出 Bean 从实例化到销毁的关键回调顺序；② 说出三种让 @Transactional 恢复生效的改法；③ 解释 REQUIRES_NEW 与 NESTED 在回滚行为上的差异。",{"id":464,"title":355,"titles":465,"content":466,"level":394},"\u002Fjava-interview\u002Fspring#参考资料",[5],"Spring Framework：IoC 容器与 Bean 生命周期 — 容器、作用域与扩展点 Spring Framework：事务管理 — 传播行为与回滚规则 Spring Boot：自动配置 — 条件注解与加载顺序 Spring Framework：AOP 编程 — 代理机制与自调用问题",1793263559713]