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

相关推荐
小 黄 鸡1 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
凤山老林2 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne3 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF3 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
silvamux_3 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi
pride.li4 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
省钱兄--zs4 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构
顽疲4 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog4 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友