Skip to content

Spring Bean 生命周期与回调顺序全过程

Spring Bean 生命周期不是“构造器 → @PostConstruct@PreDestroy”三个回调,而是一条从 BeanDefinition、实例化、提前暴露、属性填充、Aware、初始化、代理、业务使用到依赖有序销毁的完整执行链。

理解生命周期可以解释:

  • 构造器中为什么读不到字段注入值;
  • @PostConstruct 为什么可能不执行;
  • AOP 代理为什么通常在初始化后出现;
  • 循环依赖为什么需要实例化后的早期引用;
  • Lazy Bean 为什么首个请求才失败;
  • prototype 为什么不会被容器完整销毁;
  • 关闭时为什么要先停消费者,再关连接池;
  • Bean 创建失败后为什么不能留下半成品。

学习目标

  • 区分定义、实例化、属性填充、初始化和销毁;
  • 写出 doCreateBean()initializeBean() 的准确顺序;
  • 区分构造器注入和字段/Setter注入的发生阶段;
  • 解释 BeanNameAware 与 ApplicationContextAware 为什么不在同一个方法回调;
  • 说清 @PostConstruct、afterPropertiesSet、init-method 的顺序;
  • 解释 BeanPostProcessor 前置、后置和短路语义;
  • 解释 AOP 正常代理与 earlyBeanReference;
  • 理解 SmartInitializingSingleton、Lifecycle、SmartLifecycle 和 ContextRefreshedEvent 的边界;
  • 说清 @PreDestroy、DisposableBean、destroy-method 和依赖销毁顺序;
  • 理解 singleton、prototype、Lazy、FactoryBean 的生命周期差异;
  • 运行 JDK 8 + Spring 5 Demo,逐项断言 15 个回调事件;
  • 根据异常、日志、线程栈和源码断点排查创建、初始化和关闭问题。

一、先分清五个常被混用的词

术语真正含义典型入口
定义描述怎样创建Bean的元数据BeanDefinition注册和合并
实例化产生Java对象壳子构造器、工厂方法、Supplier
属性填充把依赖和配置写入实例populateBean()
初始化执行Aware、BPP前置、初始化方法、BPP后置initializeBean()
销毁容器关闭或scope移除时释放资源DisposableBeanAdapter

“实例化完成”不代表依赖已经注入;“初始化完成”也不一定代表业务拿到的是原始对象,因为 BPP 后置阶段可能返回代理。

二、生命周期总览

mermaid
flowchart TD
    A["BeanDefinition已注册"] --> B["getBean触发创建"]
    B --> C["createBeanInstance实例化"]
    C --> D["MergedBeanDefinitionPostProcessor"]
    D --> E["必要时注册early reference工厂"]
    E --> F["populateBean属性填充"]
    F --> G["BeanName等基础Aware"]
    G --> H["BeanPostProcessor BeforeInitialization"]
    H --> I["ApplicationContextAware与PostConstruct等"]
    I --> J["afterPropertiesSet"]
    J --> K["自定义init-method"]
    K --> L["BeanPostProcessor AfterInitialization"]
    L --> M["原始对象或AOP代理"]
    M --> N["进入singleton缓存并提供业务使用"]
    N --> O["容器关闭或scope移除"]
    O --> P["PreDestroy、destroy、自定义destroy-method"]

这是一条常见 singleton 主线。prototype、FactoryBean 产品、实例化前短路代理和循环依赖会进入不同分支,后文分别说明。

三、BeanDefinition 阶段还没有对象

BeanDefinition 可来自:

  • @Component 扫描;
  • @Bean 方法;
  • XML;
  • @Import
  • ImportSelector;
  • ImportBeanDefinitionRegistrar;
  • BeanDefinitionRegistryPostProcessor;
  • 编程式 registerBeanDefinition。

它可能保存 class、scope、lazy、工厂方法、构造参数、属性值、dependsOn、init/destroy 方法和角色。此时还没有业务实例,BFPP 应操作定义,不应为了读取配置随意 getBean() 创建普通业务对象。

3.1 合并 BeanDefinition

Spring 内部创建时常使用 RootBeanDefinition,它可能由父子定义合并而来。合并结果会缓存;定义重置后相关缓存和注入元数据必须失效。

3.2 为什么定义阶段先于 BPP 注册和 Bean 创建

必须先确定“有哪些 Bean、怎样创建”,再注册“怎样处理实例”的 BPP,最后创建普通 Bean。否则先创建的对象可能错过注入、@PostConstruct 和 AOP。

四、getBean 什么时候触发创建

常见触发源:

  • ApplicationContext 启动预实例化非懒 singleton;
  • 业务显式 getBean
  • eager Bean 解析依赖;
  • @DependsOn
  • FactoryBean 产品获取;
  • Runner、Listener、健康检查或预热;
  • Lazy 代理首次方法调用;
  • 自定义基础设施过早获取 Bean。

singleton 默认常在 preInstantiateSingletons() 创建;prototype 每次请求创建;Lazy singleton 跳过主动预实例化,但被普通 eager Bean 依赖时仍可能提前创建。

五、实例化:创建对象壳子

createBeanInstance() 可能使用:

  • InstanceSupplier;
  • 静态或实例工厂方法;
  • 候选构造器;
  • 默认无参构造器;
  • CGLIB method injection。
mermaid
flowchart TD
    A["createBeanInstance"] --> B{"有InstanceSupplier?"}
    B -- "是" --> C["Supplier.get"]
    B -- "否" --> D{"有factory-method?"}
    D -- "是" --> E["解析并调用工厂方法"]
    D -- "否" --> F{"有候选构造器或构造参数?"}
    F -- "是" --> G["解析构造器参数并实例化"]
    F -- "否" --> H["调用默认构造器"]
    C --> I["得到BeanWrapper中的原始实例"]
    E --> I
    G --> I
    H --> I

5.1 构造器注入为什么发生在实例化前

Java 调用构造器时必须先准备全部参数,所以 Spring 要先解析构造器依赖,再产生当前对象。A 构造器需要 B、B 构造器需要 A 时,双方都没有实例可以提前暴露,因此失败。

5.2 构造器选择不是参数最多

要结合:

  • @Autowired(required=...)
  • 单构造器规则;
  • 显式构造参数;
  • 参数类型与名称;
  • 候选 Bean;
  • Optional;
  • 类型转换;
  • 已缓存解析结果。

5.3 构造器中可以做什么

构造器只应建立对象不变量,不应:

  • 读取字段注入值;
  • 启动线程;
  • 订阅 MQ;
  • 执行无超时远程调用;
  • 依赖 AOP 已经生效;
  • 发布“服务已就绪”事件;
  • 访问尚未完整初始化的协作者。

构造失败时对象还未进入正常生命周期,资源清理尤其困难。

六、实例化前短路分支

InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 可以在常规实例化前返回替代对象。返回非 null 时,Spring 对替代对象执行相应的初始化后处理,然后跳过普通 doCreateBean 主线。

这与大多数 Spring AOP 在初始化后生成代理不是同一条路径。自定义框架若使用短路代理,必须自行理解依赖注入、生命周期和目标对象由谁创建。

七、MergedBeanDefinitionPostProcessor

实例产生后,Spring 会让 MergedBeanDefinitionPostProcessor 基于最终 class 和合并定义准备元数据:

  • AutowiredAnnotationBeanPostProcessor 缓存字段和方法注入点;
  • CommonAnnotationBeanPostProcessor 缓存 Resource 和生命周期元数据;
  • 其他处理器准备代理、销毁或持久化相关元数据。

为什么要缓存?prototype 每次创建都要注入,如果每次重新扫描所有父类字段、方法和注解,反射成本和行为一致性都会变差。

八、早期暴露位于哪里

对于允许循环引用的 singleton,实例化之后、属性填充之前,Spring 可注册:

java
addSingletonFactory(beanName,
    () -> getEarlyBeanReference(beanName, mbd, bean));

此时只是提供按需生成早期引用的工厂,不代表所有 Bean 都立即进入二级缓存。只有另一 Bean 真正需要当前创建中的对象,才调用工厂,并给 AutoProxyCreator 生成早期代理的机会。

mermaid
flowchart TD
    A["A完成实例化"] --> B["注册A的early reference工厂"]
    B --> C["A开始populateBean并需要B"]
    C --> D["创建B"]
    D --> E["B注入A"]
    E --> F["一级缓存没有完整A"]
    F --> G["调用三级缓存中的工厂"]
    G --> H["可能生成A早期代理"]
    H --> I["B持有A早期引用"]
    I --> J["B完成,A继续初始化"]
    J --> K["校验A最终对象与早期引用一致"]

完整三级缓存见循环依赖与三级缓存全过程

九、populateBean() 属性填充

常见顺序:

  1. postProcessAfterInstantiation 决定是否继续填充;
  2. 处理传统 autowire byName/byType;
  3. 调用 postProcessProperties
  4. AutowiredAnnotationBeanPostProcessor 处理 Autowired/Value;
  5. CommonAnnotationBeanPostProcessor 处理 Resource;
  6. 执行依赖检查;
  7. BeanWrapper 应用剩余 PropertyValues。

9.1 字段注入为什么构造器中是 null

字段注入发生在对象已经由构造器产生之后:

text
调用构造器

构造器返回对象

populateBean扫描字段

反射Field.set

所以构造器内读取字段注入值必然太早。

9.2 Setter 注入什么时候调用

Setter 或任意 Autowired 方法也属于属性填充。方法执行时对象已经存在,但初始化回调尚未开始。

9.3 依赖失败怎样表现

  • 无候选:NoSuchBeanDefinitionException;
  • 多候选:NoUniqueBeanDefinitionException;
  • 依赖链包装:UnsatisfiedDependencyException;
  • 名称类型错误:BeanNotOfRequiredTypeException;
  • 循环创建:BeanCurrentlyInCreationException;
  • Setter 自身抛异常:BeanCreationException 的深层 cause。

十、Aware 回调不是同一个处理位置

10.1 invokeAwareMethods 直接处理

initializeBean 内部直接处理常见基础 Aware:

  1. BeanNameAware;
  2. BeanClassLoaderAware;
  3. BeanFactoryAware。

10.2 ApplicationContextAwareProcessor 处理

其他上下文 Aware 通常由 ApplicationContextAwareProcessor 这个 BPP 在初始化前阶段处理,例如:

  • EnvironmentAware;
  • EmbeddedValueResolverAware;
  • ResourceLoaderAware;
  • ApplicationEventPublisherAware;
  • MessageSourceAware;
  • ApplicationContextAware。

Web 容器还有 ServletContextAware 等专用处理器。不能简单背成“所有 Aware 都在一个循环中调用”。

10.3 为什么业务代码不要滥用 ApplicationContextAware

到处保存静态 ApplicationContext 并 getBean() 会:

  • 隐藏真实依赖;
  • 降低可测试性;
  • 可能产生上下文泄漏;
  • 破坏模块边界;
  • 让 Bean 创建顺序难以推理。

框架集成或确实动态查找时才使用,并优先考虑 ObjectProvider、事件或明确接口。

十一、初始化回调的准确顺序

常见顺序:

text
BeanNameAware / BeanClassLoaderAware / BeanFactoryAware

BeanPostProcessor BeforeInitialization

ApplicationContextAwareProcessor等Aware处理

CommonAnnotationBeanPostProcessor触发@PostConstruct

InitializingBean.afterPropertiesSet

自定义init-method

BeanPostProcessor AfterInitialization

原始Bean或代理

严格来说,ApplicationContextAwareProcessor 和 CommonAnnotationBeanPostProcessor 都在 BPP 前置链中,二者相对位置取决于处理器注册与排序。稳定结论是:基础 BeanFactory Aware 先于整个 BPP 前置链,初始化方法先于 BPP 后置链。

十二、@PostConstruct

Spring 5/JDK 8 常使用 javax.annotation.PostConstruct;Spring 6/Boot 3 使用 Jakarta 体系。它通常由 CommonAnnotationBeanPostProcessor 的生命周期父类处理,而不是 JVM 自动调用。

适合:

  • 校验注入后的本地配置;
  • 建立轻量不可变索引;
  • 检查策略重复;
  • 初始化无需远程等待的本地状态。

不适合:

  • 无超时数据库或HTTP调用;
  • 无限重试;
  • 启动大量不可控线程;
  • 耗时全量数据加载;
  • 发布对象已经对外可用的错误信号。

如果 @PostConstruct 抛异常,Bean 创建失败,不会作为正常可用 Bean 放入一级缓存;refresh 期间还可能导致整个上下文启动失败并销毁已创建 singleton。

十三、InitializingBean 与 init-method

afterPropertiesSet()

实现 Spring 接口:

java
public class Client implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        // 依赖已填充,执行初始化
    }
}

优点是明确、无需反射猜方法;缺点是业务类耦合 Spring API。

自定义 init-method

java
@Bean(initMethod = "initialize")
Client client() { return new Client(); }

适合第三方类或希望保持 POJO。方法名配置错误会在创建阶段失败。多个初始化机制并存会按顺序全部执行;不要在三个回调中重复初始化同一资源。

十四、BeanPostProcessor 前置和后置

前置处理器

位于初始化方法之前,可用于:

  • Aware 回调;
  • PostConstruct;
  • 验证和包装初始化输入;
  • 框架元数据处理。

后置处理器

位于初始化方法之后,可返回:

  • 原始对象;
  • 包装对象;
  • JDK代理;
  • CGLIB代理;
  • 其他符合 Bean 契约的替代对象。

返回 null 的真实语义

某个 BPP 返回 null 时,Spring 通常停止当前 before 或 after 处理器链,并返回前一个非 null 结果;不是把容器里的 Bean 设置为 null。自定义 BPP 不应随意返回 null,通常返回传入 Bean。

BPP 为什么必须线程安全

BPP 通常是 singleton,并可能处理大量 Bean。不要把“当前正在处理的 Bean”保存到普通实例字段;元数据缓存必须使用线程安全结构,并以 class/beanName 等稳定键管理。

十五、AOP 代理在生命周期哪里产生

常见正常路径:

text
原始Bean完成属性填充和初始化

AutoProxyCreator.postProcessAfterInitialization

查找匹配Advisor

ProxyFactory创建JDK或CGLIB代理

代理作为最终exposedObject

因此:

  • 构造器中不能假设事务代理已存在;
  • @PostConstruct 中调用本类事务方法通常不会形成外部代理入口;
  • 业务应通过容器提供的最终引用调用;
  • this.method() 仍绕过外部代理。

循环依赖时 AutoProxyCreator 还可能在 getEarlyBeanReference 生成早期代理。最终对象必须与其他 Bean 已注入的早期引用保持一致。

详细见Spring AOP代理与拦截器链全过程

十六、singleton 何时真正进入一级缓存

doCreateBean 返回 exposedObject 后,外层 singleton 创建协调才执行 addSingleton

  • 放入 singletonObjects
  • singletonFactories 移除;
  • earlySingletonObjects 移除;
  • 保留注册顺序用于管理和销毁。

所以二级缓存中的 early reference 不等于 Bean 已经完成初始化。业务线程不应主动访问创建中的 Bean。

十七、SmartInitializingSingleton

preInstantiateSingletons() 完成普通非懒 singleton 创建后,会调用已创建 Bean 的:

java
afterSingletonsInstantiated()

适合:

  • 在所有策略 Bean 创建后检查业务码重复;
  • 构建完整处理器索引;
  • 校验必要协作者集合;
  • 触发不会长时间阻塞的最终准备。

不保证:

  • Lazy Bean 已创建;
  • prototype 已创建;
  • 外部系统永久健康;
  • SmartLifecycle 已经启动;
  • ContextRefreshedEvent 已经发布。

十八、ContextRefreshedEvent 与 Runner

finishRefresh() 发布 ContextRefreshedEvent,表示当前上下文 refresh 主流程成功。Spring Boot 随后还有 Runner、WebServer 可用事件和健康检查等更高层阶段。

监听器和 Runner 中执行长任务会延长启动或阻塞发布线程。大批量预热应:

  • 可观测;
  • 有超时;
  • 有失败策略;
  • 区分是否阻止 readiness;
  • 避免与真实流量争抢线程和连接。

十九、Lifecycle 与 SmartLifecycle

Lifecycle 表达可启动/停止组件;SmartLifecycle 进一步支持:

  • isAutoStartup()
  • phase 顺序;
  • 异步停止回调;
  • 容器 refresh 和 close 协调。

启动通常 phase 小的先启动,停止通常反向;具体还要结合依赖关系。MQ消费者、采集调度器和长连接客户端常适合 SmartLifecycle,因为它们需要在基础设施就绪后启动、在依赖关闭前停止。

stop(Runnable callback) 必须在真正停止后调用 callback,否则容器关闭会等待到超时。

二十、业务使用期与线程安全

singleton 只表示每个 BeanFactory、每个 Bean 名通常一份实例,不代表线程安全。Service 被多个请求线程并发调用,不能保存:

  • 当前用户;
  • 当前订单;
  • 当前请求参数;
  • 可变 SimpleDateFormat;
  • 未保护的 HashMap 缓存;
  • 当前事务连接。

请求状态放方法局部变量、request scope 或受控上下文。共享状态使用不可变对象、并发结构、锁或外部存储,并明确一致性要求。

二十一、销毁回调准确顺序

singleton 注册销毁时通常由 DisposableBeanAdapter 统一协调:

text
DestructionAwareBeanPostProcessor

@PreDestroy等销毁注解

DisposableBean.destroy

自定义destroy-method

框架会避免同一个方法因多个机制配置而明显重复调用,但业务不应把同一资源关闭逻辑散落在多个回调中。关闭方法应幂等:即使部分初始化失败或重复触发,也不会二次关闭导致新异常。

二十二、依赖销毁顺序

若 A 依赖 B:

text
A使用B

关闭时应先销毁 A,再销毁 B,避免 A 的停止逻辑访问已经关闭的 B。Spring 在依赖解析和 dependsOn 中记录依赖关系,销毁时先处理 dependent beans。

mermaid
flowchart TD
    A["收到容器关闭"] --> B["停止接收新流量或任务"]
    B --> C["先销毁依赖者A"]
    C --> D["A完成在途任务并释放资源"]
    D --> E["再销毁被依赖的B"]
    E --> F["B关闭连接、线程池或文件"]
    F --> G["清理singleton缓存"]

商业系统常见顺序:

text
停止MQ消费者

等待在途消息完成

停止业务线程池

关闭MQ客户端

关闭数据库和Redis连接池

二十三、prototype 的生命周期边界

Spring 会为 prototype 执行实例化、属性填充和初始化,但通常不长期保存实例,也不在容器关闭时统一调用其销毁回调。调用方取得 prototype 后负责释放资源。

因此 prototype 不适合随意持有:

  • 线程池;
  • Socket;
  • 文件流;
  • 原生内存;
  • 数据库连接。

singleton 注入 prototype 时,若普通解析只发生一次,singleton 会长期持有那一个 prototype。每次需要新实例应使用 ObjectProvider、lookup method 或合适的作用域代理。

二十四、Lazy Bean 的生命周期

定义级 Lazy 让预实例化阶段跳过 Bean;首次 getBean 时仍执行完整创建链。注入点 Lazy 则先注入代理,代理首次调用时解析目标。

风险:

  • 构造错误推迟到请求;
  • PostConstruct 失败推迟;
  • 首次请求承担初始化成本;
  • readiness 通过但能力未验证;
  • 多实例首次流量表现不一致。

详细见@Lazy懒加载与代理全过程

二十五、FactoryBean 的双重生命周期

FactoryBean 本身是容器 Bean,经历正常生命周期;getObject() 产生的是产品。普通名称通常取得产品,&name 取得工厂。

产品可能由 Spring 执行部分后处理并缓存,但不能简单假设它与普通 Bean 经过完全相同的定义、属性填充和初始化路径。销毁责任取决于 FactoryBean、产品注册方式和具体实现。排查时必须区分:

  • 工厂实例;
  • 产品实例;
  • 工厂 singleton;
  • 产品是否 singleton;
  • 产品由谁关闭。

二十六、创建和初始化失败的回滚

可能失败的阶段:

  • 构造器;
  • 工厂方法;
  • 依赖解析;
  • 字段/Setter;
  • Aware;
  • BPP 前置;
  • PostConstruct;
  • afterPropertiesSet;
  • init-method;
  • BPP 后置代理。

失败后 Spring 要清理:

  • singleton 创建中标记;
  • 一级、二级和三级缓存相关条目;
  • 依赖关系;
  • DisposableBean 注册;
  • refresh 已创建 singleton;
  • 反射和合并定义的部分状态。

业务在构造或初始化中手工创建的线程、文件和连接,Spring 未必知道,必须在 catch/finally 或统一资源对象中释放。

二十七、完整 JDK 8 + Spring 5 Demo

依赖:

xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.39</version>
</dependency>
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

完整代码:

java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.PriorityOrdered;

public class BeanLifecycleDemo {
    private static final List<String> EVENTS = new ArrayList<String>();

    static class LifecycleBean implements BeanNameAware,
            ApplicationContextAware, InitializingBean,
            SmartInitializingSingleton, DisposableBean {

        LifecycleBean() {
            EVENTS.add("1.constructor");
        }

        public void setBeanName(String name) {
            EVENTS.add("2.beanNameAware:" + name);
        }

        public void setApplicationContext(ApplicationContext context) {
            EVENTS.add("3.applicationContextAware");
        }

        @PostConstruct
        private void postConstruct() {
            EVENTS.add("5.postConstruct");
        }

        public void afterPropertiesSet() {
            EVENTS.add("6.afterPropertiesSet");
        }

        public void customInit() {
            EVENTS.add("7.customInit");
        }

        public void afterSingletonsInstantiated() {
            EVENTS.add("9.afterSingletonsInstantiated");
        }

        void business() {
            EVENTS.add("11.business");
        }

        @PreDestroy
        private void preDestroy() {
            EVENTS.add("12.preDestroy");
        }

        public void destroy() {
            EVENTS.add("13.disposableDestroy");
        }

        public void customDestroy() {
            EVENTS.add("14.customDestroy");
        }
    }

    static class TracingPostProcessor
            implements BeanPostProcessor, PriorityOrdered {
        public int getOrder() {
            return 0;
        }

        public Object postProcessBeforeInitialization(
                Object bean, String beanName) throws BeansException {
            if (bean instanceof LifecycleBean) {
                EVENTS.add("4.bppBeforeInitialization");
            }
            return bean;
        }

        public Object postProcessAfterInitialization(
                Object bean, String beanName) throws BeansException {
            if (bean instanceof LifecycleBean) {
                EVENTS.add("8.bppAfterInitialization");
            }
            return bean;
        }
    }

    @Configuration
    static class Config {
        @Bean
        static TracingPostProcessor tracingPostProcessor() {
            return new TracingPostProcessor();
        }

        @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
        LifecycleBean lifecycleBean() {
            return new LifecycleBean();
        }
    }

    public static void main(String[] args) {
        EVENTS.add("0.beforeContext");
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);
        EVENTS.add("10.refreshReturned");
        context.getBean(LifecycleBean.class).business();
        context.close();

        List<String> expected = Arrays.asList(
                "0.beforeContext",
                "1.constructor",
                "2.beanNameAware:lifecycleBean",
                "3.applicationContextAware",
                "4.bppBeforeInitialization",
                "5.postConstruct",
                "6.afterPropertiesSet",
                "7.customInit",
                "8.bppAfterInitialization",
                "9.afterSingletonsInstantiated",
                "10.refreshReturned",
                "11.business",
                "12.preDestroy",
                "13.disposableDestroy",
                "14.customDestroy");
        if (!expected.equals(EVENTS)) {
            throw new AssertionError("unexpected events: " + EVENTS);
        }
        System.out.println(EVENTS);
    }
}

为什么自定义 BPP 实现 PriorityOrdered 且返回 0:为了让示例中的 bppBeforeInitialization 稳定出现在处理 @PostConstruct 的 CommonAnnotationBeanPostProcessor 之前。生产 BPP 的顺序必须基于真实依赖设计,不能为了“更早”随意给最高优先级。

期望输出包含:

text
0.beforeContext
1.constructor
2.beanNameAware:lifecycleBean
3.applicationContextAware
4.bppBeforeInitialization
5.postConstruct
6.afterPropertiesSet
7.customInit
8.bppAfterInitialization
9.afterSingletonsInstantiated
10.refreshReturned
11.business
12.preDestroy
13.disposableDestroy
14.customDestroy

二十八、商业场景:采集客户端生命周期

医疗采集平台中的采集客户端可能包含:

  • HTTP连接池;
  • TLS证书;
  • 数据源连接;
  • 采集线程池;
  • 调度器;
  • 断点状态;
  • 指标和心跳。

合理设计:

  1. 构造器只接收配置和依赖,建立不变量;
  2. PostConstruct 校验本地配置,不执行无限远程重试;
  3. SmartInitializingSingleton 检查采集器编码是否重复;
  4. SmartLifecycle 在基础设施完成后启动调度;
  5. readiness 只在关键依赖验证后通过;
  6. stop 阶段先停止新采集;
  7. 等待在途采集完成;
  8. 最后关闭线程池和连接;
  9. 所有停止方法幂等并有超时;
  10. 日志不输出口令、token、证书私钥和医疗数据。

二十九、常见错误与后果

错误后果正确方向
构造器读取字段注入得到null构造器注入或初始化后使用
构造器启动线程创建失败时线程泄漏SmartLifecycle管理
PostConstruct无超时远程调用启动卡死超时、健康检查、异步预热
三种init重复初始化重复订阅或重复建连接只保留一个真实入口
BPP返回null后续处理器链提前结束通常返回bean
BPP成员保存当前Bean并发污染和内存泄漏安全元数据缓存
singleton保存请求状态串用户和数据竞争局部变量或request scope
prototype持有资源不关闭文件、线程、连接泄漏调用方管理销毁
PreDestroy无限等待发布关闭超时有界等待和强制终止策略
Lazy用于核心依赖首个请求才暴露错误核心能力启动验证

三十、创建失败排查 Runbook

  1. 保存最外层异常和完整 cause 链;
  2. 找到最深 BeanName 和注入点;
  3. 判断失败阶段:构造、注入、Aware、PostConstruct、init还是BPP;
  4. 检查运行时 Profile、Condition和BeanDefinition来源;
  5. 检查是否手工new或过早getBean;
  6. 检查当前 class 是否代理、FactoryBean产品或普通Bean;
  7. 对构造和init采集线程栈,判断I/O、锁或CPU;
  8. 检查失败路径是否泄漏线程、文件和连接;
  9. 使用最小上下文复现;
  10. 修复后增加启动测试和生命周期顺序断言。

三十一、@PostConstruct 不执行排查

  • 对象不是 Spring Bean;
  • 对象手工 new;
  • 方法签名无效;
  • CommonAnnotationBeanPostProcessor 未注册;
  • javax/jakarta 包与 Spring 版本不匹配;
  • Bean 在到达初始化前已失败;
  • 实例化前 BPP 返回替代对象并绕开普通链;
  • 测试没有加载完整上下文;
  • 日志看的是另一个同名或同类型实例。

不要通过手工调用 PostConstruct 方法修复,这会掩盖对象不受容器管理的根因。

三十二、Bean 没有被 AOP 代理排查

  1. 确认最终注入对象运行时 class;
  2. 使用 AopUtils 判断代理;
  3. 检查 AutoProxyCreator 是否注册;
  4. 检查 Bean 是否在完整 BPP 链注册前过早创建;
  5. 检查 Pointcut 与 Advisor;
  6. 检查 final/private/static;
  7. 检查 JDK代理接口类型;
  8. 检查 self-invocation;
  9. 检查 earlyBeanReference 与最终包装;
  10. 检查拿到的是 FactoryBean 工厂还是产品。

三十三、关闭卡住排查 Runbook

  1. 在终止宽限期内连续采集线程栈;
  2. 找到 ContextClosedEvent、LifecycleProcessor或destroy回调所在栈;
  3. 检查线程池 awaitTermination 是否无界;
  4. 检查远程请求、DNS、数据库关闭是否无超时;
  5. 检查锁顺序和回调互相等待;
  6. 检查 SmartLifecycle.stop 是否调用 callback;
  7. 检查消费者是否仍接收新任务;
  8. 检查非守护线程;
  9. 临时止损时先摘流量和停止入口;
  10. 长期修复为分阶段、有界、幂等关闭。

三十四、源码断点

固定一个 BeanName,按顺序断点:

text
AbstractBeanFactory.doGetBean
AbstractAutowireCapableBeanFactory.createBean
AbstractAutowireCapableBeanFactory.doCreateBean
AbstractAutowireCapableBeanFactory.createBeanInstance
AbstractAutowireCapableBeanFactory.populateBean
AbstractAutowireCapableBeanFactory.initializeBean
AbstractAutowireCapableBeanFactory.invokeAwareMethods
AbstractAutowireCapableBeanFactory.invokeInitMethods
DefaultSingletonBeanRegistry.addSingleton
DisposableBeanAdapter.destroy

同时观察:

  • RootBeanDefinition;
  • BeanWrapper;
  • 原始 bean;
  • exposedObject;
  • singleton 三缓存;
  • BeanPostProcessor 列表;
  • dependentBeanMap;
  • disposableBeans。

三十五、版本边界

技术线生命周期注解和基线
JDK 7/8 + Spring 4/5常见 javax.annotation.PostConstruct/PreDestroy
JDK 9/10Java EE模块进入迁移期
JDK 11 + Spring 5annotation API通常需要显式依赖
Spring 5.3 + JDK 8本页主要Demo验证线
Spring 6 / Boot 3Java 17基线,使用jakarta.annotation

迁移不能只改 PostConstruct import,还要整体检查 Resource、Servlet、Validation、JPA、容器和第三方库。

三十六、面试标准回答

Spring Bean 完整生命周期

先注册并合并BeanDefinition,getBean触发实例化;MergedBeanDefinitionPostProcessor准备注入元数据,允许循环引用时注册早期引用工厂;populateBean完成依赖注入,initializeBean依次执行基础Aware、BPP前置、PostConstruct、afterPropertiesSet、自定义init和BPP后置,后置处理器可能返回AOP代理。最终对象进入scope缓存,容器关闭时按依赖顺序执行PreDestroy、DisposableBean和destroy-method。

实例化和初始化有什么区别

实例化只产生Java对象,依赖可能尚未填充;初始化发生在属性填充后,包括Aware、BPP、PostConstruct和初始化方法,最终还可能得到代理。构造器中读取字段注入值为null,就是因为字段注入晚于实例化。

三种初始化方法顺序是什么

常见顺序是PostConstruct、InitializingBean.afterPropertiesSet、自定义init-method,外面还有BPP Before和After。PostConstruct本身通常由CommonAnnotationBeanPostProcessor在前置处理链中触发。

prototype 为什么不自动销毁

容器创建、注入并初始化prototype后把实例交给调用方,不长期持有所有实例,因此关闭上下文时无法统一回收。prototype拥有文件、线程或连接时,调用方必须明确关闭。

AOP代理在生命周期哪里产生

普通路径中AutoProxyCreator作为BeanPostProcessor在初始化后查找Advisor并返回代理;循环依赖时还可能通过getEarlyBeanReference提前生成一致代理。业务必须通过容器中的最终引用调用,this自调用仍绕过外部代理。

三十七、关联知识与验收

掌握验收:

  • 能画出BeanDefinition到销毁的完整流程;
  • 能区分实例化、属性填充和初始化;
  • 能说明基础Aware与ApplicationContextAware的处理位置;
  • 能写出PostConstruct、afterPropertiesSet、init-method顺序;
  • 能解释BPP前后置和返回null语义;
  • 能说明AOP正常代理和earlyBeanReference;
  • 能解释SmartInitializingSingleton、SmartLifecycle和ContextRefreshedEvent差异;
  • 能说明singleton、prototype、Lazy和FactoryBean生命周期边界;
  • 能运行15事件Demo并验证销毁顺序;
  • 能完成创建失败、PostConstruct缺失、代理缺失和关闭卡住排查。