Lesson 54 · Spring 生态核心原理

Bean 生命周期:从实例化到销毁的完整流程

深度·🔥 极高·#Spring·#Bean·#核心

第 1 站

面试官:说说 Spring Bean 的生命周期?

"说说 Spring Bean 的生命周期?"——这是一道能拉开候选人差距的题。初级选手答"创建、使用、销毁"三阶段;中级选手能说出 InitializingBean 和 init-method;能完整说出 14 步链路并指出扩展点的,通常是真正读过 AbstractAutowireCapableBeanFactory 源码的人。

Bean 生命周期是 Spring 面试中出现频率最高的题目之一,原因很直接:它串联了 IoC 容器、依赖注入、AOP 代理、扩展点设计等核心知识点。能把这条链路讲清楚,说明你对 Spring 的理解不是停留在"会用注解"的层面,而是理解了框架的骨架。

面试官真正在考什么?
  1. 源码功底——有没有读过 doCreateBean()initializeBean() 的核心流程
  2. 扩展意识——知不知道 BeanPostProcessor 是 AOP、事务、异步等几乎所有 Spring 高级特性的基石
  3. 工程经验——有没有在实际项目中利用生命周期回调解决过问题(如缓存预热、连接池初始化)

本文将从源码层面拆解 Bean 从出生到死亡的完整 14 步,每一步都对应 AbstractAutowireCapableBeanFactory 中的具体方法调用。读完之后,你将能够:在面试中画出完整的流程图、说出每一步的类名和方法名、并指出框架开发者最常用的扩展点。

第 2 站

14 步全景图:从 new 到 GC 的完整链路

先看全貌,再逐站拆解。下面这张图覆盖了 Bean 生命周期的全部 14 个步骤,建议你面试时能在白板上画出类似的流程:

Spring Bean 生命周期 —— 14 步完整链路 实例化阶段 ① InstantiationAwareBPP postProcessBeforeInstantiation ② createBeanInstance 构造函数 / 工厂方法 / @Bean ③ MergedBeanDefinitionPostProcessor postProcessMergedBeanDefinition ↓ 早期引用存入三级缓存(解决循环依赖) 属性注入阶段 ④ populateBean — 属性注入 ⑤ InstantiationAwareBPP.postProcessProperties Aware 回调阶段 ⑥ BeanNameAware ⑦ BeanFactoryAware ⑧ ApplicationContextAware 初始化阶段(BeanPostProcessor + 回调) ⑨ BeanPostProcessor.postProcessBeforeInitialization AOP 代理可能在此生成(AbstractAutoProxyCreator) ⑩ @PostConstruct(CommonAnnotationBPP) ⑪ InitializingBean.afterPropertiesSet() ⑫ init-method(自定义初始化方法) ⑬ BeanPostProcessor.postProcessAfterInitialization AOP 代理通常在此步创建(wrapIfNecessary) ⑭ Bean 就绪,投入使用 容器运行中,Bean 正常处理业务请求 销毁阶段(容器关闭时) 容器 shutdown D1. @PreDestroy(CommonAnnotationBPP) D2. DisposableBean.destroy() D3. destroy-method(自定义销毁方法) 核心源码入口:AbstractAutowireCapableBeanFactory.doCreateBean() → populateBean() → initializeBean() 销毁入口:DisposableBeanAdapter.destroy() — 由 DefaultSingletonBeanRegistry.destroySingletons() 驱动 图:Spring Bean 生命周期 14 步完整流程
图 1 Spring Bean 生命周期 14 步完整链路全景图
核心记忆

生命周期的主干是 doCreateBean() 方法,内部依次调用 createBeanInstance()populateBean()initializeBean()。面试时先说这三个方法名,再展开细节,面试官会知道你确实读过源码。

第 3 站

实例化阶段:createBeanInstance 的三条路径

Bean 生命周期的第一步并不是直接 new。Spring 在实例化之前和之后都留了扩展点,整个实例化阶段的执行顺序是:

步骤 ①InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() — 这是最早的拦截点。如果这个方法返回了一个非 null 的对象,Spring 会直接使用它作为 Bean 实例,跳过后续所有的创建和初始化步骤。AOP 的 CGLIB 代理理论上可以在这里介入,但 Spring 默认选择在初始化后(步骤 ⑬)才创建代理。

步骤 ②createBeanInstance() — 真正创建对象实例。Spring 有三种策略来选择构造方式:

createBeanInstance 的三条路径
// 路径 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 不参与缓存,因此不支持循环依赖

第 4 站

属性注入:populateBean 的注入顺序

对象创建出来了,但它还是一个"空壳"——所有依赖都是 null。populateBean() 负责把依赖"灌"进去。

步骤 ④populateBean() — 处理 autowireMode(BY_NAME、BY_TYPE)等传统 XML 注入方式。在现代 Spring Boot 项目中,这个分支通常不走,真正的注入在步骤 ⑤。

步骤 ⑤InstantiationAwareBeanPostProcessor.postProcessProperties() — 这是现代 Spring 属性注入的主战场。AutowiredAnnotationBeanPostProcessor 会处理 @Autowired@ValueCommonAnnotationBeanPostProcessor 会处理 @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 时推荐场景
@AutowiredSpring先按类型,多个候选按名称抛 NoSuchBeanDefinitionException构造函数注入
@ValueSpringSpEL / 配置值抛异常(除非有默认值)注入配置项
@ResourceJSR-250先按名称,再按类型抛 NoSuchBeanDefinitionException指定名称注入

@Autowired 和 @Resource 同时标注在一个字段上,谁生效?

两者由不同的 BeanPostProcessor 处理。Spring 的处理顺序是:先执行 AutowiredAnnotationBeanPostProcessor(处理 @Autowired / @Value),再执行 CommonAnnotationBeanPostProcessor(处理 @Resource)。所以如果两个注解都在,@Autowired 先注入,@Resource 后覆盖——最终效果是 @Resource 的值生效。

属性注入完成条件:所有 required=true 的依赖都已解析并赋值。如果某个 @Autowired(required=false) 的依赖找不到,该字段保持 null,不抛异常。
第 5 站

初始化阶段:6 步回调链(面试重灾区)

属性注入完成后,Bean 进入初始化阶段——这是生命周期中回调最密集、面试最爱考的阶段。全部由 initializeBean() 方法驱动,源码结构非常清晰:

AbstractAutowireCapableBeanFactory.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 会依次回调:

顺序接口回调方法注入的内容
BeanNameAwaresetBeanName(name)Bean 在容器中的名称
BeanClassLoaderAwaresetBeanClassLoader(cl)加载此 Bean 的 ClassLoader
BeanFactoryAwaresetBeanFactory(factory)BeanFactory 实例本身

注意:ApplicationContextAware 不在这个方法里处理!它是由 ApplicationContextAwareProcessor(一个 BeanPostProcessor)在步骤 ⑨ 中处理的。这个细节是面试区分度很高的考点。

步骤 ⑨BeanPostProcessor.postProcessBeforeInitialization() — 所有注册的 BPP 按顺序执行。其中最重要的几个:

  • ApplicationContextAwareProcessor:处理 EnvironmentAwareApplicationContextAwareMessageSourceAware 等 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。

为什么 AOP 代理在步骤 ⑬ 而不是步骤 ⑨?

因为代理对象需要包装的是"完全初始化好的 Bean"。如果在 postProcessBeforeInitialization 就创建代理,那么 @PostConstructafterPropertiesSet 等回调就会作用在代理对象上,可能触发不该触发的事务切面或安全检查。放在 postProcessAfterInitialization 可以保证:原始 Bean 完成所有初始化后,再被包装成代理——代理只拦截业务方法,不参与初始化流程

面试必背顺序

Aware 回调 → postProcessBeforeInitialization → @PostConstruct → afterPropertiesSet → init-method → postProcessAfterInitialization。记口诀:"先 Aware、再 BPP 前、三初始化、最后 BPP 后"

第 6 站

使用与销毁: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@PreDestroyJSR-250释放连接、关闭文件句柄
D2DisposableBean.destroy()Spring 接口框架内部资源清理
D3destroy-method配置指定业务自定义清理逻辑

@Bean(destroyMethod="close") 中的 close 是怎么来的?

Spring 有一个 inferred destroy method 机制:如果 @BeandestroyMethod 没有显式指定,Spring 会检查 Bean 类中是否存在 close()shutdown() 方法。如果存在,自动将其注册为销毁回调。这就是为什么 DataSource、ConnectionPool 等资源类不需要手动配置 destroyMethod 也能正确关闭。

实战示例:完整生命周期的 Bean
@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。你可以自己跑一下验证。

第 7 站

总结:面试速查表与框架扩展点

Bean 生命周期 14 步速查

#步骤核心方法 / 类阶段
BPP.postProcessBeforeInstantiationInstantiationAwareBeanPostProcessor实例化
createBeanInstance构造函数 / 工厂方法 / @Bean Supplier
postProcessMergedBeanDefinitionMergedBeanDefinitionPostProcessor
populateBean(XML autowire)BY_NAME / BY_TYPE 传统模式属性注入
postProcessProperties@Autowired / @Value / @Resource
BeanNameAware.setBeanNameinvokeAwareMethodsAware 回调
BeanClassLoaderAware
BeanFactoryAware.setBeanFactory
BPP.postProcessBeforeInitialization含 ApplicationContextAwareProcessor初始化
@PostConstructInitDestroyAnnotationBeanPostProcessor
InitializingBean.afterPropertiesSetSpring 接口回调
init-method自定义初始化方法
BPP.postProcessAfterInitializationAOP 代理在此创建
Bean 就绪,投入使用使用
D1@PreDestroyInitDestroyAnnotationBeanPostProcessor销毁
D2DisposableBean.destroySpring 接口回调
D3destroy-method自定义销毁方法
框架开发者的 5 个黄金扩展点

如果你要写一个 Spring Starter 或自定义框架组件,以下扩展点最常用:

  1. BeanPostProcessor — 最强大也最通用的扩展点。AOP、@Async、@ConfigurationProperties 绑定都靠它。实现 postProcessAfterInitialization 可以在 Bean 初始化完成后进行包装或替换。
  2. BeanFactoryPostProcessor — 在 Bean 实例化之前修改 BeanDefinition。典型用途:PropertySourcesPlaceholderConfigurer(处理 ${...} 占位符)。
  3. InstantiationAwareBeanPostProcessor — 比 BeanPostProcessor 更早介入,能在实例化前后拦截。AutowiredAnnotationBeanPostProcessor 就是它的实现。
  4. SmartInitializingSingleton — 在所有单例 Bean 初始化完成后回调。@EventListener 的注册就是在这一步完成的。
  5. ApplicationListener<ContextRefreshedEvent> — 容器刷新完成事件。适合做全局性的启动后检查或预热。
面试回答模板(60 秒版)

"Bean 生命周期的主干是 doCreateBean() 里的三步:createBeanInstance 实例化、populateBean 属性注入、initializeBean 初始化。初始化阶段最复杂,依次是:Aware 回调(BeanNameAware、BeanFactoryAware)、BPP 前置处理、@PostConstruct、afterPropertiesSet、init-method、BPP 后置处理。AOP 代理在 BPP 后置处理中创建。销毁时按 @PreDestroy → DisposableBean.destroy → destroy-method 的顺序回调。如果我要写框架扩展,最常用的扩展点是 BeanPostProcessor 和 BeanFactoryPostProcessor。"

一句话总结:Bean 生命周期的本质是 Spring 在 doCreateBean() 中预留了一系列回调接口,让框架和开发者可以在 Bean 的每个阶段插入自定义逻辑。理解这条链路,就理解了 Spring IoC 容器的骨架。