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 后置阶段可能返回代理。
二、生命周期总览
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。
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 --> I5.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 可注册:
addSingletonFactory(beanName,
() -> getEarlyBeanReference(beanName, mbd, bean));此时只是提供按需生成早期引用的工厂,不代表所有 Bean 都立即进入二级缓存。只有另一 Bean 真正需要当前创建中的对象,才调用工厂,并给 AutoProxyCreator 生成早期代理的机会。
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() 属性填充
常见顺序:
postProcessAfterInstantiation决定是否继续填充;- 处理传统 autowire byName/byType;
- 调用
postProcessProperties; - AutowiredAnnotationBeanPostProcessor 处理 Autowired/Value;
- CommonAnnotationBeanPostProcessor 处理 Resource;
- 执行依赖检查;
- BeanWrapper 应用剩余 PropertyValues。
9.1 字段注入为什么构造器中是 null
字段注入发生在对象已经由构造器产生之后:
调用构造器
↓
构造器返回对象
↓
populateBean扫描字段
↓
反射Field.set所以构造器内读取字段注入值必然太早。
9.2 Setter 注入什么时候调用
Setter 或任意 Autowired 方法也属于属性填充。方法执行时对象已经存在,但初始化回调尚未开始。
9.3 依赖失败怎样表现
- 无候选:NoSuchBeanDefinitionException;
- 多候选:NoUniqueBeanDefinitionException;
- 依赖链包装:UnsatisfiedDependencyException;
- 名称类型错误:BeanNotOfRequiredTypeException;
- 循环创建:BeanCurrentlyInCreationException;
- Setter 自身抛异常:BeanCreationException 的深层 cause。
十、Aware 回调不是同一个处理位置
10.1 invokeAwareMethods 直接处理
initializeBean 内部直接处理常见基础 Aware:
- BeanNameAware;
- BeanClassLoaderAware;
- BeanFactoryAware。
10.2 ApplicationContextAwareProcessor 处理
其他上下文 Aware 通常由 ApplicationContextAwareProcessor 这个 BPP 在初始化前阶段处理,例如:
- EnvironmentAware;
- EmbeddedValueResolverAware;
- ResourceLoaderAware;
- ApplicationEventPublisherAware;
- MessageSourceAware;
- ApplicationContextAware。
Web 容器还有 ServletContextAware 等专用处理器。不能简单背成“所有 Aware 都在一个循环中调用”。
10.3 为什么业务代码不要滥用 ApplicationContextAware
到处保存静态 ApplicationContext 并 getBean() 会:
- 隐藏真实依赖;
- 降低可测试性;
- 可能产生上下文泄漏;
- 破坏模块边界;
- 让 Bean 创建顺序难以推理。
框架集成或确实动态查找时才使用,并优先考虑 ObjectProvider、事件或明确接口。
十一、初始化回调的准确顺序
常见顺序:
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 接口:
public class Client implements InitializingBean {
@Override
public void afterPropertiesSet() {
// 依赖已填充,执行初始化
}
}优点是明确、无需反射猜方法;缺点是业务类耦合 Spring API。
自定义 init-method
@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 代理在生命周期哪里产生
常见正常路径:
原始Bean完成属性填充和初始化
↓
AutoProxyCreator.postProcessAfterInitialization
↓
查找匹配Advisor
↓
ProxyFactory创建JDK或CGLIB代理
↓
代理作为最终exposedObject因此:
- 构造器中不能假设事务代理已存在;
@PostConstruct中调用本类事务方法通常不会形成外部代理入口;- 业务应通过容器提供的最终引用调用;
this.method()仍绕过外部代理。
循环依赖时 AutoProxyCreator 还可能在 getEarlyBeanReference 生成早期代理。最终对象必须与其他 Bean 已注入的早期引用保持一致。
十六、singleton 何时真正进入一级缓存
doCreateBean 返回 exposedObject 后,外层 singleton 创建协调才执行 addSingleton:
- 放入
singletonObjects; - 从
singletonFactories移除; - 从
earlySingletonObjects移除; - 保留注册顺序用于管理和销毁。
所以二级缓存中的 early reference 不等于 Bean 已经完成初始化。业务线程不应主动访问创建中的 Bean。
十七、SmartInitializingSingleton
preInstantiateSingletons() 完成普通非懒 singleton 创建后,会调用已创建 Bean 的:
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 统一协调:
DestructionAwareBeanPostProcessor
↓
@PreDestroy等销毁注解
↓
DisposableBean.destroy
↓
自定义destroy-method框架会避免同一个方法因多个机制配置而明显重复调用,但业务不应把同一资源关闭逻辑散落在多个回调中。关闭方法应幂等:即使部分初始化失败或重复触发,也不会二次关闭导致新异常。
二十二、依赖销毁顺序
若 A 依赖 B:
A使用B关闭时应先销毁 A,再销毁 B,避免 A 的停止逻辑访问已经关闭的 B。Spring 在依赖解析和 dependsOn 中记录依赖关系,销毁时先处理 dependent beans。
flowchart TD
A["收到容器关闭"] --> B["停止接收新流量或任务"]
B --> C["先销毁依赖者A"]
C --> D["A完成在途任务并释放资源"]
D --> E["再销毁被依赖的B"]
E --> F["B关闭连接、线程池或文件"]
F --> G["清理singleton缓存"]商业系统常见顺序:
停止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
依赖:
<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>完整代码:
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 的顺序必须基于真实依赖设计,不能为了“更早”随意给最高优先级。
期望输出包含:
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证书;
- 数据源连接;
- 采集线程池;
- 调度器;
- 断点状态;
- 指标和心跳。
合理设计:
- 构造器只接收配置和依赖,建立不变量;
- PostConstruct 校验本地配置,不执行无限远程重试;
- SmartInitializingSingleton 检查采集器编码是否重复;
- SmartLifecycle 在基础设施完成后启动调度;
- readiness 只在关键依赖验证后通过;
- stop 阶段先停止新采集;
- 等待在途采集完成;
- 最后关闭线程池和连接;
- 所有停止方法幂等并有超时;
- 日志不输出口令、token、证书私钥和医疗数据。
二十九、常见错误与后果
| 错误 | 后果 | 正确方向 |
|---|---|---|
| 构造器读取字段注入 | 得到null | 构造器注入或初始化后使用 |
| 构造器启动线程 | 创建失败时线程泄漏 | SmartLifecycle管理 |
| PostConstruct无超时远程调用 | 启动卡死 | 超时、健康检查、异步预热 |
| 三种init重复初始化 | 重复订阅或重复建连接 | 只保留一个真实入口 |
| BPP返回null | 后续处理器链提前结束 | 通常返回bean |
| BPP成员保存当前Bean | 并发污染和内存泄漏 | 安全元数据缓存 |
| singleton保存请求状态 | 串用户和数据竞争 | 局部变量或request scope |
| prototype持有资源不关闭 | 文件、线程、连接泄漏 | 调用方管理销毁 |
| PreDestroy无限等待 | 发布关闭超时 | 有界等待和强制终止策略 |
| Lazy用于核心依赖 | 首个请求才暴露错误 | 核心能力启动验证 |
三十、创建失败排查 Runbook
- 保存最外层异常和完整 cause 链;
- 找到最深 BeanName 和注入点;
- 判断失败阶段:构造、注入、Aware、PostConstruct、init还是BPP;
- 检查运行时 Profile、Condition和BeanDefinition来源;
- 检查是否手工new或过早getBean;
- 检查当前 class 是否代理、FactoryBean产品或普通Bean;
- 对构造和init采集线程栈,判断I/O、锁或CPU;
- 检查失败路径是否泄漏线程、文件和连接;
- 使用最小上下文复现;
- 修复后增加启动测试和生命周期顺序断言。
三十一、@PostConstruct 不执行排查
- 对象不是 Spring Bean;
- 对象手工 new;
- 方法签名无效;
- CommonAnnotationBeanPostProcessor 未注册;
- javax/jakarta 包与 Spring 版本不匹配;
- Bean 在到达初始化前已失败;
- 实例化前 BPP 返回替代对象并绕开普通链;
- 测试没有加载完整上下文;
- 日志看的是另一个同名或同类型实例。
不要通过手工调用 PostConstruct 方法修复,这会掩盖对象不受容器管理的根因。
三十二、Bean 没有被 AOP 代理排查
- 确认最终注入对象运行时 class;
- 使用 AopUtils 判断代理;
- 检查 AutoProxyCreator 是否注册;
- 检查 Bean 是否在完整 BPP 链注册前过早创建;
- 检查 Pointcut 与 Advisor;
- 检查 final/private/static;
- 检查 JDK代理接口类型;
- 检查 self-invocation;
- 检查 earlyBeanReference 与最终包装;
- 检查拿到的是 FactoryBean 工厂还是产品。
三十三、关闭卡住排查 Runbook
- 在终止宽限期内连续采集线程栈;
- 找到 ContextClosedEvent、LifecycleProcessor或destroy回调所在栈;
- 检查线程池 awaitTermination 是否无界;
- 检查远程请求、DNS、数据库关闭是否无超时;
- 检查锁顺序和回调互相等待;
- 检查 SmartLifecycle.stop 是否调用 callback;
- 检查消费者是否仍接收新任务;
- 检查非守护线程;
- 临时止损时先摘流量和停止入口;
- 长期修复为分阶段、有界、幂等关闭。
三十四、源码断点
固定一个 BeanName,按顺序断点:
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/10 | Java EE模块进入迁移期 |
| JDK 11 + Spring 5 | annotation API通常需要显式依赖 |
| Spring 5.3 + JDK 8 | 本页主要Demo验证线 |
| Spring 6 / Boot 3 | Java 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自调用仍绕过外部代理。
三十七、关联知识与验收
- Spring IoC容器与依赖注入全过程
@Autowired与@Resource注入全过程@Lazy懒加载与代理全过程- 循环依赖与三级缓存全过程
- Spring AOP代理与拦截器链
- Spring容器启动与Bean创建源码执行链
- Spring核心扩展点
- Spring面试知识点
掌握验收:
- 能画出BeanDefinition到销毁的完整流程;
- 能区分实例化、属性填充和初始化;
- 能说明基础Aware与ApplicationContextAware的处理位置;
- 能写出PostConstruct、afterPropertiesSet、init-method顺序;
- 能解释BPP前后置和返回null语义;
- 能说明AOP正常代理和earlyBeanReference;
- 能解释SmartInitializingSingleton、SmartLifecycle和ContextRefreshedEvent差异;
- 能说明singleton、prototype、Lazy和FactoryBean生命周期边界;
- 能运行15事件Demo并验证销毁顺序;
- 能完成创建失败、PostConstruct缺失、代理缺失和关闭卡住排查。
