Spring Bean 生命周期:从实例化到销毁的完整钩子

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.3 invokeInitMethods 的顺序细节

复制代码
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
提前暴露 三级缓存 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


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

  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 侧的坑补齐。

相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
AI你一生一世3 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影4 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯4 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
考虑考虑5 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
无奈何杨5 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
Patrick_Wilson6 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术7 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端
橙序员小站7 小时前
我一句话让 AI 把公众号工作台搭完了:部署实测记录
后端
Patrick_Wilson7 小时前
从文本输出理解 Go 方法与接口
后端·go·编程语言