当前位置: 首页 > news >正文

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)和 SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference 方法提前生成代理对象(如 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 的字段 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,属性赋值结果成功反映在代理对象上。
  • 结果
    • 容器中的 A 是 AProxy,B 中的 a 也是 AProxy,引用一致。
    • AProxy 能够正确访问原始对象 A 的属性值(如 b),代理逻辑(如 AOP 切面)也能正常触发。

五、为什么代理对象能正确获取属性值?

1. CGLIB 代理的实现

  • 机制
    • CGLIB 生成的 AProxyA 的子类,继承了 A 的所有字段(如 private B b)。
    • 属性注入直接修改原始对象 A 的字段 b,而 AProxy 作为子类实例,共享这些字段值。
    • 调用 AProxy.getB() 时,直接访问继承的字段 b,返回注入的 B
  • 代码示例(简化):
    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 的值。
  • 代码示例(简化):
    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 代理)访问这些属性值,确保行为一致。
http://www.xdnf.cn/news/1053253.html

相关文章:

  • 234. 回文链表
  • SQL 增删改查 —— 笔记篇
  • 面向对象设计原则
  • 深度学习——基于卷积神经网络实现食物图像分类【3】(保存最优模型)
  • React19源码系列之Hooks(useState)
  • Linux中的连接符
  • 谐波减速器 MINIF8 和 MINIF11 的区别
  • 事务传播机制分析:用户注册场景分析
  • 日语学习-日语知识点小记-进阶-JLPT-真题训练-N2阶段(2):2020年12月2018年7月
  • leetcode148-排序链表
  • 《Java编程思想》读书笔记:第十二章
  • 01 人工智能起源与诞生
  • 在 Windows 上使用 Docker Desktop 快速搭建本地 Kubernetes 环境(附详细部署教程)
  • 第六章、6.2 ESP32低功耗模式详解:深度睡眠与轻度睡眠实战指南
  • Java泛型深度解析
  • MySQL-DCL数据控制语言详解
  • 深度学习打卡1
  • 【计算机网络】网络层IP协议与子网划分详解:从主机通信到网络设计的底层逻辑
  • Windows平台轻量级图片处理工具实测:功能与体验分享
  • 「Matplotlib 入门指南」 Python 数据可视化分析【数据分析全栈攻略:爬虫+处理+可视化+报告】
  • 前端面试九之Vue Router
  • 【Qt 中的元对象系统(Meta-Object System)】
  • 洛谷 P3865 【模板】ST 表 RMQ 问题
  • 基于OpenManus的跨平台部署方案及远程访问安全机制
  • 李宏毅2025《机器学习》第二讲-深度解构AI Agent:让大型语言模型学会记忆、使用工具与制定计划
  • LeetCode 2389.和有限的最长子序列
  • libuv 框架
  • RabbitMQ死信队列
  • 【测开面试题】八股文总结
  • 快速上手文本向量模型 Sentence-Transformers