菜瓜:水稻,這次我特意去看了java的迴圈依賴
水稻:喲,有什麼收穫
菜瓜:兩種情況,構造器迴圈依賴,屬性迴圈依賴
- 構造器迴圈依賴在邏輯層面無法通過。物件通過建構函式建立時如果需要建立另一個物件,就會存在遞迴呼叫。棧記憶體直接溢位
- 屬性迴圈依賴可以解決。在物件建立完成之後通過屬性賦值操作。
-
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也不支援建構函式的依賴注入,而且也不支援多例的迴圈依賴。同樣的,它支援屬性的依賴注入。
- 看效果 - 如果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的實現應該也是通過屬性注入的吧
水稻:你說的對。先給思路和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); } } }
-
- 關鍵類處理getSingleton邏輯 - 快取容器
菜瓜:demo比較簡單,流程大致明白,原始碼我還需要斟酌一下,整體有了概念。這個流程好像是摻雜在bean的建立過程中,結合bean的生命週期整體理解可能會更深入一點
水稻:是的。每個知識點都不是單一的,拿著bean的生命週期再理解一遍可能會更有收穫。
討論
- 為什麼是三級快取,兩級不行嗎?
- 猜測:理論上兩級也可以實現。多一個二級快取可能是為了加快獲取的速度。加入A依賴B,A依賴C,B依賴A,C依賴A,那麼C在獲取A的時候只需要從二級快取中就能拿到A了
總結
- Spring的處理方式和java處理的思想一致,構造器依賴本身是破壞語義和規範的
- 屬性賦值--> 依賴注入 。 先建立物件,再賦值屬性,賦值的時候發現需要建立便生成依賴物件,被依賴物件需要前一個物件就從快取容器中拿取即可