Lesson 54 · Spring 生态核心原理
Bean 生命周期:从实例化到销毁的完整流程
面试官:说说 Spring Bean 的生命周期?
"说说 Spring Bean 的生命周期?"——这是一道能拉开候选人差距的题。初级选手答"创建、使用、销毁"三阶段;中级选手能说出 InitializingBean 和 init-method;能完整说出 14 步链路并指出扩展点的,通常是真正读过 AbstractAutowireCapableBeanFactory 源码的人。
Bean 生命周期是 Spring 面试中出现频率最高的题目之一,原因很直接:它串联了 IoC 容器、依赖注入、AOP 代理、扩展点设计等核心知识点。能把这条链路讲清楚,说明你对 Spring 的理解不是停留在"会用注解"的层面,而是理解了框架的骨架。
- 源码功底——有没有读过
doCreateBean()和initializeBean()的核心流程 - 扩展意识——知不知道 BeanPostProcessor 是 AOP、事务、异步等几乎所有 Spring 高级特性的基石
- 工程经验——有没有在实际项目中利用生命周期回调解决过问题(如缓存预热、连接池初始化)
本文将从源码层面拆解 Bean 从出生到死亡的完整 14 步,每一步都对应 AbstractAutowireCapableBeanFactory 中的具体方法调用。读完之后,你将能够:在面试中画出完整的流程图、说出每一步的类名和方法名、并指出框架开发者最常用的扩展点。
14 步全景图:从 new 到 GC 的完整链路
先看全貌,再逐站拆解。下面这张图覆盖了 Bean 生命周期的全部 14 个步骤,建议你面试时能在白板上画出类似的流程:
生命周期的主干是 doCreateBean() 方法,内部依次调用 createBeanInstance() → populateBean() → initializeBean()。面试时先说这三个方法名,再展开细节,面试官会知道你确实读过源码。
实例化阶段:createBeanInstance 的三条路径
Bean 生命周期的第一步并不是直接 new。Spring 在实例化之前和之后都留了扩展点,整个实例化阶段的执行顺序是:
步骤 ①:InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() — 这是最早的拦截点。如果这个方法返回了一个非 null 的对象,Spring 会直接使用它作为 Bean 实例,跳过后续所有的创建和初始化步骤。AOP 的 CGLIB 代理理论上可以在这里介入,但 Spring 默认选择在初始化后(步骤 ⑬)才创建代理。
步骤 ②:createBeanInstance() — 真正创建对象实例。Spring 有三种策略来选择构造方式:
// 路径 1:Supplier 创建(@Bean 方法返回的 Supplier) if (mbd.getInstanceSupplier() != null) { instanceSupplier = mbd.getInstanceSupplier(); } // 路径 2:工厂方法(@Bean 标注的方法 或 factory-method) if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // 路径 3:构造函数(最常见的方式) // 如果只有默认无参构造 → 直接 newInstance() // 如果有多个构造器 → Spring 会选择"最贪婪"的那个 // 即参数最多、且参数类型都能在容器中找到 Bean 的那个构造器 return autowireConstructor(beanName, mbd, ctor, args);
构造函数注入 vs Setter 注入:Spring 官方推荐哪种?
Spring 团队从 4.x 开始推荐使用构造函数注入,理由是:(1) 保证 Bean 创建后依赖不可变(final);(2) 不会出现"属性忘了注入"的 NPE;(3) 方便写单测——直接在构造时传入 mock。Setter 注入(或 @Autowired 字段注入)适合可选依赖。
步骤 ③:MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition() — 对象已创建但还没注入属性。这一步的主要工作是预处理 BeanDefinition,比如 AutowiredAnnotationBeanPostProcessor 会在这一步扫描出所有带 @Autowired、@Value、@Inject 注解的字段和方法,缓存起来供后续 populateBean 使用。
实例化完成后(步骤 ② 之后),Spring 会立即将这个"半成品"Bean 的 ObjectFactory 放入三级缓存 singletonFactories。这是解决循环依赖的关键——如果 A 依赖 B,B 又依赖 A,B 在注入属性时发现 A 还没创建完,就会从三级缓存拿到 A 的早期引用(可能是代理对象),从而打破循环。prototype 作用域的 Bean 不参与缓存,因此不支持循环依赖。
属性注入:populateBean 的注入顺序
对象创建出来了,但它还是一个"空壳"——所有依赖都是 null。populateBean() 负责把依赖"灌"进去。
步骤 ④:populateBean() — 处理 autowireMode(BY_NAME、BY_TYPE)等传统 XML 注入方式。在现代 Spring Boot 项目中,这个分支通常不走,真正的注入在步骤 ⑤。
步骤 ⑤:InstantiationAwareBeanPostProcessor.postProcessProperties() — 这是现代 Spring 属性注入的主战场。AutowiredAnnotationBeanPostProcessor 会处理 @Autowired 和 @Value,CommonAnnotationBeanPostProcessor 会处理 @Resource。
public class OrderService { // @Autowired — Spring 提供,按类型注入,多个候选时用 @Qualifier // 默认 required=true,找不到 Bean 会抛异常 @Autowired private PaymentService paymentService; // @Value — 注入配置值、SpEL 表达式、字面量 @Value("${app.max-retry:3}") // 带默认值的配置项 private int maxRetry; // @Resource — JSR-250 标准,默认按名称注入 // name 属性匹配 Bean 名称,找不到再按类型 fallback @Resource(name = "smsSender") private MessageSender messageSender; }
| 注解 | 来源 | 注入策略 | 找不到 Bean 时 | 推荐场景 |
|---|---|---|---|---|
@Autowired | Spring | 先按类型,多个候选按名称 | 抛 NoSuchBeanDefinitionException | 构造函数注入 |
@Value | Spring | SpEL / 配置值 | 抛异常(除非有默认值) | 注入配置项 |
@Resource | JSR-250 | 先按名称,再按类型 | 抛 NoSuchBeanDefinitionException | 指定名称注入 |
@Autowired 和 @Resource 同时标注在一个字段上,谁生效?
两者由不同的 BeanPostProcessor 处理。Spring 的处理顺序是:先执行 AutowiredAnnotationBeanPostProcessor(处理 @Autowired / @Value),再执行 CommonAnnotationBeanPostProcessor(处理 @Resource)。所以如果两个注解都在,@Autowired 先注入,@Resource 后覆盖——最终效果是 @Resource 的值生效。
required=true 的依赖都已解析并赋值。如果某个 @Autowired(required=false) 的依赖找不到,该字段保持 null,不抛异常。
初始化阶段:6 步回调链(面试重灾区)
属性注入完成后,Bean 进入初始化阶段——这是生命周期中回调最密集、面试最爱考的阶段。全部由 initializeBean() 方法驱动,源码结构非常清晰:
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // —— 步骤 ⑥⑦⑧:Aware 回调 —— invokeAwareMethods(beanName, bean); // —— 步骤 ⑨:BPP 前置处理 —— wrappedBean = applyBeanPostProcessorsBeforeInitialization( wrappedBean, beanName); // —— 步骤 ⑩⑪⑫:初始化回调 —— invokeInitMethods(beanName, wrappedBean, mbd); // —— 步骤 ⑬:BPP 后置处理(AOP 代理在此创建)—— wrappedBean = applyBeanPostProcessorsAfterInitialization( wrappedBean, beanName); return wrappedBean; }
我们逐步拆解:
步骤 ⑥⑦⑧:invokeAwareMethods() — 如果 Bean 实现了以下 Aware 接口,Spring 会依次回调:
| 顺序 | 接口 | 回调方法 | 注入的内容 |
|---|---|---|---|
| ⑥ | BeanNameAware | setBeanName(name) | Bean 在容器中的名称 |
| ⑦ | BeanClassLoaderAware | setBeanClassLoader(cl) | 加载此 Bean 的 ClassLoader |
| ⑧ | BeanFactoryAware | setBeanFactory(factory) | BeanFactory 实例本身 |
注意:ApplicationContextAware 不在这个方法里处理!它是由 ApplicationContextAwareProcessor(一个 BeanPostProcessor)在步骤 ⑨ 中处理的。这个细节是面试区分度很高的考点。
步骤 ⑨:BeanPostProcessor.postProcessBeforeInitialization() — 所有注册的 BPP 按顺序执行。其中最重要的几个:
ApplicationContextAwareProcessor:处理EnvironmentAware、ApplicationContextAware、MessageSourceAware等 6 个 Aware 接口CommonAnnotationBeanPostProcessor:识别@PostConstruct方法
步骤 ⑩:@PostConstruct — JSR-250 标准注解。由 InitDestroyAnnotationBeanPostProcessor 处理,标注的方法在所有属性注入完成后、afterPropertiesSet 之前执行。
步骤 ⑪:InitializingBean.afterPropertiesSet() — Spring 自定义的初始化回调接口。在 @PostConstruct 之后执行。Spring 内部很多组件(如 AbstractApplicationContext 自身)使用此接口。
步骤 ⑫:init-method — 通过 @Bean(initMethod="xxx") 或 XML init-method 指定的自定义方法。在 afterPropertiesSet 之后执行。
步骤 ⑬:BeanPostProcessor.postProcessAfterInitialization() — 这是 Bean 初始化完成前的最后一个拦截点。AbstractAutoProxyCreator 在这个步骤中检查 Bean 是否需要 AOP 代理(事务、缓存、异步等),如果需要就创建代理对象替换原始 Bean。
因为代理对象需要包装的是"完全初始化好的 Bean"。如果在 postProcessBeforeInitialization 就创建代理,那么 @PostConstruct、afterPropertiesSet 等回调就会作用在代理对象上,可能触发不该触发的事务切面或安全检查。放在 postProcessAfterInitialization 可以保证:原始 Bean 完成所有初始化后,再被包装成代理——代理只拦截业务方法,不参与初始化流程。
Aware 回调 → postProcessBeforeInitialization → @PostConstruct → afterPropertiesSet → init-method → postProcessAfterInitialization。记口诀:"先 Aware、再 BPP 前、三初始化、最后 BPP 后"。
使用与销毁:Bean 的谢幕
经过步骤 ⑭,Bean 已经完全初始化(可能被 AOP 代理包装),存放在单例池中,等待业务代码调用。这个阶段没什么特别的回调——Bean 就是一个正常的 Java 对象。
真正的故事发生在容器关闭时。当调用 ApplicationContext.close() 或 JVM 收到 shutdown 信号时,Spring 会触发销毁流程,由 DisposableBeanAdapter.destroy() 驱动:
public void destroy() { // D1. @PreDestroy — JSR-250 标准,最先执行 // 由 InitDestroyAnnotationBeanPostProcessor 触发 invokeCustomDestroyMethod(this.destroyMethod); // D2. DisposableBean.destroy() — Spring 接口回调 if (this.bean instanceof DisposableBean) { ((DisposableBean) this.bean).destroy(); } // D3. destroy-method — 自定义销毁方法 // 通过 @Bean(destroyMethod="cleanup") 指定 invokeCustomDestroyMethod(this.destroyMethodName); }
| 顺序 | 回调 | 来源 | 典型用途 |
|---|---|---|---|
| D1 | @PreDestroy | JSR-250 | 释放连接、关闭文件句柄 |
| D2 | DisposableBean.destroy() | Spring 接口 | 框架内部资源清理 |
| D3 | destroy-method | 配置指定 | 业务自定义清理逻辑 |
@Bean(destroyMethod="close") 中的 close 是怎么来的?
Spring 有一个 inferred destroy method 机制:如果 @Bean 的 destroyMethod 没有显式指定,Spring 会检查 Bean 类中是否存在 close() 或 shutdown() 方法。如果存在,自动将其注册为销毁回调。这就是为什么 DataSource、ConnectionPool 等资源类不需要手动配置 destroyMethod 也能正确关闭。
@Component public class CacheManager implements BeanNameAware, InitializingBean, DisposableBean { private String beanName; private ConcurrentHashMap<String, Object> cache; // 步骤 ⑥:Aware 回调 @Override public void setBeanName(String name) { this.beanName = name; log.info("[⑥] BeanNameAware: " + name); } // 步骤 ⑩:@PostConstruct @PostConstruct public void postConstruct() { log.info("[⑩] @PostConstruct"); } // 步骤 ⑪:InitializingBean @Override public void afterPropertiesSet() { this.cache = new ConcurrentHashMap<>(); log.info("[⑪] afterPropertiesSet: 缓存初始化完成"); loadHotData(); // 缓存预热 } // 步骤 ⑫:init-method public void customInit() { log.info("[⑫] init-method: customInit()"); } // D1:@PreDestroy @PreDestroy public void preDestroy() { log.info("[D1] @PreDestroy: 准备清理"); } // D2:DisposableBean @Override public void destroy() { cache.clear(); log.info("[D2] DisposableBean.destroy: 缓存已清空"); } }
启动并关闭容器后,日志输出顺序严格为:⑥ → ⑩ → ⑪ → ⑫ → ... 应用运行 ... → D1 → D2。你可以自己跑一下验证。
总结:面试速查表与框架扩展点
Bean 生命周期 14 步速查
| # | 步骤 | 核心方法 / 类 | 阶段 |
|---|---|---|---|
| ① | BPP.postProcessBeforeInstantiation | InstantiationAwareBeanPostProcessor | 实例化 |
| ② | createBeanInstance | 构造函数 / 工厂方法 / @Bean Supplier | |
| ③ | postProcessMergedBeanDefinition | MergedBeanDefinitionPostProcessor | |
| ④ | populateBean(XML autowire) | BY_NAME / BY_TYPE 传统模式 | 属性注入 |
| ⑤ | postProcessProperties | @Autowired / @Value / @Resource | |
| ⑥ | BeanNameAware.setBeanName | invokeAwareMethods | Aware 回调 |
| ⑦ | BeanClassLoaderAware | ||
| ⑧ | BeanFactoryAware.setBeanFactory | ||
| ⑨ | BPP.postProcessBeforeInitialization | 含 ApplicationContextAwareProcessor | 初始化 |
| ⑩ | @PostConstruct | InitDestroyAnnotationBeanPostProcessor | |
| ⑪ | InitializingBean.afterPropertiesSet | Spring 接口回调 | |
| ⑫ | init-method | 自定义初始化方法 | |
| ⑬ | BPP.postProcessAfterInitialization | AOP 代理在此创建 | |
| ⑭ | Bean 就绪,投入使用 | — | 使用 |
| D1 | @PreDestroy | InitDestroyAnnotationBeanPostProcessor | 销毁 |
| D2 | DisposableBean.destroy | Spring 接口回调 | |
| D3 | destroy-method | 自定义销毁方法 |
如果你要写一个 Spring Starter 或自定义框架组件,以下扩展点最常用:
- BeanPostProcessor — 最强大也最通用的扩展点。AOP、@Async、@ConfigurationProperties 绑定都靠它。实现
postProcessAfterInitialization可以在 Bean 初始化完成后进行包装或替换。 - BeanFactoryPostProcessor — 在 Bean 实例化之前修改 BeanDefinition。典型用途:
PropertySourcesPlaceholderConfigurer(处理${...}占位符)。 - InstantiationAwareBeanPostProcessor — 比 BeanPostProcessor 更早介入,能在实例化前后拦截。
AutowiredAnnotationBeanPostProcessor就是它的实现。 - SmartInitializingSingleton — 在所有单例 Bean 初始化完成后回调。
@EventListener的注册就是在这一步完成的。 - ApplicationListener<ContextRefreshedEvent> — 容器刷新完成事件。适合做全局性的启动后检查或预热。
"Bean 生命周期的主干是 doCreateBean() 里的三步:createBeanInstance 实例化、populateBean 属性注入、initializeBean 初始化。初始化阶段最复杂,依次是:Aware 回调(BeanNameAware、BeanFactoryAware)、BPP 前置处理、@PostConstruct、afterPropertiesSet、init-method、BPP 后置处理。AOP 代理在 BPP 后置处理中创建。销毁时按 @PreDestroy → DisposableBean.destroy → destroy-method 的顺序回调。如果我要写框架扩展,最常用的扩展点是 BeanPostProcessor 和 BeanFactoryPostProcessor。"
doCreateBean() 中预留了一系列回调接口,让框架和开发者可以在 Bean 的每个阶段插入自定义逻辑。理解这条链路,就理解了 Spring IoC 容器的骨架。