IoC 的加载
1. 通过 BeanDefinitionReader 读取指定的配置文件或注解生成bean的定义信息(得到 BeanDefinition )。
2. 通过 BeanFactoryPostProcessor 即后置增强器,进行修改 BeanDefinition(元数据)。
- 比如能修改 scope 属性(单例 Singleton 或 多例 Prototype )。
- 还可以解析占位符:比如
@Value("${jdbc.url}"),但 Spring 此时只知道有个占位符,不知道${jdbc.url}具体是多少,BeanFactoryPostProcessor可以读取properties文件,找到对应的值。 - 总的来说,它是 Spring IoC 容器启动早期的一个“上帝视角”插件,你可以通过修改
BeanDefinition来改变 Bean 的“命运”(比如改变它的作用域、注入的值等)
3. 得到完整BeanDefinition之后,进行创建对象,而这个就是Bean的生命周期
Bean 的生命周期
Bean 生命周期的大致流程
- 实例化 (
createBeanInstance)↘ - 属性赋值 (
populateBean)———>doCreatBean方法 - 初始化 (
initializeBean)_____↗ - 使用 (??)
- 销毁 (??)
——————————————————————————————————————————————————
具体流程
实例化前置
使用方法: postProcessBeforeInstantiation
可以讲 postProcessBeforeInstantiation 方法想象成一个拦截器。首先,这个方法默认返回的是 null , 但是如果 Spring 在非常特殊的场景( 比如 Prototype 作用域的对象池,或者 AspectJ 的静态编织 ),于是它在这个方法里,利用动态代理技术,生成了一个代理对象, 同时返回这个 Proxy Object 。
注:这个方法在 InstantiationAwareBeanPostProcessor 类中
返回值:
- null(默认也是null):理解为 “放行” ,表示继续创建,接下来执行
doCreateBean方法 - Object :表示我现在有
Proxy Object了,要直接丢出去,不进行接下来的方法了,即跳过 doCreatBean 和 populateBean 方法,进行所谓“短路”。
——————————————————————————————————————————————————
实例化对象
使用方法: createBeanInstance
用 doCreateBean 中的 createBeanInstance 方法,运用反射技术,进行创建实例对象
此时,对象已经通过构造函数 new 出来了(实例化完成),但身上还是“光秃秃”的,属性(依赖)都还是 null。
——————————————————————————————————————————————————
依赖注入的“总指挥”:populateBean
核心方法:populateBean
在 Bean 实例化(new 出来)之后,Spring 就进入了依赖注入阶段。这个阶段的核心入口就是 populateBean。
你可以把它看作是依赖注入的 “总指挥” 。它的主要任务不是亲自干活,而是协调各方资源,最终把属性值填充到 Bean 里。
它做了三件大事:
- 安检(前置检查) :调用
postProcessAfterInstantiation,问问大家“这个对象能注入吗?”(只有返回true才继续)。 - 收集(清单整理) :调用
postProcessProperties,把@Autowired、@Value等注解解析出来,凑齐所有的依赖,整理成一份最终的属性清单(PropertyValues)。 - 赋值(真正干活) :最后,调用
applyPropertyValues,拿着这份清单,通过反射,按照清单内容,把值一个一个真正地塞进 Bean 的字段里。
注意:所谓的“待注入属性”,就是你平时在类里写的
private UserService userService;这种普通的变量字段。这里就是依赖注入真正发生的地方。
——————————————————————————————————————————————————
populateBean 流程第一步:实例化后置(安检口)
核心方法:postProcessAfterInstantiation
这是 populateBean 执行的第一件事。Spring 在这里给我们留了一个 “后悔药” 或者 “安检口” 。此时,对象已经通过构造函数 new 出来了,但身上还是“光秃秃”的(属性为 null)。
返回值决定命运:
-
false:表示 Bean 实例创建好了,但我不希望 Spring 为它进行任何属性填充。- 后果:直接跳过后续所有注入流程。这通常用于极端的性能优化或特殊场景。
-
true(默认):表示 Bean 实例已经创建,让 Spring 继续为它填充属性(依赖注入)。
关于
dependencyCheck的补充(已过时):
虽然源码里还有一个dependencyCheck属性用于判断是否继续,但这属于 Spring 早期(XML 时代)的产物,用于检查属性是否都赋值了。在现代开发(注解 + Java Config)中,这个属性已经过时且极少使用(默认返回为DEPENDENCY_CHECK_NONE )。
——————————————————————————————————————————————————
populateBean流程第二步:属性清单修改(预处理)
核心方法:postProcessProperties
这是 populateBean 执行的第二件事。
注:老版本叫 postProcessPropertyValues,现已废弃,但原理一致。
在属性真正注入给 Bean 之前,Spring 会先整理一份 “属性清单” (PropertyValues)。这份清单里可能已经有一些 XML 配置好的值,或者 @Value 注解的值。
它的作用:
在这个阶段,我们可以对这份清单进行 “修改” 。
- 拦截:检查清单里的值是否合法。
- 增强:这是
@Autowired发挥作用的关键时刻!Spring 会在这里扫描 Bean 里的注解,把需要的依赖(比如UserService)加入到这份清单里。
注意:此时属性还没有真正赋值给 Bean 对象,我们操作的只是“清单”,而不是 Bean 本身。
——————————————————————————————————————————————————
populateBean 流程第三步:真正的赋值(执行注入)
核心方法:applyPropertyValues
这是 populateBean 做的最后一件事。
在前面的步骤中,我们要么拿到了 XML 配置的值,要么通过 @Autowired 整理好了依赖清单(PropertyValues)。但请注意,到这一步为止,这些值还只是停留在“清单”上,并没有真正进入你的 Bean 对象里(比如 userService 字段此时可能还是 null)。
它的作用:
applyPropertyValues 方法会遍历那份整理好的“属性清单”,然后利用 Java 反射机制,把清单里的值一个一个真正地塞进 Bean 对象的对应字段中。
——————————————————————————————————————————————————
给容器属性赋值
使用方法:“一堆 Aware 接口的实现方法”
用来让 Bean 感知环境 :就是说通过一些很nb的操作, 让 Bean 知道自己“身在何处”以及“周围有什么资源”。
比如实现了以下功能:
- 获取Bean的名字
- 拿到容器,然后手动调用
context.getBean("xxx")来获取需要的服务 - 等等 ...
——————————————————————————————————————————————————
初始化前置
使用方法 postProcessBeforeInitialization()
即在初始化每一个 Bean 之前就会调用的方法
在这个阶段:
- 修改 Bean 的属性值(查漏补缺)。
- 对 Bean 进行包装或增强。
注意:此时传入的 bean 参数是原始对象,还不是代理对象。
——————————————————————————————————————————————————
执行初始化方法
使用方法
@PostConstruct注解- 通常写在依赖注入完成后的任意方法上,用于执行初始化逻辑
- 特点:优先级最高,代码侵入性最小,最推荐使用。
- 实现
InitializingBean接口- 自动调用,允许重写
- 特点:优先级第二,需要实现接口,耦合了 Spring 代码。
- 配置文件( xml 或 JavaConfig )中写
init-method- bean 标签的 init-method 属性上 或 @Bean(initMethod="...") 注解属性上
- 特点:优先级最低,配置灵活,完全解耦。
初始化的顺序:
@PostConstructInitializingBean.afterPropertiesSet()init-method
——————————————————————————————————————————————————
初始化后置
使用方法 postProcessAfterInitialization()
同上
即在初始化每一个 Bean 之后就会调用的方法
注意: Spring 默认的 AOP(比如 @Transactional)通常是在这里生成的
此时,我们的 “Bean” 真正成熟了
——————————————————————————————————————————————————
使用 Bean
最熟悉的地方,开心地使用它吧!
——————————————————————————————————————————————————
销毁 Bean
多例:
Spring 不进行销毁,不调用任何方法,通过 JVM 的垃圾回收器( GC )进行销毁
单例:
@PreDestroy注解
- 特点:优先级最高,最推荐使用。
- 先调用
DisposableBean.destroy(),自动调用,允许重写
- 特点:优先级第二,需要实现接口,耦合了 Spring 代码。
- **再调用
destroyMethod,手动调用
- 特点:优先级最低,配置灵活,完全解耦。
- 写法:
- XML 配置:
<bean destroy-method="close" ... /> - Java 配置:
@Bean(destroyMethod="close")
- XML 配置:
销毁的顺序
@PreDestroyDisposableBean.destroy()destroy-method
——————————————————————————————————————————————————
表格:Spring Bean 的 生命周期
| 阶段 | 关键方法/接口 | 核心作用 |
|---|---|---|
| 0. 容器启动/蓝图修改 | BeanFactoryPostProcessor | 修改蓝图:在对象创建前,修改 BeanDefinition(如解析 ${} 占位符)。 |
| 1. 实例化前置 | postProcessBeforeInstantiation | AOP/短路:如果返回代理对象,后续步骤(实例化、注入等)全跳过。 |
| 2. 实例化 | createBeanInstance (反射) | 生孩子:利用反射 new 出一个空的原始对象。 |
| 3. 实例化后置 | postProcessAfterInstantiation | 安检:返回 false 则跳过属性注入;返回 true 继续。 |
| 4. 属性清单修改 | postProcessProperties | 改清单:在注入前,修改或增强即将注入的属性值(PropertyValues)。 |
| 5. 属性赋值 | applyPropertyValues (在 populateBean 内) | 填属性:执行依赖注入(@Autowired),把值塞进对象字段。 |
| 6. 感知环境 | Aware 接口们 | 认祖归宗:让 Bean 拿到容器引用(BeanName, BeanFactory)。 |
| 7. 初始化前置 | postProcessBeforeInitialization | 预处理:在初始化方法执行前,对 Bean 进行最后的加工。 |
| 8. 初始化 | @PostConstruct InitializingBean init-method | 成年礼:执行自定义初始化逻辑(按此顺序执行)。 |
| 9. 初始化后置 | postProcessAfterInitialization | 包装/代理:AOP 的核心位置,将原始对象包装成代理对象。 |
| 10. 使用 | 用地好开心!! | 干活:业务调用,Bean 处于就绪状态。 |
| 11. 销毁 | @PreDestroy DisposableBean destroy-method | 入土:释放资源(仅单例,需手动关闭容器)。 |
三级缓存解决循环依赖的深入原理
接下来我会通过“用于解决循环依赖的三级缓存行为”进行知识巩固和三级缓存理解。
我将用我上期最后的 “三级缓存的行为模拟” 部分进行拆分描述
(需要请查看链接:juejin.cn/post/762175… )
三级缓存的名称和简单作用:
- 一级缓存 :
singletonObjects- 存放 :成品 Bean
- 作用 :完全初始化
Bean 已经实例化、注入了属性、执行了初始化方法。这是我们要用的最终对象。
- 二级缓存 :
earlySingletonObjects- 存放 :半成品
- 作用 :已实例化,未初始化
Bean 已经new出来了,属性也注入了,但还没执行@PostConstruct等方法。
- 三级缓存 :
singletonFactories- 存放 :工厂 (ObjectFactory)
- 作用 :实例化刚结束
这里存的是一个“工厂对象”,它的作用是生产早期引用(主要是为了解决 AOP 代理问题)
三级缓存的行为详述:
首先假设 ServiceA 和 ServiceB 互相依赖,且两者都开启了 AOP(例如都加了 @Transactional)
1. IoC 容器启动
- 读取:通过
BeanDefinitionReader读取配置文件,解析出 A 和 B 的BeanDefinition(元数据)。 - 修改:通过
BeanFactoryPostProcessor(容器级后置处理器),对 BeanDefinition 进行修改(例如解析${}占位符)。 - 结果:此时容器中有了 A 和 B 的完整定义(完整的
BeanDifinition),但还没有创建任何对象。
此时 A 和 B 的状态:
- A :完整的
BeanDifinitionA- B :完整的
BeanDifinitionB
2. A 开始创建(实例化)
Spring 尝试实例化 A 。
- 实例化前置__检查:调用
postProcessBeforeInstantiation。因为 A 的 AOP 代理是在后面生成的,所以这里返回 null,表示“放行”,不进行短路。若特殊情况(比如外部池等等,则直接短路到初始化后置方法postProcessAfterInitialization,即 直接跳过实例化、属性注入、初始化前置和初始化) - 创建对象__反射实例化:执行
createBeanInstance,通过反射new ServiceA()。此时生成了一个原始对象(TargetA),它的属性目前全为 null。
此时 A 和 B 的状态:
- A :原始对象(
TargetA)- B :完整的
BeanDifinitionB
3. A 的工厂进入三级缓存
在属性注入( 即执行 populateBean 流程 和 容器属性赋值,前者包含: postProcessInstantiation 、 postProcessProperties 、 applyPropertyValues ,后者为 “一堆 Aware 接口的实现方法” )之前,Spring 将 A 的工厂( ObjectFactory A )放入三级缓存 ( singletonFactories )。
关键点:这个工厂里包含了一个逻辑:如果 A 需要 AOP,当别人调用
getObject()时,它会提前创建代理对象(ProxyA)。
此时 A 和 B 的状态:
- A :原始对象(
TargetA) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :完整的
BeanDifinitionB
4. A 尝试注入 B
A 开始属性赋值( populateBean 流程),发现需要 B 。
*注意!:此时 属性赋值( populateBean 流程)将暂停,并且 Aware感知方法是在populateBean流程之后进行,所以还没执行Aware
此时 A 和 B 的状态:
- A :原始对象(
TargetA) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :完整的
BeanDifinitionB
5. 寻找 B(失败)
Spring 在一级缓存( singletonObjects )、二级缓存( earlySingletonObjects )中查找 ServiceB,没找到。
于是: 暂停 A 的属性填充( populateBean 流程),转而去实例化创建 B 。
此时 A 和 B 的状态:
- A :原始对象(
TargetA)(半填充) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :完整的
BeanDifinitionB
6. B 开始创建(实例化)
Spring 尝试实例化创建 B 。通过 createBeanInstance 反射出 new ServiceB() ,生成原始对象(TargetB)。
此时 A 和 B 的状态:
- A :原始对象(
TargetA)(半填充) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :原始对象(
TargetB)
7. B 的工厂入三级缓存
同样地,Spring 将 B 的工厂( ObjectFactory B )放入三级缓存( singletonFactories )。
此时 A 和 B 的状态:
- A :原始对象(
TargetA) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :原始对象(
TargetB)和 三级缓存(singletonFactories)中的 B 的工厂(ObjectFactoryB )
8. B 尝试注入 A(关键破局点)
B 开始属性赋值( populateBean 流程 ),发现需要 A 。
此时 B 暂停属性注入( populateBean 流程 ),同样Aware还没执行
- 查一级缓存(
singletonObjects)?无。 - 查二级缓存(
earlySingletonObjects)?无。 - 查三级缓存(
singletonFactories)?找到了 A 的工厂!
此时 A 和 B 的状态:
- A :原始对象(
TargetA)(半填充) 和 三级缓存(singletonFactories)中的 A 的工厂(ObjectFactoryA )- B :原始对象(
TargetB)和 三级缓存(singletonFactories)中的 B 的工厂(ObjectFactoryB )
9. A 的工厂( ObjectFactory A )生成代理对象 ProxyA
Spring 调用 A 的工厂( ObjectFactory A ) 进行 getObject()。
- AOP 介入:工厂发现 A 需要 AOP,于是提前执行了初始化后置逻辑,生成了 ProxyA(代理对象)。
- 缓存转移:Spring 将 ProxyA 放入二级缓存 (
earlySingletonObjects),并立即从三级缓存(singletonFactories)中移除 A 的工厂(ObjectFactoryA )。
此时 A 和 B 的状态:
- A :存放在二级缓存 (
earlySingletonObjects)中的ProxyA(半填充)- B :原始对象(
TargetB)(半注入) 和 三级缓存(singletonFactories)中的 B 的工厂(ObjectFactoryB )
10. B 注入 ProxyA 并完成
- B 拿到了 ProxyA ,注入到自己的属性中。
- B 继续执行后续的
Aware方法和 初始化方法(@PostConstruct等 ),最终生成 B 的代理对象ProxyB。 ProxyB被放入 一级缓存 (singletonObjects),同时清理 B 在二、三级缓存中的记录。
此时 A 和 B 的状态:
- A :存放在二级缓存 (
earlySingletonObjects)中的ProxyA(半注入)- B :存放在一级缓存 (
singletonObjects)中的ProxyB
11. A 注入 ProxyB 并完成
- B 创建完毕,Spring 回到 ServiceA 的创建流程。
- ProxyA 从一级缓存中拿到了完整的 ProxyB,注入到自己的属性中。
- ProxyA 继续执行
Aware方法,进行环境感知 - ProxyA 继续执行初始化方法(
@PostConstruct等)。
最终,ProxyA(注意:这里复用的是二级缓存里的那个代理对象)被放入一级缓存 ( singletonObjects ),同时清理 ProxyA 在二级缓存 (earlySingletonObjects) 中的记录。
此时 A 和 B 的状态:
- A :存放在一级缓存 (
singletonObjects)中的ProxyA- B :存放在一级缓存 (
singletonObjects)中的ProxyB
12. A 和 B 继续执行 SpringBean 的生命周期,使用 Bean 、销毁 Bean 。
- 到此结束了这两个互相纠缠的 A 和 B 的一生
本文结束
如果这篇文章帮你理清了思路,我也感到很开心。
这也同时是我的学习笔记,能帮到别人我不胜感谢,若哪里出错希望指出,同样不胜感激!
以上,@Aroaku