返回介绍

5.6.2 Spring 如何解决循环依赖

发布于 2025-04-22 22:09:10 字数 5482 浏览 0 评论 0 收藏

Spring 容器循环依赖包括构造器循环依赖和 setter 循环依赖,那 Spring 容器如何解决循环依赖呢?首先让我们来定义循环引用类:

public class TestA {

  private TestB testB;

  public void a() {

  testB.b();

 }

  public TestB getTestB() {

   return testB;

 }

  public void setTestB(TestB testB) {

   this.testB = testB;

 }

}

public class TestB {

  private TestC testC;

  public void b() {

  testC.c();

 }

  public TestC getTestC() {

   return testC;

 }

  public void setTestC(TestC testC) {

   this.testC = testC;

 }

}

public class TestC {

  private TestA testA;

  public void c() {

  testA.a();

 }

  public TestA getTestA() {

   return testA;

 }

  public void setTestA(TestA testA) {

   this.testA = testA;

 }

}

在 Spring 中将循环依赖的处理分成了 3 种情况。

1.构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyIn CreationException 异常表示循环依赖。

如在创建 TestA 类时,构造器需要 TestB 类,那将去创建 TestB,在创建 TestB 类时又发现需要 TestC 类,则又去创建 TestC,最终在创建 TestC 时发现又需要 TestA,从而形成一个环,没办法创建。

Spring 容器将每一个正在创建的 bean 标识符放在一个“当前创建 bean 池”中,bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 bean 过程中发现自己已经在“当前创建 bean 池”里时,将抛出 BeanCurrentlyInCreationException 异常表示循环依赖;而对于创建完毕的 bean 将从“当前创建 bean 池”中清除掉。

我们通过一个直观的测试用例来进行分析。

(1)创建配置文件。

<bean id="testA" class="com.bean.TestA">

  <constructor-arg index="0" ref="testB"/>

</bean>

<bean id="testB" class="com.bean.TestB">

  <constructor-arg index="0" ref="testC"/>

</bean>

<bean id="testC" class="com.bean.TestC">

  <constructor-arg index="0" ref="testA"/>

</bean>

(2)创建测试用例。

@Test(expected = BeanCurrentlyInCreationException.class)

public void testCircleByConstructor() throws Throwable {

  try {

   new ClassPathXmlApplicationContext("test.xml");

  } catch (Exception e) {

  //因为要在创建 testC 时抛出;

   Throwable e1 = e.getCause().getCause().getCause();

   throw e1;

 }

}

针对以上代码的分析如下。

Spring 容器创建“testA”bean,首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数“testB”,并将“testA”标识符放到“当前创建 bean 池”。

Spring 容器创建“testB”bean,首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数“testC”,并将“testB”标识符放到“当前创建 bean 池”。

Spring 容器创建“testC”bean,首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数“testA”,并将“testC”标识符放到“当前创建 Bean 池”。

到此为止 Spring 容器要去创建“testA”bean,发现该 bean 标识符在“当前创建 bean 池”中,因为表示循环依赖,抛出 BeanCurrentlyInCreationException。

2.setter 循环依赖

表示通过 setter 注入方式构成的循环依赖。对于 setter 注入造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入)的 bean 来完成的,而且只能解决单例作用域的 bean 循环依赖。通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到该 bean,如下代码所示:

addSingletonFactory(beanName, new ObjectFactory() {

  public Object getObject() throws BeansException {

   return getEarlyBeanReference(beanName, mbd, bean);

 }

});

具体步骤如下。

(1)Spring 容器创建单例“testA”bean,首先根据无参构造器创建 bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的 bean,并将“testA”标识符放到“当前创建 bean 池”,然后进行 setter 注入“testB”。

(2)Spring 容器创建单例“testB”bean,首先根据无参构造器创建 bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的 bean,并将“testB”标识符放到“当前创建 bean 池”,然后进行 setter 注入“circle”。

(3)Spring 容器创建单例“testC”bean,首先根据无参构造器创建 bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的 bean,并将“testC”标识符放到“当前创建 bean 池”,然后进行 setter 注入“testA”。进行注入“testA”时由于提前暴露了“ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的 bean。

(4)最后在依赖注入“testB”和“testA”,完成 setter 注入。

3.prototype 范围的依赖处理

对于“prototype”作用域 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存“prototype”作用域的 bean,因此无法提前暴露一个创建中的 bean。示例如下:

(1)创建配置文件。

<bean id="testA" class="com.bean.CircleA" scope="prototype">

  <property name="testB" ref="testB"/>

</bean>

<bean id="testB" class="com.bean.CircleB" scope="prototype">

  <property name="testC" ref="testC"/>

</bean>

<bean id="testC" class="com.bean.CircleC" scope="prototype">

  <property name="testA" ref="testA"/>

</bean>

(2)创建测试用例。

@Test(expected = BeanCurrentlyInCreationException.class)

public void testCircleBySetterAndPrototype () throws Throwable {

  try {

   ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(

  "testPrototype.xml");

  System.out.println(ctx.getBean("testA"));

  } catch (Exception e) {

   Throwable e1 = e.getCause().getCause().getCause();

   throw e1;

 }

}

对于“singleton”作用域 bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用。

感谢互联网时代,让我可以方便地获取我想要的各种信息,当初我刚开始学习的时候,一直纠结于这里错综复杂的逻辑,幸好我看到了一篇文章解开了我心中的疑惑。在此,感谢原作者,并将原文与大家分享,帮助大家更好的理解 Spring 的依赖,大家可以从 http://www.iflym. com/index.php/code/201208280001.html 来获取原文。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。