菜瓜:水稻,這次我特意去看了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處理的思想一致,構造器依賴本身是破壞語義和規範的
- 屬性賦值–> 依賴注入 。 先創建對象,再賦值屬性,賦值的時候發現需要創建便生成依賴對象,被依賴對象需要前一個對象就從緩存容器中拿取即可
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
※聚甘新