5.6循环依赖

5.6.1什么是循环依赖、循环调用
循环依赖:实例化bean是一个复杂的过程。循环依赖就是两个或者多个bean相互之间持有对方,比如TestA引用TestB,TestB引用TestA,则它们最终反映为一个环。
循环调用:循环调用是方法之间的环调用。循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出。
 
5.6.2Spring如何解决循环依赖
首先我们定义循环依赖类、注入、测试方法
package CircularReference;
public class TestA {

    private TestB testB;

    public TestA(){

    }

    public TestA(TestB testB) {
        this.testB = testB;
    }

    public TestB getTestB() {
        return testB;
    }

    public void setTestB(TestB testB) {
        this.testB = testB;
    }
}

package CircularReference;
public class TestB {

    private TestA testA;

    public TestB(){

    }

    public TestB(TestA testA) {
        this.testA = testA;
    }

    public TestA getTestA() {
        return testA;
    }

    public void setTestA(TestA testA) {
        this.testA = testA;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--构造器循环依赖-->
    <!--<bean id="testA" class="CircularReference.TestA">-->
        <!--<constructor-arg ref="testB"></constructor-arg>-->
    <!--</bean>-->

    <!--<bean id="testB" class="CircularReference.TestB">-->
        <!--<constructor-arg ref="testA"></constructor-arg>-->
    <!--</bean>-->

    <!--setter循环依赖-->
    <bean id="testA" class="CircularReference.TestA">
        <property name="testB" ref="testB"></property>
    </bean>

    <bean id="testB" class="CircularReference.TestB">
        <property name="testA" ref="testA"></property>
    </bean>

</beans>
/**
* 构造器循环依赖
*/
@Test
public void constructor(){
    try {
        new ClassPathXmlApplicationContext("/circularReference.xml");
    }catch (Exception e){
        e.printStackTrace();
        System.out.println(e.getCause());
    }
}

/**
* setter循环依赖
*/
@Test
public void setter(){
    ApplicationContext contex =
        new ClassPathXmlApplicationContext("/circularReference.xml");
    System.out.println(contex.getBean("testA"));
}

 

3种循环依赖
1)构造器循环依赖(单例的)
构造器注入的循环依赖,此依赖是无法解决的。只能抛出BeanCurrentlyInCreationEx-ception异常表示循环依赖。bean实例化有关
代码中的实现方式:DefaultSingletonBeanRegistry中getSingleton(beanName-->,singletonFactory)方法的beforeSingletonCreation中的this.singletonsCurrently-InCreation.add(beanName)。5.4获取单例---大致逻辑---第3条。
Spring容器将每一条正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因为如果在创建bean过程中发现自己已经在“当前创建bean池”里面时,抛出BeanCurrentlyInCreationEx-ception异常表示循环依赖。而对于创建完毕的bean将从“当前创建bean池”中移除。
模拟执行:
 

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

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

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

2)setter循环依赖(单例的)
简单来说是通过一个默认构造器,并暴露一个FactoryBean用于返回一个创建中的bean(AbstractBeanFactory.doGetBean方法的getSingleton会用到),然后再进行setter设置。
通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入(默认构造器)但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。
代码中的实现方式:
获取:AbstractBeanFactory-->doGetBean-->getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   //检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      //缓存中没有实例但是又在创建中,那么可能是循环依赖,解决循环依赖的方式是
      //Spring创建bean的原则是不等bean创建完成就会创建bean的ObjectFactory提早曝光
singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         //如果为空,则锁定全局变量并进行处理
synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  //当某些方法需要提前初始化的时候则会调用addSingletonFactory方法将对应的
                  //ObjectFactory初始化策略存储在singletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     //调用预先设定的getObject方法
singletonObject = singletonFactory.getObject();
//记录在缓存中,earlySingletonObjects和singletonFactories互斥
this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
}
               }
            }
         }
      }
   }
   return singletonObject;
}

 

存储:AbstractAutowireCapableBeanFactory-->createBean-->doCreateBean-->addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
}
   }
}
3)prototype范围的依赖处理
对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存“prototype”作用域的bean。因此无法暴露一个创建中的bean。
对于singleton作用域bean,可以通过“setAllowCircularReferences(false)”来禁止循环引用。
 
博客:Spring中循环引用的处理
https://www.iflym.com/index.php/code/201208280001.html





posted @ 2021-01-13 17:16  _Shing  阅读(203)  评论(0)    收藏  举报