Spring 如何解决循环依赖?

1,208 阅读2分钟

一、 什么是循环依赖?

A 中有 B,B 中有 A,即循环依赖。

二、Spring 解决方式

Spring 紧紧能解决 scope 为 singleton 的 Bean 循环依赖问题。

  1. 构造器循环依赖 constructor

    Spring 无法解决,直接抛出异常,org.springframework.beans.factory.BeanCurrentlyInCreationException

    Requested bean is currently in creation: Is there an unresolvable circular reference?

    • 源代码

      protected void beforeSingletonCreation(String beanName) {
      		if (!this.inCreationCheckExclusions.contains(beanName) && 		                      		!this.singletonsCurrentlyInCreation.add(beanName)) {
      			throw new BeanCurrentlyInCreationException(beanName);
      		}
      	}
      
  2. Set 循环依赖

    Spring 通过 提前暴露 创建 Bean 的 ObjectFactory 解决 Set 循环依赖,在创建 B 的时候直接从 A 的 ObjectFactory 中 getObject()

    • 源代码

      // 缓存提前暴露的 Bean
      /** Cache of early singleton objects: bean name to bean instance. */
      private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
      
      // 缓存创建 singleton Bean 的 ObjectFactory
      /** Cache of singleton factories: bean name to ObjectFactory. */
      private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
      
      // 可以看到这里提前暴露的条件是: isSingleton && (allowCircularReferences == true) && bean in creation
      // Eagerly cache singletons to be able to resolve circular references
      // even when triggered by lifecycle interfaces like BeanFactoryAware.
      boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                        isSingletonCurrentlyInCreation(beanName));
      if (earlySingletonExposure) {
          addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      }
      
      protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
          synchronized (this.singletonObjects) {
              if (!this.singletonObjects.containsKey(beanName)) {
                  // 将创建 Bean 的 ObjectFactory 缓存到 singletonFactories 中
                  this.singletonFactories.put(beanName, singletonFactory);
                  // 将提前暴露的 beanName 从 earlySingletonObjects 中移除
                  this.earlySingletonObjects.remove(beanName);
                  // 已注册的 Bean
                  this.registeredSingletons.add(beanName);
              }
          }
      }
      
      /**
       * 这个方法在 earlySingletonExposure == true,即创建需要提前暴露的单例 Bean 时调用(其中之一条件)
       */
      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
          // 从已注册的 singletonObjects 中取
          Object singletonObject = this.singletonObjects.get(beanName);
          if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
              synchronized (this.singletonObjects) {
                  // 上面取不到,且当前是正在创建中的单例时,从 earlySingletonObjects 中取
                  singletonObject = this.earlySingletonObjects.get(beanName);
                  if (singletonObject == null && allowEarlyReference) {
                      // 上面取不到,且允许提前引用,从 singletonFactories 中取出提前暴露的 ObjectFactory
                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                          // 有提前暴露的 ObjectFactory,则从中取出 Bean
                          singletonObject = singletonFactory.getObject();
                          // 缓存入 earlySingletonObjects
                          this.earlySingletonObjects.put(beanName, singletonObject);
                          // 清除提前暴露的 ObjectFactory
                          this.singletonFactories.remove(beanName);
                      }
                  }
              }
          }
          return singletonObject;
      }
      
  3. prototype 循环依赖

和构造器循环一样抛出:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxx': Requested bean is currently in creation: Is there an unresolvable circular reference? 异常

三、测试

public class TestA {
    private TestB testB;
    public TestA() {}
    public TestA(TestB testB) {
        this.testB = testB;
    }
    public void a() {
        testB.b();
    }
    // 省略 get/set 方法
}
public class TestB {
    private TestC testC;
    public TestB() {}
    public TestB(TestC testC) {
        this.testC = testC;
    }
    public void b() {
        testC.c();
    }
    // 省略 get/set 方法
}
public class TestC {
    private TestA testA;
    public TestC() {}
    public TestC(TestA testA) {
        this.testA = testA;
    }
    public void c() {
        testA.a();
    }
    // 省略 get/set 方法
}
  1. 构造器循环依赖
  • spring-constructor-cycle-reference.xml
<bean id="testA" class="com.marksman.spring.cycle.TestA">
    <constructor-arg index="0" ref="testB"/>
</bean>
<bean id="testB" class="com.marksman.spring.cycle.TestB">
    <constructor-arg index="0" ref="testC"/>
</bean>
<bean id="testC" class="com.marksman.spring.cycle.TestC">
    <constructor-arg index="0" ref="testA"/>
</bean>
  • 测试
try {
    new ClassPathXmlApplicationContext("/META-INF/spring-constructor-cycle-reference.xml");
} catch (Exception e) {
    Throwable throwable = e.getCause().getCause().getCause();
    throw throwable;
}

// 抛出 BeanCurrentlyInCreationException 异常
  1. Set 循环依赖
  • spring-set-cycle-reference.xml
<bean id="testA" class="com.marksman.spring.cycle.TestA">
    <property name="testB" ref="testB"/>
</bean>
<bean id="testB" class="com.marksman.spring.cycle.TestB">
    <property name="testC" ref="testC"/>
</bean>
<bean id="testC" class="com.marksman.spring.cycle.TestC">
    <property name="testA" ref="testA"/>
</bean>
  • 测试
ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring-set-cycle-reference.xml");
System.out.println(context.getBean("testA"));
System.out.println(context.getBean("testB"));
System.out.println(context.getBean("testC"));

// 打印出 testA、testB、testC 引用地址
  1. prototype 循环依赖
  • spring-prototype-cycle-reference.xml
<bean id="testA" class="com.marksman.spring.cycle.TestA" scope="prototype">
    <property name="testB" ref="testB"/>
</bean>
<bean id="testB" class="com.marksman.spring.cycle.TestB" scope="prototype">
    <property name="testC" ref="testC"/>
</bean>
<bean id="testC" class="com.marksman.spring.cycle.TestC" scope="prototype">
    <property name="testA" ref="testA"/>
</bean>
  • 测试
try {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring-prototype-cycle-reference.xml");
    System.out.println(context.getBean("testA"));
    System.out.println(context.getBean("testB"));
    System.out.println(context.getBean("testC"));
} catch (Exception e) {
    Throwable throwable = e.getCause().getCause().getCause();
    throw throwable;
}

// 抛出 BeanCurrentlyInCreationException 异常

参考


  • 《Spring 源码深度解析》