一、什么是 Bean 生命周期?
简单来说,Bean 生命周期就是:
一个 Bean 从创建 → 初始化 → 使用 → 销毁的完整过程
换句话说:
- 对象什么时候创建?
- 依赖什么时候注入?
- 初始化逻辑什么时候执行?
- 什么时候被销毁?
全部由 Spring 控制,而不是开发者手动管理。
二、Bean 生命周期整体流程
一个完整的 Bean 生命周期可以抽象为:
实例化 → 属性填充 → 初始化 → 使用 → 销毁
但如果展开来看,其实远比这复杂:
1. 实例化 Bean
Spring 通过反射创建 Bean 对象(调用构造方法),此时只是"空对象",还没有属性值(Bean 中的成员变量(字段)所对应的值)。
关键点:只执行构造方法,依赖还没注入
2. 属性填充(依赖注入)
Spring 根据配置(XML / 注解如 @Autowired)为 Bean 注入依赖属性。
关键点:完成对象之间的依赖关系绑定
可能会存在循环依赖处理,下来会有一篇专门讲如何解决
3. Aware 接口回调
如果 Bean 实现了 BeanNameAware、BeanFactoryAware、ApplicationContextAware 等接口, Spring 会回调这些方法,把容器相关信息注入进去。
关键点:让 Bean "感知" Spring 容器(拿到 BeanName、容器对象等)
4. BeanPostProcessor 前置处理
所有注册的 BeanPostProcessor 会在初始化前执行 postProcessBeforeInitialization() 方法,对 Bean 进行增强或修改。
关键点:可以对 Bean 做"初始化前加工"(例如修改属性)
5. 初始化(InitializingBean / init-method)
执行 Bean 的初始化逻辑,比如:
afterPropertiesSet()(实现 InitializingBean)- 自定义
init-method
关键点:开发者定义的"初始化完成后要做的事"
6. BeanPostProcessor 后置处理(AOP在这里)
执行 postProcessAfterInitialization() 方法,这一步常用于生成代理对象(比如 AOP)。
关键点:
- AOP 动态代理在这里产生
- 返回的可能已经不是原始 Bean,而是代理对象
7. Bean 就绪可用
Bean 完成所有处理,进入容器缓存(单例池),可以被应用程序正常使用。
关键点:
- 从这里开始,你
getBean()拿到的就是最终对象(可能是代理对象)
8. 容器关闭 → 销毁
当容器关闭时,执行 Bean 的销毁方法:
destroy()(实现 DisposableBean)- 自定义
destroy-method
关键点:释放资源(连接、线程、文件等)
三、核心流程详解
1. 实例化 Bean
Spring 在创建 Bean 时,首先会通过反射实例化对象,其核心逻辑位于 createBeanInstance 方法中:
Object bean = beanClass.newInstance();
更底层:
Constructor<?> constructor = clazz.getDeclaredConstructor();
bean = constructor.newInstance();
此时:
- 只是一个"空对象"
- 属性还没有注入
- 依赖关系还没建立
这一阶段的本质,仅仅是在 JVM 堆中创建了一个对象实例。此时的 Bean 还没有任何依赖关系,属性值也全部为默认值,可以理解为一个"空壳对象"。Spring 并不会简单停留在这里,而是在实例化之后立即执行一个非常关键的操作------提前暴露 Bean 引用:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
这一步的作用是将当前 Bean 的"早期引用"放入三级缓存中,从而为后续可能发生的循环依赖提供解决基础。
2. 属性填充(依赖注入)
在实例化完成之后,Spring 会进入依赖注入阶段:
populateBean(beanName, mbd, instanceWrapper);
在这个过程中,Spring 会解析 Bean 中的依赖(如 @Autowired、@Resource),并通过 resolveDependency 方法递归查找或创建依赖对象,最终通过反射完成注入:
Object dependency = resolveDependency(...);
field.set(bean, dependency);
关键点:
- 这里会触发 依赖查找
- 循环依赖问题正是发生在这里!我们可以发现, 循环依赖产生的根本原因在于:Bean 之间的依赖关系是在初始化完成之前建立的。
3. Aware 接口回调(容器能力注入)
当依赖注入完成后,Spring 会执行一系列 Aware 接口回调,其核心逻辑位于:
invokeAwareMethods(beanName, bean);
Spring 会判断 Bean 是否实现了 BeanNameAware、BeanFactoryAware、ApplicationContextAware 等接口,并调用对应方法,例如:
((BeanNameAware) bean).setBeanName(beanName);
这一阶段的设计目的,是让 Bean 可以"感知"容器的存在,从而具备获取上下文信息的能力。
一句话总结:开发者可以主动从 Spring 容器里"拿东西",而不是等容器注入
例如,开发者可以通过 ApplicationContext 手动获取其他 Bean
ApplicationContext context = ...;
UserService userService = context.getBean("userService");
从设计角度来看,这是一种典型的"控制反转中的反向注入"。
4. 初始化前处理(BeanPostProcessor 前置增强)
在正式初始化之前,Spring 会执行 BeanPostProcessor 的前置方法,其源码如下:
applyBeanPostProcessorsBeforeInitialization(bean, beanName);
底层实现是遍历所有 BeanPostProcessor:
for (BeanPostProcessor processor : processors) {
bean = processor.postProcessBeforeInitialization(bean, beanName);
}
BeanPostProcessor 的前置处理方法是在初始化方法执行之前调用的,主要用于对 Bean 进行统一的增强、修改或预处理,属于 Spring 提供的扩展机制;而 @PostConstruct 等初始化方法则是开发者定义的,用于完成 Bean 自身的初始化逻辑。两者的主要区别在于职责不同,一个偏框架扩展,一个偏业务初始化。
5. 初始化阶段(Bean 完整构建)
在前置处理完成后,Spring 会进入真正的初始化阶段,其核心方法为:
invokeInitMethods(beanName, bean, mbd);
Spring 支持多种初始化方式,例如实现 InitializingBean 接口:
afterPropertiesSet();
或者通过配置 init-method:
<bean init-method="init"/>
BeanPostProcessor 前置处理主要用于对 Bean 进行统一的增强或修改,属于框架级扩展机制;而初始化阶段(如 @PostConstruct 或 afterPropertiesSet)则由开发者定义,用于完成 Bean 的业务初始化逻辑,使其具备实际可用性。两者的区别在于,前者侧重于"加工 Bean",后者侧重于"使用 Bean"。
这一阶段的本质,是对 Bean 做最后的"加工处理",例如参数校验、资源加载、连接初始化等。可以认为,Bean 在这里才真正具备"业务可用性"。
6. 初始化后处理(AOP 代理生成关键阶段🔥)
初始化完成后,Spring 会再次执行 BeanPostProcessor,但这一次是后置处理:
applyBeanPostProcessorsAfterInitialization(bean, beanName);
其核心逻辑通常表现为:
return wrapIfNecessary(bean);
这一阶段是 Spring AOP 实现的关键入口。如果 Bean 满足切面条件(如被 @Transactional、@Aspect 等增强),Spring 会在这里为其创建代理对象(JDK 动态代理或 CGLIB)。因此,最终放入容器中的对象,很可能已经不是原始对象,而是一个代理对象。这一点在理解循环依赖时尤为重要,因为"提前暴露"的对象必须与最终对象保持一致,否则会导致代理失效问题。
总结:在 Spring 中,如果 Bean 被 AOP 增强,那么容器中最终存储的并不是原始对象,而是其代理对象,BeanName 不发生变化。开发者通过依赖注入或 getBean 获取到的也是该代理对象。原始对象只在 Bean 创建过程中短暂存在,不会被放入一级缓存,也不会被直接使用。
7. Bean 就绪(进入容器,可被使用)
当上述所有步骤完成后,Bean 会被放入单例缓存中(singletonObjects),正式成为容器中的一个可用组件。此时 Bean 已经完成了依赖注入、初始化以及可能的 AOP 增强,可以被其他 Bean 或业务代码正常获取:
context.getBean(A.class);
从生命周期角度来看,这一阶段是 Bean 存在时间最长、最稳定的阶段。
8. Bean 销毁(生命周期终点)
当 Spring 容器关闭时(例如调用 context.close()),会触发 Bean 的销毁流程。其核心逻辑包括:
DisposableBean.destroy();
以及:
@PreDestroy
或:
<bean destroy-method="destroy"/>
这一阶段主要用于释放资源,例如关闭数据库连接、线程池、网络连接等。从设计角度来看,这是对资源管理的一种"兜底机制",确保系统优雅关闭。
四、总结
从整体来看,Spring 中的 Bean 生命周期本质上是一个"逐步加工对象"的过程。
从源码执行顺序来看,这一过程可以概括为:实例化 → 提前暴露 → 依赖注入 → 初始化 → AOP增强 → 使用 → 销毁。其中一个非常关键的细节在于:依赖注入发生在初始化之前,而 Bean 在实例化之后就已经被提前暴露。这意味着,一个 Bean 在尚未完全构建完成时,就有可能被其他 Bean 所引用。
正是这一设计,引出了一个经典且不可避免的问题:如果两个 Bean 之间存在相互依赖关系(如 A 依赖 B,B 又依赖 A),那么在依赖注入阶段就会出现"对象尚未完成初始化却被再次请求"的情况。这种现象就是所谓的循环依赖问题。
那么,Spring 是如何在允许这种"半成品对象被引用"的前提下,仍然保证系统正常运行的?又是如何避免无限递归创建的?这正是下一步需要深入分析的核心------基于三级缓存的循环依赖解决机制。