【spring】循環依賴 Java Vs Spring

菜瓜:水稻,此次我特地去看了java的循環依賴java

水稻:喲,有什麼收穫spring

菜瓜:兩種狀況,構造器循環依賴,屬性循環依賴緩存

  • 構造器循環依賴在邏輯層面沒法經過。對象經過構造函數建立時若是須要建立另外一個對象,就會存在遞歸調用。棧內存直接溢出
  • 屬性循環依賴能夠解決。在對象建立完成以後經過屬性賦值操做。
  • package club.interview.base;
    
    /**
     * 構造器循環依賴 - Exception in thread "main" java.lang.StackOverflowError
     * toString()循環打印也會異常 - Exception in thread "main" java.lang.StackOverflowError
     * @author QuCheng on 2020/6/18.
     */
    public class Circular {
    
        class A {
            B b;
    
    //        public A() {
    //            b = new B();
    //        }
    
    //        @Override
    //        public String toString() {
    //            return "A{" +
    //                    "b=" + b +
    //                    '}';
    //        }
        }
    
        class B {
            A a;
    
    //        public B() {
    //            a = new A();
    //        }
    
    //        @Override
    //        public String toString() {
    //            return "B{" +
    //                    "a=" + a +
    //                    '}';
    //        }
        }
    
        private void test() {
            B b = new B();
            A a = new A();
            a.b = b;
            b.a = a;
            System.out.println(a);
            System.out.println(b);
        }
    
        public static void main(String[] args) {
            new Circular().test();
        }
    }

水稻:厲害啊,Spring也不支持構造函數的依賴注入,並且也不支持多例的循環依賴。一樣的,它支持屬性的依賴注入。app

  • 看效果 - 若是toString()打印一樣會出現棧內存溢出。
  • package com.vip.qc.circular;
    
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    /**
     * @author QuCheng on 2020/6/18.
     */
    @Component("a")
    public class CircularA {
    
        @Resource
        private CircularB circularB;
    
    //    @Override
    //    public String toString() {
    //        return "CircularA{" +
    //                "circularB=" + circularB +
    //                '}';
    //    }
    }
    
    
    package com.vip.qc.circular;
    
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    /**
     * @author QuCheng on 2020/6/18.
     */
    @Component("b")
    public class CircularB {
    
        @Resource
        private CircularA circularA;
    
    //    @Override
    //    public String toString() {
    //        return "CircularB{" +
    //                "circularA=" + circularA +
    //                '}';
    //    }
    }
    
    
        @Test
        public void testCircular() {
            String basePackages = "com.vip.qc.circular";
            new AnnotationConfigApplicationContext(basePackages);
        }

菜瓜:看來spring的實現應該也是經過屬性注入的吧ide

水稻:你說的對。先給思路和demo,以後帶你掃一遍源碼,follow me !函數

  • spring的思路是給已經初始化的bean標記狀態,假設A依賴B,B依賴A,先建立A
    • 先從緩存容器(總共三層,一級拿不到就拿二級,二級拿不到就從三級緩存中拿正在建立的)中獲取A,未獲取到就執行建立邏輯
    • 對象A在建立完成還未將屬性渲染完以前標記爲正在建立中,放入三級緩存容器。渲染屬性populateBean()會獲取依賴的對象B。
    • 此時B會走一次getBean邏輯,B一樣會先放入三級緩存,而後渲染屬性,再次走getBean邏輯注入A,此時能從三級緩存中拿到A,並將A放入二級容器。B渲染完成放入一級容器
    • 回到A渲染B的方法populateBean(),拿到B以後能順利執行完本身的建立過程。放入一級緩存
  •  爲了證明結果,我把源碼給改了一下,看結果測試

    • package com.vip.qc.circular;
      
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      
      /**
       * @author QuCheng on 2020/6/18.
       */
      @Component("a")
      public class CircularA {
      
          @Resource
          private CircularB circularB;
      
          @Override
          public String toString() {
              return "CircularA{" +
                      "circularB=" + circularB +
                      '}';
          }
      }
      
      
      package com.vip.qc.circular;
      
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      
      /**
       * @author QuCheng on 2020/6/18.
       */
      @Component("b")
      public class CircularB {
      
          @Resource
          private CircularA circularA;
      
          @Override
          public String toString() {
              return "CircularB{" +
                      "circularA=" + circularA +
                      '}';
          }
      }
      
      
      測試代碼
      @Test
          public void testCircular() {
              String basePackages = "com.vip.qc.circular";
              new AnnotationConfigApplicationContext(basePackages);
      }
      
      測試結果(我改過源碼了)
      ---- 
      將a放入三級緩存
      將b放入三級緩存
      將a放入二級緩存
      將b放入一級緩存
      從二級緩存中拿到了a
      將a放入一級緩存
        
  • 再看源碼
    • 關鍵類處理getSingleton邏輯 - 緩存容器
      • public class DefaultSingletonBeanRegistry 
        
          /** Cache of singleton objects: bean name to bean instance. */
          // 一級緩存
            private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
        
            /** Cache of singleton factories: bean name to ObjectFactory. */
          // 三級緩存
            private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
        
            /** Cache of early singleton objects: bean name to bean instance. */
          // 二級緩存
            private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    • 主流程 AbstractApplicationContext#refresh() -> DefaultListableBeanFactory#preInstantiateSingletons() -> AbstractBeanFactory#getBean() & #doGetBean()
      • protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
           /**
            * 處理FactoryBean接口名稱轉換 {@link BeanFactory#FACTORY_BEAN_PREFIX }
            */
           final String beanName = transformedBeanName(name);
                ...
           // ①從緩存中拿對象(若是對象正在建立中且被依賴注入,會放入二級緩存)
           Object sharedInstance = getSingleton(beanName);
           if (sharedInstance != null && args == null) {
              ...
           }else {      
                    ...
                 if (mbd.isSingleton()) {
                   // ② 將建立的對象放入一級緩存
                    sharedInstance = getSingleton(beanName, () -> {
                       try {
                             // ③ 具體建立的過程,每一個bean建立完成以後都會放入三級緩存,而後渲染屬性
                          return createBean(beanName, mbd, args);
                       }catch (BeansException ex) {
                         ...
           ...
           return (T) bean;
        } 
    • ①getSingleton(beanName) - 二級緩存操做
      • protected Object getSingleton(String beanName, boolean allowEarlyReference) {
           // 實例化已經完成了的放在singletonObjects
           Object singletonObject = this.singletonObjects.get(beanName);
           // 解決循環依賴
           if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
              synchronized (this.singletonObjects) {
                 singletonObject = this.earlySingletonObjects.get(beanName);
                 if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                       singletonObject = singletonFactory.getObject();
                       this.earlySingletonObjects.put(beanName, singletonObject);
                       if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                          System.out.println("將"+beanName+"放入二級緩存");;
                       this.singletonFactories.remove(beanName);
                    }
                 }else if(singletonObject != null){
                    System.out.println("從二級緩存中拿到了"+beanName);
                 }
              }
           }
           return singletonObject;
        }
    • ② getSingleton(beanName,lamdba) - 一級緩存操做
      • public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
              Assert.notNull(beanName, "Bean name must not be null");
              synchronized (this.singletonObjects) {
                 Object singletonObject = this.singletonObjects.get(beanName);
                 if (singletonObject == null) {
                    if (this.singletonsCurrentlyInDestruction) {
                    ...
                    // 正在建立的bean加入singletonsCurrentlyInCreation - 保證只有一個對象建立,阻斷循環依賴
                    beforeSingletonCreation(beanName);
                              ...
                    try {
                       singletonObject = singletonFactory.getObject();
                    ...
                    finally {
                    ...
                       // 從singletonsCurrentlyInCreation中移除
                       afterSingletonCreation(beanName);
                    }
                    if (newSingleton) {
                       // 對象建立完畢 - 放入一級緩存(從其餘緩存移除)
                       addSingleton(beanName, singletonObject);
                    }
                 }
                 return singletonObject;
              }
           }
                
         //  -----  內部調用一級緩存操做
            protected void addSingleton(String beanName, Object singletonObject) {
                synchronized (this.singletonObjects) {
                    if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                        System.out.println("將"+beanName+"放入一級緩存");;
                    this.singletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                    this.earlySingletonObjects.remove(beanName);
                    this.registeredSingletons.add(beanName);
                }
            }        
           
    • ③createBean(beanName, mbd, args) - 三級緩存操做
      • protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {
             ...
           if (instanceWrapper == null) {
              // 5* 實例化對象自己
              instanceWrapper = createBeanInstance(beanName, mbd, args);
           }
           ...
           boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                 isSingletonCurrentlyInCreation(beanName));
           if (earlySingletonExposure) {
              ...
              // 將建立好還未渲染屬性的bean 放入三級緩存
              addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
           }
        
           Object exposedObject = bean;
           try {
              // 渲染bean自身和屬性
              populateBean(beanName, mbd, instanceWrapper);
              // 實例化以後的後置處理 - init
              exposedObject = initializeBean(beanName, exposedObject, mbd);
           }
           catch (Throwable ex) {
           ...
           return exposedObject;
        }
          
          
           // ------------- 內部調用三級緩存操做 
           protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
                Assert.notNull(singletonFactory, "Singleton factory must not be null");
                synchronized (this.singletonObjects) {
                    if (!this.singletonObjects.containsKey(beanName)) {
                        if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                            System.out.println("將"+beanName+"放入三級緩存");;
                        this.singletonFactories.put(beanName, singletonFactory);
                        this.earlySingletonObjects.remove(beanName);
                        this.registeredSingletons.add(beanName);
                    }
                }
            }       

菜瓜:demo比較簡單,流程大體明白,源碼我還須要斟酌一下,總體有了概念。這個流程好像是摻雜在bean的建立過程當中,結合bean的生命週期總體理解可能會更深刻一點ui

水稻:是的。每一個知識點都不是單一的,拿着bean的生命週期再理解一遍可能會更有收穫。this

 

討論spa

  • 爲何是三級緩存,兩級不行嗎?
    • 猜想:理論上兩級也能夠實現。多一個二級緩存多是爲了加快獲取的速度。加入A依賴B,A依賴C,B依賴A,C依賴A,那麼C在獲取A的時候只須要從二級緩存中就能拿到A了

總結

  • Spring的處理方式和java處理的思想一致,構造器依賴自己是破壞語義和規範的
  • 屬性賦值--> 依賴注入 。 先建立對象,再賦值屬性,賦值的時候發現須要建立便生成依賴對象,被依賴對象須要前一個對象就從緩存容器中拿取便可
相關文章
相關標籤/搜索