☰
Spring Bean 生命周期:从实例化到销毁的完整钩子
2026/10/14 16:11:39 网站建设 项目流程

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#setBeanName
  • BeanClassLoaderAware#setBeanClassLoader
  • BeanFactoryAware#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 时:

  1. A 实例化完成,放入三级缓存;
  2. A 填充属性时发现依赖 B,去创建 B;
  3. B 填充属性时发现依赖 A,从三级缓存拿到 A 的早期引用(可能还只是原始对象,未走 AOP);
  4. B 完成初始化,回到 A,A 完成初始化。

边界:如果 A 需要被 AOP 代理,第 3 步拿到的早期引用是原始对象,而 A 最终暴露的是代理对象,两者不一致——这就是为什么构造器注入的循环依赖 Spring 无法解决(实例化都没完成),也是为什么@Async+ 循环依赖容易出问题。


六、完整钩子顺序速查表

阶段钩子触发位置
实例化构造器 /@Autowired构造器createBeanInstance
提前暴露三级缓存ObjectFactoryaddSingletonFactory
属性填充@Autowired/@Value/ setterpopulateBean
初始化BeanNameAware/BeanClassLoaderAware/BeanFactoryAwareinvokeAwareMethods
初始化ApplicationContextAware等postProcessBeforeInitialization
初始化@PostConstructpostProcessBeforeInitialization
初始化InitializingBean#afterPropertiesSetinvokeInitMethods
初始化自定义init-methodinvokeInitMethods
初始化AOP 代理生成postProcessAfterInitialization
销毁@PreDestroyDisposableBeanAdapter
销毁DisposableBean#destroyDisposableBeanAdapter
销毁自定义destroy-methodDisposableBeanAdapter

官方参考文档对生命周期回调有完整说明: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


七、什么时候别用 / 别踩的坑

  1. 别在构造器里访问注入的依赖。构造器阶段populateBean还没跑,字段一定是null。需要早期逻辑就用@PostConstruct或InitializingBean。
  1. 别在@PostConstruct里调用同类@Transactional/@Async方法。此时 AOP 代理还没生成,自调用绕过代理,事务和异步都不会生效。
  1. 别指望 prototype Bean 的销毁钩子被调用。Spring 只对单例注册DisposableBean,prototype 的@PreDestroy不会被容器触发,需要自己管理。
  1. 别把初始化和销毁的顺序搞反。初始化是@PostConstruct → afterPropertiesSet → init-method,销毁是@PreDestroy → destroy → destroy-method,前者 JSR-250 在前,后者 JSR-250 也在前——但@PostConstruct在afterPropertiesSet之前,@PreDestroy在destroy之前,方向一致。
  1. 别用构造器注入去解循环依赖。三级缓存只能救 setter / 字段注入,构造器注入的循环依赖 Spring 直接抛BeanCurrentlyInCreationException。
  1. 别忽略@Bean(initMethod = "afterPropertiesSet")的防重逻辑。源码里对同名方法有专门判断,重复配置不会执行两次,但也不要依赖这个巧合。

下一篇我们顺着BeanPostProcessor这条线,聊聊@Async代理生成时机与线程池绑定的那些边界——本系列前面讲线程池时留过伏笔,这次把 Spring 侧的坑补齐。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询