Spring有代理对象的循环依赖时,如何确保代理对象能够正确持有原始对象的所有属性赋值结果?
有代理对象的循环依赖时,如何确保代理对象能够正确持有原始对象的所有属性赋值结果?
- 有代理对象的循环依赖时,如何确保代理对象能够正确持有原始对象的所有属性赋值结果?
- 一、问题背景
- 二、解答核心
- 三、Spring 代理对象与属性赋值的核心机制
- 1. 代理对象的本质
- 2. 属性赋值的对象
- 3. 三级缓存的作用
- 4. 关键点:代理对象如何“继承”属性
- 四、详细流程分析
- 1. 阶段一:创建 Bean A
- 2. 阶段二:创建 Bean B
- 3. 阶段三:完成 Bean A 的创建
- 4. 阶段四:代理对象访问属性
- 五、为什么代理对象能正确获取属性值?
- 1. CGLIB 代理的实现
- 2. JDK 动态代理的实现
- 3. Spring 的缓存管理
源码见:mini-spring
有代理对象的循环依赖时,如何确保代理对象能够正确持有原始对象的所有属性赋值结果?
一、问题背景
在 Spring 解决有代理对象的循环依赖时,核心是通过三级缓存(singletonFactories
)和 SmartInstantiationAwareBeanPostProcessor
的 getEarlyBeanReference
方法提前生成代理对象(如 AProxy
)。但是,代理对象生成时,原始对象(如 A
)可能尚未完成属性注入(例如 A
的属性 b
尚未赋值)。你的疑问是:
- 提前生成的代理对象
AProxy
是基于“半成品”原始对象A
创建的,此时A
的属性(如b
)尚未赋值。 - 在后续
A
的创建过程中,属性注入是针对原始对象A
完成的。 - 最终容器中的
AProxy
如何确保其属性(如b
)是正确赋值的?
二、解答核心
Spring 的代理对象(如通过 CGLIB 或 JDK 动态代理生成的 AProxy
)通常是对原始对象 A
的包装,而不是一个独立的对象。代理对象通过持有原始对象的引用(称为 target),在调用时将请求委托给原始对象。因此,对原始对象 A
的属性赋值会直接反映到代理对象 AProxy
中,因为代理对象内部的逻辑依赖于原始对象的状态。
以下从机制和流程两个方面详细解答。
三、Spring 代理对象与属性赋值的核心机制
1. 代理对象的本质
- JDK 动态代理:基于接口,代理对象实现与目标对象相同的接口,内部通过 InvocationHandler 持有原始对象的引用。
- CGLIB 代理:基于子类,代理对象是原始对象的子类,继承了原始对象的字段,并通过 super 访问或直接持有原始对象的引用。
- 在 Spring 的循环依赖场景中,代理对象(AProxy)通常通过 CGLIB 生成,代理对象会直接包含原始对象 A 的字段,或者通过某种方式(如委托模式)访问原始对象的字段。
2. 属性赋值的对象
- 在 Spring 的 Bean 创建过程中,属性注入(如 @Autowired 或 Setter 注入)是直接针对原始对象 A 完成的。
- 代理对象 AProxy 本身并不直接参与属性注入,而是通过引用原始对象 A 来访问其属性值。
3. 三级缓存的作用
- 三级缓存(singletonFactories)存储 ObjectFactory,通过 getEarlyBeanReference 提前生成代理对象 AProxy。
- 二级缓存(earlySingletonObjects)保存提前暴露的代理对象 AProxy,供依赖方(如 B)使用。
- 一级缓存(singletonObjects)最终存储完全初始化的代理对象 AProxy。
4. 关键点:代理对象如何“继承”属性
- CGLIB 代理:AProxy 是 A 的子类,继承了 A 的所有字段。属性注入直接修改 A 的字段,而 AProxy 作为子类实例,天然持有这些字段值。
- JDK 动态代理:AProxy 通过 InvocationHandler 持有原始对象 A 的引用,任何对字段的访问(如 getB())都会委托给原始对象 A,从而获取注入的属性值。
- 因此,无论哪种代理方式,属性注入的结果都会反映在代理对象的行为中,因为代理对象最终依赖原始对象的状态。
四、详细流程分析
让我们以文章中的例子(A 依赖 B,B 依赖 A,且 A 被 AOP 代理)来梳理整个流程,重点说明属性赋值如何传递到代理对象。
1. 阶段一:创建 Bean A
- 步骤:
- Spring 调用 A 的构造方法,生成原始对象 A(尚未进行属性注入)。
- 将 A 封装为 ObjectFactory,加入三级缓存(singletonFactories),其中 ObjectFactory.getObject() 调用 getEarlyBeanReference。
- A 需要注入属性 b(类型为 B),触发 B 的创建。
- 状态:
- 此时,原始对象 A 的字段 b 为 null。
- 三级缓存中存储了 A 的 ObjectFactory。
2. 阶段二:创建 Bean B
- 步骤:
- Spring 创建 B 的原始对象,加入三级缓存。
- B 需要注入 A(字段 a),Spring 调用 getSingleton(“A”):
- 一级缓存(singletonObjects)和二级缓存(earlySingletonObjects)中没有 A。
- 从三级缓存(singletonFactories)获取 A 的 ObjectFactory,调用 getObject()。
- getEarlyBeanReference 被触发,SmartInstantiationAwareBeanPostProcessor(如 AbstractAdvisorAutoProxyCreator)生成 A 的代理对象 AProxy。
- AProxy 加入二级缓存(earlySingletonObjects),从三级缓存移除。
- B 的字段 a 被注入 AProxy,B 完成创建,放入一级缓存。
- 状态:
- B 的字段 a 持有 AProxy。
- 原始对象 A 的字段 b 仍为 null,因为 A 的属性注入尚未完成。
- 二级缓存中存储了 AProxy。
3. 阶段三:完成 Bean A 的创建
- 步骤:
- 返回到 A 的创建流程,Spring 对原始对象 A 进行属性注入:
- 调用 Setter 方法(如 setB(B b))或直接通过反射设置字段 b。
- 从一级缓存获取已创建的 B,注入到原始对象 A 的字段 b。
- 原始对象 A 的字段 b 现在持有 B 的引用。
- Spring 继续执行 A 的初始化,调用 BeanPostProcessor 的 postProcessAfterInitialization:
- 检查 earlyProxyReferences,发现 A 已提前生成代理(AProxy),因此不重复生成。
- Spring 从二级缓存获取 AProxy,将其放入一级缓存(singletonObjects)。
- 返回到 A 的创建流程,Spring 对原始对象 A 进行属性注入:
- 状态:
- 原始对象 A 的字段 b 已注入 B。
- AProxy 作为 A 的子类(CGLIB)或通过 InvocationHandler 引用 A(JDK 代理),可以访问 A 的字段 b。
- 一级缓存中的 AProxy 是最终暴露的对象,B 中的字段 a 也指向 AProxy。
4. 阶段四:代理对象访问属性
- 访问机制:
- 当通过 AProxy 调用方法(如 getB())时:
- CGLIB 代理:AProxy 继承了 A 的字段,getB() 直接返回 A 的字段 b(已注入 B)。
- JDK 动态代理:AProxy 的 InvocationHandler 将 getB() 调用委托给原始对象 A,返回字段 b 的值。
- 因此,AProxy.getB() 返回注入的 B,属性赋值结果成功反映在代理对象上。
- 当通过 AProxy 调用方法(如 getB())时:
- 结果:
- 容器中的 A 是 AProxy,B 中的 a 也是 AProxy,引用一致。
- AProxy 能够正确访问原始对象 A 的属性值(如 b),代理逻辑(如 AOP 切面)也能正常触发。
五、为什么代理对象能正确获取属性值?
1. CGLIB 代理的实现
- 机制:
- CGLIB 生成的
AProxy
是A
的子类,继承了A
的所有字段(如private B b
)。 - 属性注入直接修改原始对象
A
的字段b
,而AProxy
作为子类实例,共享这些字段值。 - 调用
AProxy.getB()
时,直接访问继承的字段b
,返回注入的B
。
- CGLIB 生成的
- 代码示例(简化):
class A {private B b;public B getB() { return b; }public void setB(B b) { this.b = b; } } class AProxy extends A {// 继承了 A 的字段 b@Overridepublic B getB() {// AOP 拦截逻辑return super.getB(); // 访问父类 A 的字段 b} }
- 结论:
- 属性注入修改了原始对象
A
的字段,而AProxy
继承了这些字段,因此能直接访问注入的属性值。
- 属性注入修改了原始对象
2. JDK 动态代理的实现
- 机制:
- JDK 动态代理生成
AProxy
,实现与A
相同的接口(如IA
),并通过InvocationHandler
持有原始对象A
的引用。 - 属性注入修改原始对象
A
的字段b
。 - 调用
AProxy.getB()
时,InvocationHandler
将调用转发到原始对象A
,返回字段b
的值。
- JDK 动态代理生成
- 代码示例(简化):
interface IA {B getB(); } class A implements IA {private B b;public B getB() { return b; }public void setB(B b) { this.b = b; } } class AProxyHandler implements InvocationHandler {private A target;AProxyHandler(A target) { this.target = target; }public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// AOP 拦截逻辑return method.invoke(target, args); // 转发到原始对象 A} }
- 结论:
- 属性注入修改了原始对象
A
的字段,AProxy
通过InvocationHandler
访问A
的字段,因此能获取注入的属性值。
- 属性注入修改了原始对象
3. Spring 的缓存管理
- Spring 确保代理对象
AProxy
是最终暴露的对象(一级缓存和B
的字段a
都引用AProxy
)。 - 属性注入发生在原始对象
A
上,但代理对象通过继承(CGLIB)或委托(JDK 代理)访问这些属性值,确保行为一致。