Bean 生命周期与循环依赖¶
一句话记忆口诀:
实例化 → 属性注入 → Aware → BPP#before → 初始化 → BPP#after(AOP 代理)→ SmartInitializingSingleton → 使用 → 销毁(LIFO);
三级缓存提前暴露半成品,一级存完整 Bean,二级存早期引用,三级存工厂(支持 AOP 代理),构造器注入无法提前暴露所以不能解决循环依赖。📖 边界声明:本文聚焦"单个 Bean 从
createBean()到destroy()之间发生了什么",以下主题请见对应专题:
refresh()12 步宏观启动流程、getBean宏观入口 → Spring 容器启动流程深度解析BeanDefinition静态结构、DependencyDescriptor依赖解析算法、字段/构造器/Setter 三种注入方式对比 → IoC 与 DIBeanPostProcessor/BeanFactoryPostProcessor家族的完整成员矩阵与自定义实现 → Spring 扩展点详解- AOP 代理生成的内部流程(JDK/CGLIB 选择、
ProxyFactory内部)与同类自调用失效的工程解法 → AOP 面向切面编程- 三级缓存完整源码案例、调试技巧 → 本文 §6(源头),其他姊妹文档 📖 引用此处
1. 引入:生命周期为何是高级开发必修¶
很多开发者停留在"8 步流程图"的死记层面,但面试与排障时真正被考察的是更深的一层:
- 为什么
@PostConstruct里的@Autowired不会为null,而构造器里会? @EventListener注册到底发生在生命周期的哪一步?@ConfigurationProperties的属性绑定发生在populateBean之前还是之后?- Spring 6 升级后以前能跑的循环依赖为什么突然报错?
- 为什么
@Async方法在同类内调用不生效?是"同一个问题的另一个表现"吗?
这些问题的共同答案是:生命周期不是 8 步,而是 8 个"显式阶段" + 若干"隐式钩子",它们共同定义了 Bean 从字节码到可用对象的全部状态转移。
2. 类比:Bean 的一生就像员工入职到离职¶
招聘(实例化)→ 培训(属性注入)→ 报到(Aware 回调)→ 入职审查(BeanPostProcessor before)
→ 上岗(初始化)→ 转正(BeanPostProcessor after,AOP 代理在此创建)
→ 全员到齐公告(SmartInitializingSingleton)→ 工作(使用中)→ 离职(销毁)
3. 完整生命周期流程(显式 8 步 + 隐式钩子)¶
flowchart LR
A["① 实例化<br>createBeanInstance<br>反射/@Bean/FactoryBean"] --> B["② 属性注入<br>populateBean<br>+ InstantiationAwareBPP"]
B --> C["③ Aware 回调<br>invokeAwareMethods"]
C --> D["④ BPP#before<br>@PostConstruct 实际在此触发"]
D --> E["⑤ 初始化<br>InitializingBean → init-method"]
E --> F["⑥ BPP#after<br>⚠️ AOP代理在此创建"]
F --> G["⑦ SmartInitialzingSingleton<br>所有单例就绪后的全局钩子"]
G --> H["⑧ 使用中 In Use"]
H --> I["⑨ 销毁(LIFO)<br>@PreDestroy → destroy → destroy-method"] 阶段与源码方法的一一对应(全部位于
AbstractAutowireCapableBeanFactory):doCreateBean() ├─ createBeanInstance() ← 第 ① 步 ├─ applyMergedBeanDefinitionPostProcessors() ← 合并元数据 ├─ populateBean() ← 第 ② 步(内含隐式钩子) └─ initializeBean() ├─ invokeAwareMethods() ← 第 ③ 步 ├─ applyBeanPostProcessorsBeforeInitialization() ← 第 ④ 步 ├─ invokeInitMethods() ← 第 ⑤ 步 └─ applyBeanPostProcessorsAfterInitialization() ← 第 ⑥ 步 preInstantiateSingletons() 最后循环回调 SmartInitializingSingleton ← 第 ⑦ 步
① 实例化 Instantiation¶
容器调用 createBeanInstance(),按 IoC 与 DI §7 列出的三条路径之一创建原始对象:反射构造器 / @Bean 工厂方法 / FactoryBean。此时所有字段均为默认值(null / 0),单例 Bean 的 ObjectFactory 在这一步完成后立刻放入三级缓存(为循环依赖做准备)。
隐式钩子:InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
在 createBeanInstance() 之前,容器会先询问所有 InstantiationAwareBeanPostProcessor:"你要不要直接返回一个对象替代正常的实例化?" 如果某个 BPP 返回非 null,生命周期立即短路——跳过后续所有步骤,直接进入第⑥步。AOP 的 AnnotationAwareAspectJAutoProxyCreator 就是靠这个钩子实现"为某些 Bean 提前创建代理"。
② 属性注入 Populate¶
populateBean() 扫描字段与 setter 上的 @Autowired / @Value / @Resource,通过 DependencyDescriptor 解析(算法见 IoC 与 DI §6)。@Value("${...}") 占位符在容器启动阶段由 PropertySourcesPlaceholderConfigurer(一个 BeanFactoryPostProcessor)在 BeanDefinition 级别预解析,此步只是把最终值填入。
隐式钩子:InstantiationAwareBeanPostProcessor#postProcessProperties
注入的真正执行者是 AutowiredAnnotationBeanPostProcessor(处理 @Autowired)和 CommonAnnotationBeanPostProcessor(处理 @Resource),它们都是 InstantiationAwareBeanPostProcessor 的实现,在这一步通过反射对字段 / setter 赋值。 Spring Boot 的 @ConfigurationProperties 也在此阶段绑定属性(通过 ConfigurationPropertiesBindingPostProcessor)。
③ Aware 接口回调¶
📖 术语家族:*Aware
字面义:Aware = "知晓的 / 感知的"。 在本框架中的含义:Spring 容器在 Bean 初始化阶段回调,把 Bean 所处的上下文信息"告诉"Bean——谁想拿到 Xxx,就实现 XxxAware 接口。 同家族成员:
| 成员 | 注入内容 | 触发时机 | 源码位置 |
|---|---|---|---|
BeanNameAware | Bean 在容器中的 id | 第③步 invokeAwareMethods | org.springframework.beans.factory.BeanNameAware |
BeanClassLoaderAware | 加载该 Bean 的 ClassLoader | 第③步 invokeAwareMethods | org.springframework.beans.factory.BeanClassLoaderAware |
BeanFactoryAware | 基础容器 BeanFactory | 第③步 invokeAwareMethods | org.springframework.beans.factory.BeanFactoryAware |
ApplicationContextAware | 企业级容器 ApplicationContext | 第④步(经 ApplicationContextAwareProcessor) | org.springframework.context.ApplicationContextAware |
EnvironmentAware | Environment 对象 | 第④步(同上) | org.springframework.context.EnvironmentAware |
ResourceLoaderAware | ResourceLoader | 第④步(同上) | org.springframework.context.ResourceLoaderAware |
MessageSourceAware | MessageSource(国际化) | 第④步(同上) | org.springframework.context.MessageSourceAware |
命名规律:XxxAware = "想拿到 Xxx 的 Bean 就实现这个接口"。两类触发路径:基础容器级(BeanName / BeanClassLoader / BeanFactory)由 invokeAwareMethods 在第③步显式调用;应用上下文级(ApplicationContext / Environment / ResourceLoader / MessageSource 等)由 ApplicationContextAwareProcessor 这个 BeanPostProcessor 在第④步统一回调——严格按源码它们属于第④步,只是概念上归为 Aware 家族。
④ BeanPostProcessor#before¶
📖 术语家族:*Processor / BeanPostProcessor(简称 BPP)
字面义:Processor = "处理器";Post = "在...之后"(BPP 即"Bean 之后的处理器",相对于 BeanFactoryPostProcessor 的"BeanFactory 之后")。 在本框架中的含义:Spring 对单个 Bean 实例的标准加工流水线上的拦截点——每个 Bean 在生命周期的固定阶段都会被全部 BPP 依次"过一遍"。 同家族成员:
| 成员 | 关键钩子方法 | 触发阶段 | 典型实现 |
|---|---|---|---|
BeanPostProcessor | postProcessBeforeInitialization / postProcessAfterInitialization | 第④ / 第⑥步 | ApplicationContextAwareProcessor(Aware 回调)、AbstractAutoProxyCreator(AOP 代理生成) |
InstantiationAwareBeanPostProcessor | postProcessBeforeInstantiation / postProcessProperties | 第①步前 / 第②步 | AutowiredAnnotationBeanPostProcessor(@Autowired)、CommonAnnotationBeanPostProcessor(@Resource / @PostConstruct) |
SmartInstantiationAwareBeanPostProcessor | getEarlyBeanReference | 循环依赖触发时 | AbstractAutoProxyCreator(提前生成代理) |
MergedBeanDefinitionPostProcessor | postProcessMergedBeanDefinition | 第①步与第②步之间 | AutowiredAnnotationBeanPostProcessor(缓存注入点元数据) |
DestructionAwareBeanPostProcessor | postProcessBeforeDestruction | 第⑨步 | InitDestroyAnnotationBeanPostProcessor(@PreDestroy) |
继承关系:
flowchart TB
BPP["BeanPostProcessor<br>before/after Initialization"]
IABPP["InstantiationAwareBeanPostProcessor<br>before Instantiation / postProcessProperties"]
SIABPP["SmartInstantiationAwareBeanPostProcessor<br>getEarlyBeanReference"]
MBDPP["MergedBeanDefinitionPostProcessor<br>postProcessMergedBeanDefinition"]
DABPP["DestructionAwareBeanPostProcessor<br>postProcessBeforeDestruction"]
BPP --> IABPP
IABPP --> SIABPP
BPP --> MBDPP
BPP --> DABPP 命名规律:XxxBeanPostProcessor = "在 Bean 生命周期的某个阶段插一刀";前缀决定插入点——Instantiation* 管实例化前后(第①~②)、SmartInstantiation* 管循环依赖提前代理、MergedBeanDefinition* 管元数据合并、Destruction* 管销毁。BPP vs BFPP 的本质分工:BPP 改单个 Bean 实例;BeanFactoryPostProcessor(BFPP)改整个容器的 BeanDefinition 元数据——详见 Spring 扩展点详解 §3~§4。
applyBeanPostProcessorsBeforeInitialization() 遍历所有 BeanPostProcessor,依次调用 postProcessBeforeInitialization()。
@PostConstruct 的真实触发点在这里,不在第⑤步
很多资料说 "@PostConstruct 在初始化阶段执行"——不精确。实际上它由 CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization 触发,在第④步而非第⑤步。只是因为第④和第⑤步紧挨着执行、都在属性注入之后,从业务视角合并表述为"初始化"。
⑤ 初始化 Initialization¶
invokeInitMethods() 按固定顺序执行业务初始化钩子:
InitializingBean.afterPropertiesSet()(实现接口方式)@Bean(initMethod = "xxx")或 XMLinit-method(声明式)
📖 术语家族:Bean 初始化/销毁钩子三兄弟
同一个"初始化时机"有三套并行的 API,分别来自三套命名哲学:
| 钩子 | 来源 | 触发阶段 | 与 Spring 耦合度 | 典型使用方 |
|---|---|---|---|---|
@PostConstruct / @PreDestroy | JSR-250(jakarta.annotation) | 第④步 / 第⑨步(由 CommonAnnotationBeanPostProcessor 触发) | 零耦合(标准注解) | 业务代码首选 |
InitializingBean#afterPropertiesSet / DisposableBean#destroy | Spring 专有接口 | 第⑤步 / 第⑨步 | 强耦合(侵入 Spring 类型) | Spring 框架内部组件(如 SqlSessionFactoryBean) |
@Bean(initMethod=/destroyMethod=) / XML init-method / destroy-method | 声明式配置 | 第⑤步 / 第⑨步(在 InitializingBean 之后) | 零耦合(仅配置) | 集成第三方库(无法改源码时) |
命名规律:@PostConstruct(JSR-250:"构造之后")= afterPropertiesSet(Spring:"属性就绪之后")= 声明式 init-method——三者语义等价、触发时机紧邻(④→⑤→⑤),执行顺序是 @PostConstruct → afterPropertiesSet → init-method。为什么 Spring 自身偏爱 afterPropertiesSet、业务偏爱 @PostConstruct:框架内部用 Spring 接口天经地义,业务代码用 JSR-250 标准可保持与 Spring 解耦,便于将来替换容器。
⑥ BeanPostProcessor#after ⚠️ AOP 代理在此创建¶
applyBeanPostProcessorsAfterInitialization() 遍历所有 BeanPostProcessor 调用 postProcessAfterInitialization()。AbstractAutoProxyCreator 就是在这里检测 Bean 是否匹配切点(@Transactional / @Aspect / @Async),匹配则用 JDK 动态代理或 CGLIB 生成代理对象返回,替换掉原始 Bean 写入单例缓存。
为什么代理在第⑥步创建,而不是第④步
代理必须包装一个行为完整的对象——初始化逻辑(第⑤步)必须先跑完,代理才能正确拦截方法调用。如果在第④步就创建代理,@PostConstruct 就会在代理对象上执行,被不必要地套上事务/AOP 语义,行为不可控。 唯一例外:循环依赖场景下,Spring 会在第②步(通过三级缓存的 ObjectFactory)提前创建代理,以保证所有持有 A 引用的 Bean 拿到的都是同一个代理对象。详见第 6 节。
⑦ SmartInitializingSingleton —— 所有单例就绪后的全局钩子¶
当容器在 preInstantiateSingletons() 循环中把最后一个单例 Bean 创建完毕后,会再遍历一次所有单例,调用实现了 SmartInitializingSingleton#afterSingletonsInstantiated() 的 Bean。
| 对比点 | @PostConstruct / afterPropertiesSet | SmartInitializingSingleton |
|---|---|---|
| 触发时机 | 自己初始化完时 | 容器所有单例都初始化完后 |
| 可见性 | 只能看到自己已注入的依赖 | 可以 getBeansOfType 看到全部 Bean |
| 典型使用者 | 业务 Bean 的初始化 | EventListenerMethodProcessor(注册 @EventListener)、ScheduledAnnotationBeanPostProcessor(启动定时任务) |
需要扫描所有 Bean 再做决定的场景
比如"遍历容器里所有 XxxProcessor 构建一个路由表"——这种逻辑放在 @PostConstruct 里会丢失后注册的 Bean;必须实现 SmartInitializingSingleton,等全员到齐再扫一遍。
⑧ 使用中 In Use¶
外部通过 getBean() 或 @Autowired 拿到的是第⑥步最终返回的对象(可能是代理)。
⑨ 销毁 Destruction(LIFO)¶
容器关闭(close() / stop())时反向执行:
@PreDestroy标注的方法(由CommonAnnotationBeanPostProcessor处理)DisposableBean.destroy()@Bean(destroyMethod = "xxx")或 XMLdestroy-method
两条销毁规则
- prototype 作用域的 Bean 不会触发销毁——容器不持有其引用,实例由 GC 负责回收。
@DependsOn声明的依赖链在销毁时自动反序(LIFO):若 A@DependsOn("B"),创建顺序是 B → A,销毁顺序是 A → B,保证 A 在销毁时 B 还活着。
4. 生命周期全景对照表(高频面试)¶
| 阶段 | 源码方法 | 触发的扩展点 | 典型业务动作 |
|---|---|---|---|
| 实例化前 | resolveBeforeInstantiation | InstantiationAwareBPP#before...Instantiation | AOP 提前代理 |
| ① 实例化 | createBeanInstance | 三条路径 | 反射 new |
| 元数据合并 | applyMergedBeanDefinitionPostProcessors | MergedBeanDefinitionPostProcessor | 缓存 @Autowired 注入点 |
| ② 属性注入 | populateBean | InstantiationAwareBPP#postProcessProperties | @Autowired / @Value / @ConfigurationProperties 绑定 |
| ③ Aware | invokeAwareMethods | 6 大 Aware 接口 | 拿容器 / BeanName |
| ④ BPP#before | applyBeanPostProcessorsBeforeInitialization | BeanPostProcessor#before...Initialization | @PostConstruct |
| ⑤ 初始化 | invokeInitMethods | InitializingBean / init-method | 预热缓存、建立连接 |
| ⑥ BPP#after | applyBeanPostProcessorsAfterInitialization | BeanPostProcessor#after...Initialization | AOP 代理生成 |
| ⑦ 全员到齐 | preInstantiateSingletons 末尾 | SmartInitializingSingleton | @EventListener 注册、@Scheduled 启动 |
| ⑧ 使用 | - | - | 业务调用 |
| ⑨ 销毁 | DisposableBeanAdapter | @PreDestroy / DisposableBean | 释放连接、落盘 |
5. 不理解底层会踩的坑¶
误区 1:在构造器中使用 @Autowired 注入的字段¶
// ❌ 构造器在第①步执行,@Autowired 在第②步才注入,此时字段是 null
@Component
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public CacheService() {
redisTemplate.opsForValue().set("init", "true"); // NullPointerException
}
}
// ✅ 方案一(首选):构造器注入,依赖在实例化时就已传入
@Component
public class CacheService {
private final RedisTemplate<String, Object> redisTemplate;
public CacheService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
redisTemplate.opsForValue().set("init", "true"); // 安全
}
}
// ✅ 方案二:初始化逻辑移到 @PostConstruct(第④步执行,字段已就绪)
@Component
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@PostConstruct
public void init() {
redisTemplate.opsForValue().set("init", "true");
}
}
误区 2:以为 @Scope("prototype") 的 Bean 也会执行 @PreDestroy¶
Spring 不管理 prototype Bean 的销毁——若跟踪所有 prototype 实例会导致内存泄漏(持有全部引用)。因此 prototype Bean 的 @PreDestroy 永远不会被容器调用,必须业务代码自己管理。
误区 3:手动 new 的对象期望 @Autowired 生效¶
只有容器管理的 Bean 才会走生命周期。如果确实需要对容器外对象做注入,用 AutowireCapableBeanFactory.autowireBean(obj)(见 IoC 与 DI §3.1)。
误区 4:把 @PostConstruct 当作"全员到齐"的钩子¶
@PostConstruct 只能看到自己注入的依赖,看不到后创建的兄弟 Bean。需要"扫描全部同类型 Bean 再处理"的场景,必须改用 SmartInitializingSingleton 或 ApplicationListener<ContextRefreshedEvent>。
6. 循环依赖与三级缓存¶
6.1 循环依赖是什么¶
本质:对象图中存在环,对象构造期无法按"拓扑序"完成。
Spring 6 / Boot 3 默认禁用循环依赖
Spring Framework 6.0+ 将 spring.main.allow-circular-references 默认值改为 false——字段/Setter 注入的循环依赖启动时直接抛 BeanCurrentlyInCreationException。Boot 2 升级 Boot 3 时的典型症状。 治本:重构设计(见 6.5);治标:
6.2 三级缓存的源码定位¶
全部位于 DefaultSingletonBeanRegistry:
📖
ObjectFactory属*Factory家族,与BeanFactory/FactoryBean的命名差异详见 IoC 与 DI。这里只关注它作为"延迟的早期引用生成器"的角色。
// 一级缓存:完整 Bean
private final Map<String, Object> singletonObjects
= new ConcurrentHashMap<>(256);
// 二级缓存:早期引用(已完成代理判断,但属性/初始化未完成)
private final Map<String, Object> earlySingletonObjects
= new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory(延迟生成早期引用 / 决定是否提前代理)
private final Map<String, ObjectFactory<?>> singletonFactories
= new HashMap<>(16);
| 缓存 | 存什么 | 何时放入 | 何时移出 |
|---|---|---|---|
三级 singletonFactories | ObjectFactory(封装"要不要代理"的逻辑) | 实例化完成后(第①之后、第②之前) | 被取用后立刻移除,结果迁入二级 |
二级 earlySingletonObjects | 早期 Bean 引用(可能是代理) | 三级工厂首次被调用时 | Bean 完整创建后移入一级 |
一级 singletonObjects | 完成全生命周期的 Bean | 第⑥步结束后 | 容器关闭时清空 |
查找顺序:一级 → 二级 → 三级(通过工厂生成后放入二级)。
6.3 三级缓存解决循环依赖的完整流程¶
flowchart LR
A1["实例化 A<br>放入三级缓存"] --> A2["A 属性注入<br>发现要 B"]
A2 --> B1["实例化 B<br>放入三级缓存"]
B1 --> B2["B 属性注入<br>发现要 A"]
B2 --> B3["从三级缓存取 A<br>调用 ObjectFactory.getObject()<br>(决定是否提前代理)<br>结果存入二级缓存"]
B3 --> B4["B 拿到 A 的早期引用<br>完成注入、初始化<br>→ 一级缓存"]
B4 --> A3["A 完成 B 的注入(B 已在一级)<br>A 完成初始化<br>→ 一级缓存"] 完整事件序列:
① new A() → 放入【三级缓存】(此时 A 字段全 null)
② A 开始 populateBean,发现要 B
① new B() → 放入【三级缓存】
② B 开始 populateBean,发现要 A
查一级缓存 → 无
查二级缓存 → 无
查三级缓存 → 有!调用 ObjectFactory.getObject()
→ AbstractAutoProxyCreator.getEarlyBeanReference()
→ 判断 A 是否匹配切点
→ 匹配:立即生成 A 的 AOP 代理
→ 不匹配:返回原始 A
→ 结果放入【二级缓存】,从三级缓存删除 A 的条目
② B 拿到 A 的早期引用,完成注入
⑤ B 初始化 → ⑥ BPP#after → 【一级缓存】
② A 拿到 B(B 已是完整对象)
⑤ A 初始化 → ⑥ BPP#after
此时检查:二级缓存里有 A 的早期引用吗?
有 → 说明 A 已被循环依赖触发过代理 → 复用二级缓存对象
无 → 按正常流程创建代理(普通场景)
⑦ A 完成 → 【一级缓存】,从二级缓存移除
为什么要三级缓存而不是二级
常见错误说法:"二级缓存会 AOP 失效"。严谨解释:
如果在第①步之后立刻判断是否需要代理并把结果存入二级缓存,AOP 不会失效。但这意味着每个 Bean 实例化后都要遍历所有切点做匹配——这是昂贵的,而 99% 的 Bean 不会发生循环依赖,这部分开销是浪费的。三级缓存的 ObjectFactory 把"代理判断"延迟到"Bean 自己还在创建途中、别人回头来取它"的那一刻才执行。触发条件很苛刻(见 DefaultSingletonBeanRegistry#getSingleton):必须同时满足
- ①当前 Bean 处于"正在创建中"状态(
isSingletonCurrentlyInCreation为true) - ②一级缓存未命中
- ③二级缓存也未命中
这只在循环依赖路径上才会成立。没有循环依赖时,Bean 走完生命周期直接进一级缓存,后续所有 @Autowired 注入、getBean 调用都在 singletonObjects.get() 一击命中,ObjectFactory 从未被触碰;有循环依赖时才按需触发,并把结果缓存在二级缓存避免重复生成。
一句话:三级缓存 = 二级缓存 + 懒加载优化。
6.4 为什么构造器注入无法解决循环依赖¶
三级缓存的前提是"Bean 先被 new 出来放入缓存,再注入依赖"。但构造器注入在 new 这一步就需要依赖——A 还没机会进缓存,B 来查 A 时三级缓存里什么都没有 → 死锁,抛 BeanCurrentlyInCreationException。
6.5 解决方案¶
| 方案 | 适用场景 | 原理 |
|---|---|---|
| 重构:提取公共依赖 | 首选 | A ↔ B 改成 A → C ← B,环被打破 |
| 重构:事件解耦 | 发布-订阅类场景 | 用 ApplicationEventPublisher 替代直接引用,见 扩展点详解 §6 |
@Lazy | 构造器注入被迫循环时 | 注入代理对象,首次调用时才解析目标 |
字段注入 + allow-circular-references=true | 历史遗留代码临时救场 | 依赖三级缓存,不推荐长期使用 |
// @Lazy 打破构造器注入的循环
@Component
public class A {
private final B b;
public A(@Lazy B b) { this.b = b; } // 注入 B 的代理,首次调用才真正初始化 B
}
决策树
7. 与其他代理机制的边界:同类自调用失效¶
@Async / @Scheduled / @Transactional / @Cacheable 在同类内部调用时全部失效,它们本质是"生命周期第⑥步代理副作用"的同一问题。
@Service
public class OrderService {
public void createOrder() {
this.sendMail(); // ❌ this 是原始对象,不走代理,@Async 失效
}
@Async // 依赖 BPP#after 生成的代理来拦截
public void sendMail() { ... }
}
📖 完整排查清单与解决方案(注入自身代理、
AopContext.currentProxy()等)见 AOP 面向切面编程 §4。本文只强调:这是"代理模式的本质限制",而非生命周期 bug。
8. 高频面试题(源码级标准答案)¶
Q1:Spring Bean 完整生命周期?
源码入口
doCreateBean():①createBeanInstance(反射实例化)→ ②populateBean(属性注入,内含InstantiationAwareBPP)→ ③invokeAwareMethods→ ④applyBeanPostProcessorsBeforeInitialization(@PostConstruct实际触发点)→ ⑤invokeInitMethods(InitializingBean/init-method)→ ⑥applyBeanPostProcessorsAfterInitialization(AOP 代理生成);所有单例到齐后,容器再调用 ⑦SmartInitializingSingleton→ ⑧ 使用 → ⑨ 销毁(@PreDestroy/DisposableBean/destroy-method,LIFO)。
Q2:@PostConstruct 和 afterPropertiesSet 到底谁先执行?
@PostConstruct在第④步由CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization触发;afterPropertiesSet在第⑤步invokeInitMethods中触发。所以执行顺序是@PostConstruct→afterPropertiesSet→init-method。虽然很多资料把它们都归在"初始化",但严格按源码它们处于两个不同阶段。
Q3:Spring 如何解决循环依赖?
通过三级缓存解决字段/Setter 注入的单例循环依赖。本质是"先把半成品放入缓存再注入依赖":
new A后立刻把 A 的ObjectFactory放入三级缓存 → 注入 B 时发现要 A → 从三级缓存触发ObjectFactory生成 A 的早期引用(必要时提前创建 AOP 代理)并转入二级缓存 → B 拿到 A 的引用完成创建 → A 再完成自身的属性注入和初始化。构造器注入无法解决,因为实例化阶段就要依赖,A 还来不及放入缓存。Spring 6 默认禁用该机制,需显式allow-circular-references=true。
Q4:为什么需要三级缓存,二级不够吗?
技术上二级缓存能解决循环依赖,三级缓存的意义是延迟代理判断、避免性能浪费。判断 Bean 是否需要 AOP 代理需要遍历全部切点做匹配,成本不低;99% 的 Bean 不会发生循环依赖,如果每个 Bean 实例化后都立刻做这个判断是浪费。三级缓存的
ObjectFactory把这次判断延迟到真正有人来查时才执行——没有循环依赖时根本不触发,节省了启动期大量开销;有循环依赖时才按需生成并缓存到二级避免重复。
Q5:构造器注入为什么不能解决循环依赖?
构造器注入在
createBeanInstance()这一步就要传入依赖,此时 Bean 还没有机会被放进任何缓存——三级缓存需要"先new再暴露"的前提被破坏。字段注入可以"先new一个空对象放入缓存,再通过反射注入依赖",所以能走三级缓存。
Q6:@EventListener 是什么时候注册到事件广播器的?
不是在第④步或第⑤步,而是在第⑦步
SmartInitializingSingleton#afterSingletonsInstantiated中,由EventListenerMethodProcessor扫描全部单例 Bean 上的@EventListener方法注册。这也是为什么@EventListener不能监听"容器刷新完成前"发出的事件——它自己还没注册。
Q7:Spring 6 升级后循环依赖启动报错怎么处理?
先考虑重构:① 提取公共依赖成第三个类;② 把主动调用改为发布
ApplicationEvent解耦。如果必须先跑起来,临时开启spring.main.allow-circular-references=true,但要在日志里打红标警示团队"这是技术债"。
9. 一句话口诀¶
显式 8 步 + 隐式钩子:
createBeanInstance→populateBean(含InstantiationAwareBPP)→invokeAwareMethods→BPP#before(@PostConstruct)→invokeInitMethods(InitializingBean)→BPP#after(AOP 代理)→SmartInitializingSingleton(@EventListener注册)→ 使用 →destroy(LIFO)。三级缓存:一级存完整 Bean、二级存早期引用、三级存工厂;工厂的存在只为"延迟代理判断"。Spring 6 默认禁用循环依赖;构造器注入在实例化阶段就要依赖,所以永远无法被三级缓存救。