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

Spring是如何实现无代理对象的循环依赖

无代理对象的循环依赖

    • 什么是循环依赖
    • 解决方案
      • 实现方式
      • 测试验证
    • 引入代理对象的影响
      • 创建代理对象
      • 问题分析

源码见:mini-spring

在这里插入图片描述

什么是循环依赖

循环依赖是指在对象创建过程中,两个或多个对象相互依赖,导致创建过程陷入死循环。以下通过一个简单的例子来说明:

public class A {  @Autowired  private B b;  public void func() {}  public B getB() {  return b;  }  public void setB(B b) {  this.b = b;  }  
}
public class B {  @Autowired  private A a;  public A getA() {  return a;  }  public void setA(A a) {  this.a = a;  }  
}

在上述代码中,类 A 依赖于 B(通过属性 b),而类 B 又依赖于 A(通过属性 a)。如果不加以处理,在创建 A 时会尝试注入 B,创建 B 时又需要注入 A,从而形成死循环,导致程序无法正常运行。


解决方案

对于没有代理对象的循环依赖问题,Spring 提供了一种简单有效的解决方案:提前暴露 Bean。核心思想是在 Bean 实例化完成后(但尚未完成属性注入),将其加入缓存,从而避免在属性注入阶段因循环依赖而导致的死循环。

实现方式

在 Spring 的 DefaultSingletonBeanRegistry 类中,引入二级缓存 earlySingletonObjects,用于存储提前暴露的 Bean 实例。虽然从实现角度看,将其放入一级缓存也可以解决问题,但 Spring 使用二级缓存是为了与已完全初始化的 Bean(存储在一级缓存中)进行区分。

// 二级缓存,保存实例化后的 Bean  
protected Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

接下来,修改单例 Bean 的获取逻辑。在 getSingletonBean 方法中,首先从一级缓存 singletonObjects 中查找 Bean,若未找到,则尝试从二级缓存 earlySingletonObjects 中获取:

@Override  
public Object getSingletonBean(String beanName) {  Object singletonObject = singletonObjects.get(beanName);  if (singletonObject == null) {  singletonObject = earlySingletonObjects.get(beanName);  }  return singletonObject;  
}

通过上述修改,一个基本的循环依赖解决方案即告完成。

测试验证

以下是测试代码,用于验证循环依赖是否被成功解决:

@Test  
public void testCircularReference() {  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-without-proxy-bean.xml");  A a = applicationContext.getBean("a", A.class);  B b = applicationContext.getBean("b", B.class);  Assert.assertEquals(a.getB(), b);  
}

在创建 A 对象时,Spring 会在实例化完成后将其加入二级缓存 earlySingletonObjects。随后在注入属性时,需要创建 B 对象,而 B 依赖于 A。此时,Spring 直接从二级缓存中获取 A 实例,完成 B 的创建,并将 B 注入到 A 中,从而成功打破循环依赖。


引入代理对象的影响

如果 Bean 被代理(例如通过 AOP 实现),上述解决方案可能会失效。下面通过一个示例分析问题所在。

创建代理对象

假设对 A 对象应用代理,添加一个前置通知(Before Advice):

@Component  
public class ABefpreAdvice implements MethodBeforeAdvice {  @Override  public void before(Method method, Object[] args, Object target) throws Throwable {  System.out.println("before");  }  
}

Spring 的 XML 配置如下,其中 A 被配置为通过 AOP 代理:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd">  <bean id="b" class="org.qlspringframework.test.bean.B">  <property name="a" ref="a"/>  </bean>  <!-- A 被代理 -->  <bean id="a" class="org.qlspringframework.test.bean.A">  <property name="b" ref="b"/>  </bean>  <bean class="org.qlspringframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>  <bean id="pointcutAdvisor" class="org.qlspringframework.aop.aspectj.AspectJExpressionPointcutAdvisor">  <property name="expression" value="execution(* org.qlspringframework.test.bean.A.func(..))"/>  <property name="advice" ref="methodInterceptor"/>  </bean>  <bean id="methodInterceptor" class="org.qlspringframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">  <property name="advice" ref="beforeAdvice"/>  </bean>  <bean id="beforeAdvice" class="org.qlspringframework.test.common.ABefpreAdvice"/>  
</beans>

问题分析

在引入代理对象后,测试结果会发生变化。B 中注入的 A 实例是原始对象(实例化后但未完成初始化的对象),而从 Spring 容器中最终获取的 A 是代理对象,二者不再是同一个对象。这是因为 Spring 的二级缓存机制保存的是未代理的 A 实例,而代理对象是在后续阶段生成的。

因此,Assert.assertEquals(a.getB(), b) 可能失败,因为 a 是代理对象,而 b 中持有的 a 是原始对象。

相关文章:

  • 企业签名.
  • GeoDrive:基于三维几何信息有精确动作控制的驾驶世界模型
  • 5G 智慧工业园区解决方案
  • 永久磁铁的特点有哪些
  • 影子栈指针是什么?
  • CSS标题下划线动态进入和移开
  • 可视化预警系统:如何为企业生产保驾护航?
  • 从0开始一篇文章学习Nginx
  • riscv操作系统记录(一)
  • __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
  • 【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
  • 【从零学习JVM|第三篇】类的生命周期(高频面试题)
  • 【JVM】- 内存结构
  • 八股文——JVM
  • 电子电气架构 --- E/E架构战略
  • 【RAG排序】rag排序代码示例-简单版
  • 医疗风险预测AI模型:机器学习与深度学习方法的深度分析与实践
  • Linux应用开发之网络套接字编程(实例篇)
  • 中医有效性探讨
  • 安卓贝利自动点击器高级版下载安装教程
  • 网站开发的成果/昆明seo案例
  • 课桌公司网站建设/小白如何学电商运营
  • 传媒公司营业执照怎么办理/免费广州seo
  • 个人网站做企业备案/网站搭建详细教程
  • 在国外做网站推广/自己建网站怎么弄
  • 网站开发建设流程图/百度业务范围