Spring Bean生命周期原来是这样-一篇讲透实例化、初始化与销毁

0 阅读13分钟

前言

“对象不是已经 new 出来了吗,为什么面试官还说它没初始化完?”

很多人第一次学 Spring Bean 生命周期,都会被一堆名词绕晕:实例化、属性注入、Aware、BeanPostProcessor、afterPropertiesSet、init-method、destroy-method……

更麻烦的是,这些概念经常被混在一起讲,看着像懂了,真到面试或排查问题时又说不清。

今天这篇文章,我不打算再给你背“八股口诀”,而是基于 Spring 官方文档和源码主线,用最通俗的语言把两件事讲明白:

  1. Spring Bean 的一生到底经历了什么。
  2. 初始化过程到底从哪开始,到哪结束。

关于 Spring Bean 生命周期,最容易混淆的 4 个点

Spring Bean 生命周期这个话题,最容易让人越学越乱,不是因为它太难,而是因为很多概念看起来很像。

常见误区更准确的说法
Bean 生命周期只能死记硬背一串步骤更适合先按“创建、使用、销毁”三大阶段理解,再往里拆细节
设置属性值也算初始化设置属性值属于依赖注入或属性填充,发生在初始化之前
所有 Aware 都是在 initializeBean() 里调用BeanNameAwareBeanFactoryAwareBeanClassLoaderAware 是,ApplicationContextAware 这类通常由 BeanPostProcessor 处理
销毁就是容器关闭时统一调一下 destroy()真实情况还要看作用域、销毁回调类型,以及是否注册了销毁逻辑

还有两个常被漏掉的点:

  1. 现代 Spring 更常见的初始化和销毁回调,其实是 @PostConstruct@PreDestroy
  2. prototype 作用域的 Bean,Spring 通常只负责创建,不会在容器关闭时自动帮你执行销毁回调。

@PostConstruct@PreDestroy 到底是什么?

很多人第一次看到这两个注解会懵:它们和 afterPropertiesSet()destroy() 到底是什么关系?

你在 Bean 里写两个普通方法,Spring 会在合适的时机自动帮你调用。

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class CacheService {

    @PostConstruct
    public void init() {
        System.out.println("Bean 初始化完成,开始预热缓存");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("容器关闭前,释放资源");
    }
}

它们分别表示:

  • @PostConstruct:依赖注入完成后执行一次,常用来做配置校验、缓存预热、初始化连接等
  • @PreDestroy:容器销毁 Bean 前执行一次,常用来做关闭线程池、断开连接、清理资源等

之所以说它们在现代 Spring 里更常见,是因为以前很多人会这样写:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class DemoBean implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() {
        System.out.println("初始化逻辑");
    }

    @Override
    public void destroy() {
        System.out.println("销毁逻辑");
    }
}

这种写法当然能用,但类会直接依赖 Spring 接口。相比之下,@PostConstruct / @PreDestroy 更自然,因为业务类只需要写普通方法,再用注解告诉容器何时调用它。

如果一个 Bean 同时配置了多种初始化机制,Spring 官方文档给出的顺序是:

  1. @PostConstruct
  2. afterPropertiesSet()
  3. 自定义 init-method

销毁顺序则是:

  1. @PreDestroy
  2. destroy()
  3. 自定义 destroy-method

还有一个容易踩坑的细节:

在 Spring 6 和 Spring Boot 3 里,常见导包一般是下面这个:

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

不是早年常见的 javax.annotation.*

再提醒一次,@PreDestroy 主要对容器完整管理生命周期的 Bean 有效。对于 prototype 作用域 Bean,Spring 通常不会在容器关闭时自动帮你执行销毁回调。

把 Bean 生命周期讲成人话

你可以把 Spring 容器理解成一家“管家型酒店”。

你只负责说一句:“我想要一个 UserService。”

剩下的事情,Spring 管家会帮你做完:把房间造出来、家具摆好、电通上、水接好、打扫完,再把钥匙交给你。等酒店关门时,它还会按规则做清理。

对应到 Bean,大致就是下面这条主线:

createBean(...)
  -> doCreateBean(...)
  -> createBeanInstance(...)
  -> populateBean(...)
  -> initializeBean(...)
     -> invokeAwareMethods(...)
     -> invokeInitMethods(...)
     -> applyBeanPostProcessorsAfterInitialization(...)
  -> registerDisposableBeanIfNecessary(...)
     // 如果 Bean 需要销毁逻辑,就把 destroy 回调先注册好,等容器关闭时再统一触发

这条链路讲的是普通 Bean 的常规主线。继续往源码细节里展开,还会看到 resolveBeforeInstantiation()MergedBeanDefinitionPostProcessor 这类扩展点,但不影响先把“实例化、依赖注入、初始化、销毁”这条主路走明白。

可以这样理解:

  1. createBean():这是总入口。Spring 先确认这个 Bean 的定义信息没问题,类也能正确解析出来。与此同时,它还会先问一句:“有没有 BeanPostProcessor 想在实例化前提前接管,直接返回一个代理对象?” 如果有,后面的常规创建流程甚至可以被短路。
  2. doCreateBean():这是核心调度方法。你可以把它理解成总装车间的流水线负责人,它本身不只做一件事,而是负责把实例化、属性填充、初始化、销毁回调注册这些动作串起来。
  3. createBeanInstance():这一步只干一件事,就是先把对象造出来。至于怎么造,要看 Bean 的定义方式,可能走无参构造器,可能走带参构造器自动装配,也可能走工厂方法。
  4. populateBean():对象造出来后,Spring 开始往里面塞依赖。比如 @Autowired 注入的其他 Bean,@Value 注入的配置值,或者配置文件里声明的属性,主要都在这里完成。
  5. initializeBean():到了这里,才进入我们平时说的“初始化阶段”。也就是说,对象不只是被创建出来了,还要开始执行一系列“正式上岗前的准备动作”。
  6. invokeAwareMethods():如果这个 Bean 实现了某些 Aware 接口,Spring 会在这里回调它。比如告诉它自己的 Bean 名字、当前 BeanFactory 是谁、类加载器是什么。
  7. applyBeanPostProcessorsBeforeInitialization():进入“初始化前处理”阶段。这里调用的仍然是 BeanPostProcessor 这个扩展点,只不过执行的是它的 postProcessBeforeInitialization() 回调。很多基于注解的生命周期逻辑,都是在这一阶段被触发的。像 @PostConstruct,以及 ApplicationContextAware 这一类能力,通常都和这里密切相关。
  8. invokeInitMethods():真正的初始化方法在这里执行。常见的就是 afterPropertiesSet(),以及你自己定义的 init-method
  9. applyBeanPostProcessorsAfterInitialization():初始化完成后,Spring 再执行一次 BeanPostProcessor,这次走的是 postProcessAfterInitialization()。很多时候,最终放进容器、交给业务代码使用的已经不是最原始的对象,而是这里返回的包装对象或代理对象。
  10. registerDisposableBeanIfNecessary():Bean 可以用了,但 Spring 还会顺手做一件事,就是看看这个 Bean 将来要不要销毁。如果需要,它会先把销毁逻辑登记好,等容器关闭时再统一调用。

一张文字流程图看懂主流程

实例化 Bean
-> 先把对象创建出来

属性填充
-> 把 @Autowired、@Value 和配置属性注入进去

Aware 回调
-> 告诉 Bean:你叫什么、归哪个 BeanFactory 管、用什么类加载器

初始化前置处理
-> 执行 BeanPostProcessor 前置逻辑
-> 常见的 @PostConstruct、ApplicationContextAware 相关处理通常在这附近发生

初始化方法
-> 执行 afterPropertiesSet()
-> 执行自定义 init-method

初始化后置处理
-> 再执行一次 BeanPostProcessor
-> 很多 AOP 代理对象会在这里返回

Bean 进入可用状态
-> 到这里,业务代码拿到的才是最终可用的 Bean

注册销毁回调
-> Spring 先记住这个 Bean 将来该怎么销毁

容器关闭
-> 执行 @PreDestroy
-> 执行 destroy()
-> 执行自定义 destroy-method

把这条主线再压缩成一句话,就是:

先把对象造出来,再把依赖塞进去,再做上岗前检查和增强,最后登记好下岗时怎么收尾。

从 Spring Boot 启动到 createBeanInstance() 的调用链

看 Spring Boot 源码时,最容易混淆的一点是:createBeanInstance() 并不是直接写在 run() 里,而是主要在 refreshContext(context) 这一步里被间接触发的。

调用链可以先记成下面这样:

SpringApplication.run(...)
-> refreshContext(context)
-> context.refresh()
-> AbstractApplicationContext.refresh()
-> finishBeanFactoryInitialization(beanFactory)
-> beanFactory.preInstantiateSingletons()
-> instantiateSingleton(beanName)
-> getBean(beanName)
-> doGetBean(...)
-> createBean(beanName, mbd, args)
-> doCreateBean(...)
-> createBeanInstance(beanName, mbd, args)

把这条链翻译成人话,就是:

  1. SpringApplication.run(...):Spring Boot 启动总控,负责准备环境、监听器、上下文对象和启动过程中的各种钩子。
  2. refreshContext(context):真正开始刷新容器。大多数普通单例 Bean,就是从这里开始进入创建流程的。
  3. context.refresh():进入 Spring Framework 的容器刷新主逻辑。
  4. finishBeanFactoryInitialization(beanFactory):这里是一个关键节点,作用是完成 BeanFactory 的最后初始化,并准备实例化剩余的单例 Bean。
  5. beanFactory.preInstantiateSingletons():开始批量创建所有非懒加载的单例 Bean。
  6. instantiateSingleton(beanName):对某个具体 Bean 进入实例化流程。
  7. getBean(beanName):进入 BeanFactory 的统一取 Bean 入口。对于还没创建的 Bean,这里会继续往下触发创建。
  8. doGetBean(...):判断单例缓存里有没有,没有的话就正式创建。
  9. createBean(...):进入 Spring 的标准创建入口。
  10. doCreateBean(...):进入完整的 Bean 创建主线。
  11. createBeanInstance(...):到了这里,才是真正把对象实例创建出来的那一步。

这也解释了一个关键分工:

Spring Boot 负责调度启动流程,真正负责创建 Bean 的核心逻辑仍然在 Spring Framework 的 BeanFactory 体系里。

还有两个细节也值得顺手记住:

  1. createBeanInstance() 对应的是“实例化对象”这一下,不等于整个 Bean 生命周期结束。后面还会继续走 populateBean()initializeBean()registerDisposableBeanIfNecessary()
  2. 不是所有 Bean 都会在 run() 启动阶段创建。非懒加载单例通常会在 refreshContext(context) 期间创建,@Lazy Bean 往往会在第一次使用时再创建,prototype Bean 则通常是每次 getBean() 时创建。

实例化,不等于初始化

实例化很简单,就是“对象被造出来了”。

比如 Spring 通过构造器、工厂方法,或者自动装配构造器,先把 Bean 对象创建出来。这一步在源码里主要对应 createBeanInstance()

这时候你可以把它理解成:房子主体已经盖好了,但里面还什么都没装。

接下来 Spring 会进入属性填充阶段,也就是 populateBean()。这一阶段会把依赖注入进去,比如 @Autowired@Value,或者 XML / @Bean 里配置的属性值。

注意,这一步仍然不叫“初始化完成”。

因为对象虽然有了,依赖也塞进去了,但它还没执行那些“我要开始工作前先做点准备”的逻辑。

初始化,到底初始化了什么?

真正更贴近“初始化”的,是 initializeBean() 这条链路。

它的常规顺序可以理解成 4 步。

1. 先处理一部分 Aware 接口

BeanNameAwareBeanFactoryAwareBeanClassLoaderAware,会在这里被回调。

这一步的意义是:Spring 告诉这个 Bean,“你是谁、你在哪个工厂里、你用的类加载器是什么”。

但别把所有 Aware 都归到这里。

ApplicationContextAwareEnvironmentAware 这一类,通常是 ApplicationContextAwareProcessor 这个 BeanPostProcessor 在初始化前阶段帮你注入的。

2. 执行初始化前置处理

这里会走 BeanPostProcessor#postProcessBeforeInitialization()

这里的关键点是:BeanPostProcessor 是扩展点名称,postProcessBeforeInitialization() 是它在“初始化之前”的那个回调时机。

@PostConstruct 就是由相关的 BeanPostProcessor 在这个回调里触发的。所以从执行顺序上看,@PostConstruct 会发生在 afterPropertiesSet() 和自定义 init-method 之前。

3. 执行真正的初始化方法

这一阶段最常见的有两类:

  1. 实现 InitializingBean,调用 afterPropertiesSet()
  2. 自己定义初始化方法,比如 init-method@Bean(initMethod = "...")

这里还要特别澄清一个容易误解的名字:afterPropertiesSet() 不是“开始设置属性”,而是“属性已经设置完成之后”。

也就是说:

  • populateBean():负责填充属性、完成依赖注入
  • afterPropertiesSet():在属性填充完成之后执行初始化逻辑

两者不是一件事,前者是“把依赖塞进去”,后者是“依赖都齐了,我开始做正式初始化”。

如果一个 Bean 同时配置了多种初始化机制,顺序仍然是:@PostConstruct -> afterPropertiesSet() -> 自定义 init-method

4. 执行初始化后置处理

最后会走 BeanPostProcessor#postProcessAfterInitialization()

很多 AOP 代理,通常就是在这一步之后对外暴露出去的。所以你平时拿到的 Bean,很多时候已经不是最原始的那个对象了,而是包装后的最终版本。

为什么很多文章会把“属性注入”和“初始化”混在一起?

因为从大白话理解上说,大家都觉得“把对象准备好”都算初始化。

这种说法不算全错,但要真正理解 Spring 源码或者回答面试题,最好分清这两个层次:

  • 广义的“初始化前准备”:实例化 + 依赖注入 + 各种容器处理
  • 狭义的“初始化回调”:@PostConstructafterPropertiesSet()init-method

所以更准确的说法应该是:

Bean 的创建过程包含实例化、属性填充和初始化;而初始化只是创建过程里的一个子阶段。

这句话一旦分清,这个知识点你再看就不会糊涂了。

销毁阶段也别背错

当 Bean 初始化完成后,Spring 还会做一件事:如果这个 Bean 需要销毁逻辑,就先把它的销毁回调注册起来。

这对应源码里的 registerDisposableBeanIfNecessary()

注意,这不是“Bean 已经开始销毁了”,只是 Spring 先记账:等容器关闭时,该通知谁、按什么顺序通知。

真正销毁时,常规顺序可以这样理解:

  1. 先执行销毁前处理,比如 @PreDestroy 所在的销毁回调逻辑
  2. 如果实现了 DisposableBean,调用 destroy()
  3. 如果配置了自定义销毁方法,再调用 destroy-methodclose()shutdown()

这里还有一个高频坑:

不是所有 Bean 都会在容器关闭时自动走销毁流程。

尤其是 prototype Bean,Spring 通常只负责“生”,不负责“养到老再送终”。你拿到它之后,后续资源释放很多时候要自己管。

一句话记住 Spring Bean 的生命周期

用一句话概括 Spring Bean 的生命周期:

Spring Bean 的主线不是“new 一个对象就完了”,而是“先实例化,再注入依赖,再初始化,最后在合适的时机销毁”。

再压缩成面试回答版,就是:

  1. 实例化 Bean
  2. 填充属性,完成依赖注入
  3. 执行 Aware 回调
  4. 执行初始化前置处理
  5. 执行 @PostConstructafterPropertiesSet()init-method
  6. 执行初始化后置处理,必要时生成代理
  7. Bean 进入可用状态
  8. 容器关闭时执行销毁回调

总结

真正理解 Spring Bean 生命周期,最关键的是别把下面 3 件事混在一起:

  1. 不要把“属性注入”直接等同于“初始化”
  2. 不要把所有 Aware 接口都归到同一个调用点
  3. 不要漏掉 @PostConstruct@PreDestroyprototype Bean 的销毁差异

理解 Spring Bean 生命周期,真正的重点从来不是死记步骤,而是搞清楚:

对象是什么时候被创建的,什么时候依赖才完整,什么时候才真正能安全工作,什么时候又该被正确清理。

这才是生命周期这个知识点真正有用的地方。

参考资料

Spring 官方文档: docs.spring.io/spring-fram…

Spring 官方文档: docs.spring.io/spring-fram…

Spring Framework 源码: github.com/spring-proj…

Spring Framework 源码: github.com/spring-proj…

Spring Framework 源码: github.com/spring-proj…

欢迎关注公众号 FishTech Notes,一块交流使用心得!