Spring Bean 生命周期:从实例化到销毁的完整钩子
关键词标签:Spring 源码、Bean 生命周期、BeanPostProcessor、Aware 接口、循环依赖
一个反直觉的结论
很多人以为@PostConstruct是"Bean 初始化完成后"执行的。但打开AbstractAutowireCapableBeanFactory#initializeBean的源码你会发现,@PostConstruct所在的CommonAnnotationBeanPostProcessor是在postProcessBeforeInitialization里被调用的——也就是说,它执行的时候,InitializingBean#afterPropertiesSet和init-method还没跑。名字叫 "PostConstruct",位置却在 "BeforeInitialization",这是 Spring 生命周期里最容易被误解的一处。
本文不重复本系列已经讲过的自动配置、线程池、AQS 等话题,直接把AbstractAutowireCapableBeanFactory这条主线走一遍,看清楚每个钩子真实的位置、顺序,以及背后的设计动机。
一、先把主干流程钉死:doCreateBean的骨架
Spring 单例 Bean 的创建入口是AbstractAutowireCapableBeanFactory#doCreateBean。它的主干逻辑可以简化为下面这段(源码结构,非逐行照抄):
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 1. 实例化:反射调用构造器 / 工厂方法 instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); // 2. 提前暴露:允许循环依赖时被其它 Bean 引用(三级缓存) boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // 3. 属性填充:@Autowired / @Value / XML property populateBean(beanName, mbd, instanceWrapper); // 4. 初始化:各类 Aware + 后置处理器 + init 方法 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { // ... } // 5. 注册销毁回调 registerDisposableBeanIfNecessary(beanName, bean, mbd); return exposedObject; }五个阶段:实例化 → 提前暴露 → 属性填充 → 初始化 → 注册销毁。所有钩子都挂在这五步上。
二、实例化阶段:构造器里拿不到依赖
createBeanInstance会走determineConstructorsFromBeanPostProcessors,这一步会调用所有SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors。AutowiredAnnotationBeanPostProcessor就是在这里决定用哪个构造器(比如@Autowired标注的那个,或唯一的非默认构造器)。
关键边界:此时populateBean还没执行,所以:
- 构造器里通过字段
@Autowired注入的属性一定是null; - 构造器里调用
@PostConstruct方法也不行,方法还没被识别; - 如果你在构造器里
this.xxxService.doSomething(),NPE 基本是必然的。
一种常见场景是:OrderValidator在构造器里想调用OrderRepository校验规则——这在 Spring 下是拿不到的。正确做法是把逻辑挪到@PostConstruct或InitializingBean。
三、初始化阶段:initializeBean才是钩子密集区
initializeBean是整个生命周期最密集的地方,源码骨架如下:
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 3.1 Aware 系列:BeanNameAware / BeanClassLoaderAware / BeanFactoryAware invokeAwareMethods(beanName, bean); Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 3.2 前置后置处理器:@PostConstruct / ApplicationContextAware 在这里 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 3.3 InitializingBean#afterPropertiesSet + 自定义 init-method invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } if (mbd == null || !mbd.isSynthetic()) { // 3.4 后置后置处理器:AOP 代理、@Async 代理在这里生成 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }3.1 Aware 接口:三段式调用
invokeAwareMethods里只处理三个 Aware:
BeanNameAware#setBeanNameBeanClassLoaderAware#setBeanClassLoaderBeanFactoryAware#setBeanFactory
其余 Aware(ApplicationContextAware、EnvironmentAware、ResourceLoaderAware等)都不是在这里处理的,而是由ApplicationContextAwareProcessor在postProcessBeforeInitialization里调用。这就是为什么BeanFactoryAware比ApplicationContextAware更早拿到容器——一个在 Aware 段,一个在后置处理器段。
3.2@PostConstruct的真实位置
CommonAnnotationBeanPostProcessor继承自InitDestroyAnnotationBeanPostProcessor,它的postProcessBeforeInitialization会反射调用所有被@PostConstruct标注的方法。所以真实执行顺序是:
构造器 → populateBean → BeanNameAware → BeanFactoryAware → ApplicationContextAware → @PostConstruct → InitializingBean#afterPropertiesSet → init-method → BeanPostProcessor#postProcessAfterInitialization注意@PostConstruct排在afterPropertiesSet之前。JSR-250 的语义是"依赖注入完成后",而afterPropertiesSet的语义是"Bean 属性设置完成后",两者语义接近,但 Spring 选择了 JSR-250 在前。
3.3invokeInitMethods的顺序细节
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { ((InitializingBean) bean).afterPropertiesSet(); } if (mbd != null && bean.getClass() != NullBean.class) { String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }这段代码里有个防重逻辑:如果init-method名字恰好也是afterPropertiesSet,且 Bean 实现了InitializingBean,那它不会被调用两次。这是很多人配置@Bean(initMethod = "afterPropertiesSet")时容易忽略的边界。
3.4 AOP 代理生成在最后一步
postProcessAfterInitialization是AbstractAutoProxyCreator生成 AOP 代理的时机。这意味着:如果你在@PostConstruct里调用同类方法,走的是原始对象,AOP 切面不生效。这是@Transactional、@Async自调用失效的根源之一——代理还没生成,或者说你拿到的this根本不是代理。
四、销毁阶段:注册与触发是两回事
registerDisposableBeanIfNecessary在doCreateBean的最后被调用,它决定这个 Bean 是否需要被销毁:
- 单例且不是
DisposableBeanAdapter之外的特殊情况 → 注册到disposableBeans; - prototype Bean 的销毁不会被 Spring 管理,需要调用方自己处理。
销毁触发在DefaultSingletonBeanRegistry#destroySingletons,由AbstractApplicationContext#close或registerShutdownHook触发。执行顺序是:
@PreDestroy → DisposableBean#destroy → destroy-method和初始化反过来:@PreDestroy在前,DisposableBean#destroy在后。同样地,@PreDestroy由CommonAnnotationBeanPostProcessor处理,DisposableBean由DisposableBeanAdapter处理。
五、循环依赖与生命周期的交叉点
三级缓存(singletonFactories)本质上就是在"实例化完成但未初始化"这个窗口期,提前把ObjectFactory暴露出去。所以当 A 依赖 B、B 依赖 A 时:
- A 实例化完成,放入三级缓存;
- A 填充属性时发现依赖 B,去创建 B;
- B 填充属性时发现依赖 A,从三级缓存拿到 A 的早期引用(可能还只是原始对象,未走 AOP);
- B 完成初始化,回到 A,A 完成初始化。
边界:如果 A 需要被 AOP 代理,第 3 步拿到的早期引用是原始对象,而 A 最终暴露的是代理对象,两者不一致——这就是为什么构造器注入的循环依赖 Spring 无法解决(实例化都没完成),也是为什么@Async+ 循环依赖容易出问题。
六、完整钩子顺序速查表
| 阶段 | 钩子 | 触发位置 |
|---|---|---|
| 实例化 | 构造器 /@Autowired构造器 | createBeanInstance |
| 提前暴露 | 三级缓存ObjectFactory | addSingletonFactory |
| 属性填充 | @Autowired/@Value/ setter | populateBean |
| 初始化 | BeanNameAware/BeanClassLoaderAware/BeanFactoryAware | invokeAwareMethods |
| 初始化 | ApplicationContextAware等 | postProcessBeforeInitialization |
| 初始化 | @PostConstruct | postProcessBeforeInitialization |
| 初始化 | InitializingBean#afterPropertiesSet | invokeInitMethods |
| 初始化 | 自定义init-method | invokeInitMethods |
| 初始化 | AOP 代理生成 | postProcessAfterInitialization |
| 销毁 | @PreDestroy | DisposableBeanAdapter |
| 销毁 | DisposableBean#destroy | DisposableBeanAdapter |
| 销毁 | 自定义destroy-method | DisposableBeanAdapter |
官方参考文档对生命周期回调有完整说明:https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html
BeanPostProcessor的扩展点语义见:https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html
七、什么时候别用 / 别踩的坑
- 别在构造器里访问注入的依赖。构造器阶段
populateBean还没跑,字段一定是null。需要早期逻辑就用@PostConstruct或InitializingBean。
- 别在
@PostConstruct里调用同类@Transactional/@Async方法。此时 AOP 代理还没生成,自调用绕过代理,事务和异步都不会生效。
- 别指望 prototype Bean 的销毁钩子被调用。Spring 只对单例注册
DisposableBean,prototype 的@PreDestroy不会被容器触发,需要自己管理。
- 别把初始化和销毁的顺序搞反。初始化是
@PostConstruct → afterPropertiesSet → init-method,销毁是@PreDestroy → destroy → destroy-method,前者 JSR-250 在前,后者 JSR-250 也在前——但@PostConstruct在afterPropertiesSet之前,@PreDestroy在destroy之前,方向一致。
- 别用构造器注入去解循环依赖。三级缓存只能救 setter / 字段注入,构造器注入的循环依赖 Spring 直接抛
BeanCurrentlyInCreationException。
- 别忽略
@Bean(initMethod = "afterPropertiesSet")的防重逻辑。源码里对同名方法有专门判断,重复配置不会执行两次,但也不要依赖这个巧合。
下一篇我们顺着BeanPostProcessor这条线,聊聊@Async代理生成时机与线程池绑定的那些边界——本系列前面讲线程池时留过伏笔,这次把 Spring 侧的坑补齐。