美文网首页
Spring循环依赖的解决思路

Spring循环依赖的解决思路

作者: 码而优则仕 | 来源:发表于2020-05-11 19:56 被阅读0次

    什么是循环依赖

    循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。 此处不是循环调用,循环调用是方法之间的环调用。循环调用是无法解决的 除非有终结条件 ,否则就是死循环,最终导致内存溢出错误。

    Spring 如何解决循环依赖

    Spring容器循环依赖包括 构造器循环依赖setter循环依赖,那 Spring容器如何解决循环依赖呢?

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

    1. 构造器循环依赖

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

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

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

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

    循环依赖配置文件—构造器注入

    <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>
    

    创建测试用例。

    @Test(expected=BeanCurrentlyinCreacionException.class) 
    publiC void testCircleByConstructor() throws Throwable 
    {
         try {
                  new ClassPathXmlApplicationContext ("test.xml");
                  } catch(Exception e) {
    //因为要在创建 testC 时抛出;
              Throwable el = e.getCause().getCause().getCause(); 
              throw el;
    }
    

    Setter循环依赖

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

         //为避免后期循环依赖,可以在 bean 初始化完成前将创建实例的 ObjectFactory 加入工厂
             addSingletonFactory(beanName, () ->
                     //对 bean再一次依赖引用,主要应用 SmartInstantiationAwareBeanPostProcessor
                     // 其中我们熟知的 AOP 就是在这里将 advice 动态织人 beaη中,若没有则直接返回bean ,不做任何处理
                     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 注入 。

    setter注入单例bean的循环依赖解决思路:

    三级缓存

    DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry{
    
     //单例对象的缓存
     private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
     //单例对象工厂的缓存
     private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
     //提前曝光的单例对象的缓存
     private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
      //以上三个缓存就是所谓的三级缓存
      
      //缓存正在创建的 bean
      private final Set<String> singletonsCurrentlyInCreation =
             Collections.newSetFromMap(new ConcurrentHashMap<>(16));
      @Nullable
     protected Object getSingleton(String beanName, boolean allowEarlyReference) {
         //单例对象的缓存中是否存在实例
         Object singletonObject = this.singletonObjects.get(beanName);
         // 当前创建的bean不在缓存中且当前bean正在被创建 
        //创建A时,需要注入B ,先去创建B,B又需要创建A 此时去创建A A就不在缓存中且正在创建中
         if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
             //创建bean,锁定缓存单利的对象集合
             synchronized (this.singletonObjects) {
                 //创建前检查提早曝光的 单例bean中是否存在当前要创建的bean
            //如果在提前曝光的单利bean缓存中,则说明当前bean正在被创建中,不处理
                 singletonObject = this.earlySingletonObjects.get(beanName);
            //如果不存在,且允许提前引用(是否允许从singletonFactories中通过getObject拿到对象)
                 if (singletonObject == null && allowEarlyReference) {
                     //创建前再检查 单例bean的工厂缓存中是否存在当前bean的工厂缓存
                     //当某些方法需要提前初始化的时候则会调用 addSingletonFactory方法将对应的ObjectFactory 初始化策略缓存在singletonFactories
                     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
              //如果当前单例bean的工厂已经被缓存则直接使用工厂 获取单例bean
                     if (singletonFactory != null) {
                         //调用预先设定的getObject()方法获取单例bean
                         singletonObject = singletonFactory.getObject();
                         //获取到后将之前的三级缓存提升为二级缓存
                //添加到二级缓存中
                         this.earlySingletonObjects.put(beanName, singletonObject);
                //从三级缓存中移除
                         this.singletonFactories.remove(beanName);
                     }
                 }
             }
         }
         return singletonObject;
     }
    }
    

    prototype 范围的依赖处理

    //缓存正在创建中的 原型 bean--原型bean不支持循环依赖,如果循环依赖则直接报错就是基于该缓存实现的
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
     //自己注入自己,或者自己注入的bean中又包含自己
     Object curVal = this.prototypesCurrentlyInCreation.get();
     return (curVal != null &&
             (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
    

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

    1. 创建配置文件

      <bean id="testA" class="com.bean.CircleA" scope="prototype">
         <property name=”tests” ref=”tests”/> 
      </bean>
      <bean id="testB" class="com.bean.Circles" scope="prototype"> 
        <property name="testC" ref="testC"/>
      </bean>
      <bean id="testC" class="com.bean.CircleC" scope="prototype">
        <property name="testA" ref="testA"/> 
      </bean>
      
    1. 创建测试用例

      @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 el = e.getCause().getCause ().getCause(); 
        throw el;
      }
      }
      

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

    相关文章

      网友评论

          本文标题:Spring循环依赖的解决思路

          本文链接:https://www.haomeiwen.com/subject/zoypnhtx.html