Skip to content

Bean 生命周期与扩展点全家桶

本文是 Spring 系统学习系列的 L2 核心篇。前置:容器启动流程:从 refresh() 到 BeanDefinition。学完可以配合面试题食用:Spring Bean 生命周期详解Spring 循环依赖与三级缓存源码解析

为什么需要搞懂生命周期

写业务代码时感知不到 Bean 的生命周期,出问题时才会:@PostConstruct 里拿到的配置是 null、两个 Bean 相互依赖导致启动报错、给原型 Bean 加了 @PreDestroy 却永远不执行。这些问题的根源都在创建流水线的顺序上。上一篇看到 refresh() 最后的 finishBeanFactoryInitialization 会把所有非懒加载单例真正创建出来,这一篇把"创建"两个字拆开:对象在哪一步诞生、属性在哪一步注入、Spring 留了哪些合法的插队窗口、容器关闭时又发生什么。

生命周期主线:四步走完一生

单例 Bean 的创建集中在 AbstractAutowireCapableBeanFactory#doCreateBean,主线四步:

  1. 实例化(createBeanInstance):反射调用构造器,对象诞生,此时所有字段都是默认值
  2. 属性填充(populateBean):处理 @Autowired、@Value,依赖的其它 Bean 在这一步被递归创建并注入
  3. 初始化(initializeBean):先回调 Aware 系列接口,再走 BeanPostProcessor 前置、init 三连、后置
  4. 就绪:成品放入单例池(singletonObjects),此后 getBean 直接命中缓存

容器 close() 时对所有单例执行销毁链:@PreDestroy -> DisposableBean#destroy -> 自定义 destroy-method。注意初始化三连(@PostConstruct -> afterPropertiesSet -> init-method)和销毁三连是同构的:注解优先,接口居中,配置殿后。

mermaid
flowchart LR
    A[实例化<br/>反射调构造器] --> B[属性填充<br/>@Autowired/@Value]
    B --> C[Aware 回调<br/>BeanName/BeanFactory]
    C --> D[BPP 前置<br/>@PostConstruct 在此窗口]
    D --> E[init 三连<br/>注解/接口/init-method]
    E --> F[BPP 后置<br/>AOP 代理在这生成]
    F --> G[就绪<br/>进入单例池]
    G --> H[容器 close<br/>销毁三连]

五个扩展点各管一段

扩展点回调时机典型用途
Aware 系列属性填充后、初始化前拿到 Bean 名、BeanFactory、ApplicationContext 的引用
@PostConstructBPP 前置阶段(由 CommonAnnotationBeanPostProcessor 触发)依赖注入完成后初始化状态:预热缓存、校验配置
InitializingBean#afterPropertiesSet@PostConstruct 之后、init-method 之前同上,历史接口,会把代码耦合进 Spring
BeanPostProcessor每个 Bean 初始化的前后各一次加工别的 Bean:替换、包装、生成代理
SmartInitializingSingleton所有单例预实例化完成后,只调一次全局一次性初始化,前提是别的 Bean 都就绪

选型原则一句话:初始化自己的状态用 @PostConstruct(三个 init 手段里最不侵入);要等其他 Bean 全部就绪再动作用 SmartInitializingSingleton;要加工别的 Bean 用 BeanPostProcessor。

BeanPostProcessor 需要特别注意:它对容器里所有 Bean 生效,是容器级设施。Spring 自己大量用它干活,@Autowired 靠 AutowiredAnnotationBeanPostProcessor 解析注入,@PostConstruct 靠 CommonAnnotationBeanPostProcessor 触发,AOP 代理靠 AbstractAutoProxyCreator 在后置阶段生成。日常业务很少直接写它,但看 Spring 源码时遇到的八成" Processor"都是这一家子。

三级缓存:循环依赖的解法

A 依赖 B,B 又依赖 A,最朴素的创建顺序会死循环。Spring 的解法是三级缓存,都在 DefaultSingletonBeanRegistry 里:

  • 一级 singletonObjects:成品单例,走完了全部四步
  • 二级 earlySingletonObjects:提前曝光的半成品,实例化了但没走完初始化
  • 三级 singletonFactories:ObjectFactory 工厂,调用后才产出早期引用

流程拿 A、B 互相依赖举例:A 实例化之后、属性填充之前,把自己的 ObjectFactory 放进三级缓存;A 填充属性时触发创建 B;B 填充属性时需要 A,依次查一、二、三级缓存,在三级缓存里找到工厂,调用工厂拿到 A 的早期引用(如果 A 需要 AOP,这里拿到的就是提前生成的代理),放进二级缓存;B 顺利走完创建进入一级缓存;A 拿到 B 的引用,把自己的初始化走完。

为什么是三级不是两级?只用两级的话,A 必须在实例化后立刻决定是否生成代理并放进二级缓存。但 Spring 的设计是代理在初始化之后(BPP 后置)生成,两级缓存会强行改变所有 Bean 的行为,包括绝大多数没有循环依赖的 Bean。三级缓存用工厂把"要不要提前生成代理"这个决策推迟到真的发生循环依赖那一刻才做,没循环依赖就完全按原流程走。代价也有:构造器注入的循环依赖(实例化阶段就需要对方)和原型 Bean 的循环依赖,三级缓存都救不了,只能改造依赖关系或改用 setter 注入。源码级细节可以看面试题 03 那篇。

单例 vs 原型:容器管到哪一步为止

单例的完整一生都由容器负责:创建、注入、初始化、缓存、关闭时销毁,销毁链一定会执行。

原型每次 getBean 都重新走一遍创建流水线,但容器把实例交出去之后就不再持有引用,也就不知道它何时死亡。所以销毁三连(@PreDestroy、DisposableBean、destroy-method)对原型全部无效,需要释放资源就自己管理。原型同样不受 SmartInitializingSingleton 照顾(它只遍历单例),也享受不到三级缓存的循环依赖解法。

动手实操:一个 Bean 打满全部扩展点

写一个类把所有扩展点都实现一遍,配上一个探针 Processor,直接观察调用顺序:

java
@Component("lifecycleDemoBean")
public class LifecycleDemoBean implements BeanNameAware, BeanFactoryAware,
        InitializingBean, DisposableBean, SmartInitializingSingleton {

    public LifecycleDemoBean() {
        System.out.println("1. 构造器:对象诞生,字段全是默认值");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("2. BeanNameAware:我的名字是 " + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("3. BeanFactoryAware:拿到工厂引用");
    }

    @PostConstruct
    public void annotateInit() {
        System.out.println("5. @PostConstruct:注入完成,适合预热缓存");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("6. InitializingBean#afterPropertiesSet");
    }

    // 配合 @Bean(initMethod = "customInit") 使用,@Component 默认不识别 initMethod
    public void customInit() {
        System.out.println("7. 自定义 init-method");
    }

    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("所有单例就绪后:SmartInitializingSingleton,只调一次");
    }

    @PreDestroy
    public void annotateDestroy() {
        System.out.println("9. @PreDestroy:容器关闭,注解销毁先走");
    }

    @Override
    public void destroy() {
        System.out.println("10. DisposableBean#destroy");
    }
}
java
@Component
public class LifecycleProbeProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if ("lifecycleDemoBean".equals(beanName)) {
            System.out.println("4. BPP 前置:@PostConstruct 就发生在这个窗口");
        }
        return bean; // 返回值会替换原 Bean,返回 null 会出问题
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if ("lifecycleDemoBean".equals(beanName)) {
            System.out.println("8. BPP 后置:AOP 代理在这里替换原对象");
        }
        return bean;
    }
}
java
AnnotationConfigApplicationContext ctx =
        new AnnotationConfigApplicationContext(AppConfig.class);
ctx.close(); // 触发销毁三连

输出顺序固定为:1 构造器 -> 2 setBeanName -> 3 setBeanFactory -> 4 BPP 前置 -> 5 @PostConstruct -> 6 afterPropertiesSet -> 7 init-method -> 8 BPP 后置 -> afterSingletonsInstantiated(此时其它单例也都好了)-> close 时 9 @PreDestroy -> 10 destroy -> 11 destroy-method。把这个 Demo 跑一遍,比背十遍面试答案记得牢。

常见误区与小结

  • 在构造器里访问 @Autowired 字段——属性填充还没发生,拿到的是 null,构造器只负责让对象诞生
  • 给原型 Bean 加 @PreDestroy 期望容器来调——容器不跟踪原型,销毁回调不会执行
  • 在 @PostConstruct 里做重活——它阻塞的是整个单例预实例化过程,耗时操作考虑放 ApplicationRunner 或异步执行
  • 把 BeanPostProcessor 当业务工具随手写——它影响容器内所有 Bean,写错波及面是全局的
  • 以为三级缓存能解一切循环依赖——构造器注入和原型 Bean 的循环依赖它无能为力

小结:上一篇看容器怎么把图纸变成实例,这一篇拆开了单个实例内部每一站的顺序,BPP 后置正是 AOP 代理的生成入口。下一篇 AOP 与事务源码 就顺着这个入口往下走。

参考

  • Spring 官方文档:Core Technologies - Lifecycle Callbacks 与 Dependencies
  • AbstractAutowireCapableBeanFactory#doCreateBean、DefaultSingletonBeanRegistry#getSingleton 源码
  • 《Spring 源码深度解析》第 5 章

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1