一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring 處理循環依賴只使用二級緩存,可以嗎?

Spring 處理循環依賴只使用二級緩存,可以嗎?

2022-01-12 23:44Java識堂李立敏 Java教程

先說一下什么是循環依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成。

Spring 處理循環依賴只使用二級緩存,可以嗎?

什么是循環依賴?

先說一下什么是循環依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成。

Spring的循環依賴有4種場景:

  • 構造器的循環依賴(singleton,prototype)
  • 屬性的循環依賴(singleton,prototype)

「spring目前只支持singleton類型的屬性循環依賴」

構造器的循環依賴

  1. @Component 
  2. public class ConstructorA { 
  3.  
  4.  private ConstructorB constructorB; 
  5.  
  6.  @Autowired 
  7.  public ConstructorA(ConstructorB constructorB) { 
  8.   this.constructorB = constructorB; 
  9.  } 
  1. @Component 
  2. public class ConstructorB { 
  3.  
  4.  private ConstructorA constructorA; 
  5.  
  6.  @Autowired 
  7.  public ConstructorB(ConstructorA constructorA) { 
  8.   this.constructorA = constructorA; 
  9.  } 
  1. @Configuration 
  2. @ComponentScan("com.javashitang.dependency.constructor"
  3. public class ConstructorConfig { 
  1. public class ConstructorMain { 
  2.  
  3.  public static void main(String[] args) { 
  4.   AnnotationConfigApplicationContext context = 
  5.     new AnnotationConfigApplicationContext(ConstructorConfig.class); 
  6.   System.out.println(context.getBean(ConstructorA.class)); 
  7.   System.out.println(context.getBean(ConstructorB.class)); 
  8.  } 

運行ConstructorMain的main方法的時候會在第一行就報異常,說明Spring沒辦法初始化所有的Bean,即上面這種形式的循環依賴Spring無法解決。

「構造器的循環依賴,可以在構造函數中使用@Lazy注解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再創建對象完成注入」

  1. @Autowired 
  2. public ConstructorB(@Lazy ConstructorA constructorA) { 
  3.  this.constructorA = constructorA; 

因為我們主要關注屬性的循環依賴,構造器的循環依賴就不做過多分析了。

屬性的循環依賴

先演示一下什么是屬性的循環依賴。

  1. @Data 
  2. @Component 
  3. public class A { 
  4.  
  5.     @Autowired 
  6.     private B b; 
  1. @Data 
  2. @Component 
  3. public class B { 
  4.  
  5.     @Autowired 
  6.     private A a; 
  1. @Configuration 
  2. @EnableAspectJAutoProxy 
  3. @ComponentScan("com.javashitang.dependency"
  4. public class Config { 
  1. public class Main { 
  2.  
  3.     public static void main(String[] args) { 
  4.         AnnotationConfigApplicationContext context = 
  5.                 new AnnotationConfigApplicationContext(Config.class); 
  6.         System.out.println(context.getBean(A.class).getB() == context.getBean(B.class)); 
  7.         System.out.println(context.getBean(B.class).getA() == context.getBean(A.class)); 
  8.     } 

Spring容器正常啟動,運行結果為true,想實現類似的功能并不難,我寫個demo演示一下。

  1. public class DependencyDemoV1 { 
  2.  
  3.     private static final Map<String, Object> singletonObjects = 
  4.             new HashMap<>(256); 
  5.  
  6.     @SneakyThrows 
  7.     public static <T> T getBean(Class<T> beanClass) { 
  8.         String beanName = beanClass.getSimpleName(); 
  9.         if (singletonObjects.containsKey(beanName)) { 
  10.             return (T) singletonObjects.get(beanName); 
  11.         } 
  12.         // 實例化bean 
  13.         Object object = beanClass.getDeclaredConstructor().newInstance(); 
  14.         singletonObjects.put(beanName, object); 
  15.         // 開始初始化bean,即填充屬性 
  16.         Field[] fields = object.getClass().getDeclaredFields(); 
  17.         for (Field field : fields) { 
  18.             field.setAccessible(true); 
  19.             // 獲取需要注入字段的class 
  20.             Class<?> fieldClass = field.getType(); 
  21.             field.set(object, getBean(fieldClass)); 
  22.         } 
  23.         return (T) object; 
  24.     } 
  25.  
  26.     public static void main(String[] args) { 
  27.         // 假裝掃描出來的類 
  28.         Class[] classes = {A.class, B.class}; 
  29.         for (Class aClass : classes) { 
  30.             getBean(aClass); 
  31.         } 
  32.         System.out.println(getBean(A.class).getB() == getBean(B.class)); 
  33.         System.out.println(getBean(B.class).getA() == getBean(A.class)); 
  34.     } 
  35.  

「在開始后面的內容的時候,我們先明確2個概念」

實例化:調用構造函數將對象創建出來 初始化:調用構造函數將對象創建出來后,給對象的屬性也被賦值。

可以看到只用了一個map就實現了循環依賴的實現,但這種實現有個小缺陷,singletonObjects中的類有可能只是完成了實例化,并沒有完成初始化。

而在spring中singletonObjects中的類都完成了初始化,因為我們取單例Bean的時候都是從singletonObjects中取的,不可能讓我們獲取到沒有初始化完成的對象。

所以我們來寫第二個實現,「用singletonObjects存初始化完成的對象,而用earlySingletonObjects暫存實例化完成的對象,等對象初始化完畢再將對象放入singletonObjects,并從earlySingletonObjects刪除」。

  1. public class DependencyDemoV2 { 
  2.  
  3.     private static final Map<String, Object> singletonObjects = 
  4.             new HashMap<>(256); 
  5.  
  6.     private static final Map<String, Object> earlySingletonObjects = 
  7.             new HashMap<>(256); 
  8.  
  9.     @SneakyThrows 
  10.     public static <T> T getBean(Class<T> beanClass) { 
  11.         String beanName = beanClass.getSimpleName(); 
  12.         if (singletonObjects.containsKey(beanName)) { 
  13.             return (T) singletonObjects.get(beanName); 
  14.         } 
  15.         if (earlySingletonObjects.containsKey(beanName)) { 
  16.             return (T) earlySingletonObjects.get(beanName); 
  17.         } 
  18.         // 實例化bean 
  19.         Object object = beanClass.getDeclaredConstructor().newInstance(); 
  20.         earlySingletonObjects.put(beanName, object); 
  21.         // 開始初始化bean,即填充屬性 
  22.         Field[] fields = object.getClass().getDeclaredFields(); 
  23.         for (Field field : fields) { 
  24.             field.setAccessible(true); 
  25.             // 獲取需要注入字段的class 
  26.             Class<?> fieldClass = field.getType(); 
  27.             field.set(object, getBean(fieldClass)); 
  28.         } 
  29.         singletonObjects.put(beanName, object); 
  30.         earlySingletonObjects.remove(beanName); 
  31.         return (T) object; 
  32.     } 
  33.  
  34.     public static void main(String[] args) { 
  35.         // 假裝掃描出來的類 
  36.         Class[] classes = {A.class, B.class}; 
  37.         for (Class aClass : classes) { 
  38.             getBean(aClass); 
  39.         } 
  40.         System.out.println(getBean(A.class).getB() == getBean(B.class)); 
  41.         System.out.println(getBean(B.class).getA() == getBean(A.class)); 
  42.     } 
  43.  

現在的實現和spring保持一致了,并且只用了2級緩存。spring為什么搞第三個緩存呢?「第三個緩存主要和代理對象相關」

我還是把上面的例子改進一下,改成用3級緩存的實現:

  1. public interface ObjectFactory<T> { 
  2.     T getObject(); 
  1. public class DependencyDemoV3 { 
  2.  
  3.     private static final Map<String, Object> singletonObjects = 
  4.             new HashMap<>(256); 
  5.  
  6.     private static final Map<String, Object> earlySingletonObjects = 
  7.             new HashMap<>(256); 
  8.  
  9.     private static final Map<String, ObjectFactory<?>> singletonFactories = 
  10.             new HashMap<>(256); 
  11.  
  12.     @SneakyThrows 
  13.     public static <T> T getBean(Class<T> beanClass) { 
  14.         String beanName = beanClass.getSimpleName(); 
  15.         if (singletonObjects.containsKey(beanName)) { 
  16.             return (T) singletonObjects.get(beanName); 
  17.         } 
  18.         if (earlySingletonObjects.containsKey(beanName)) { 
  19.             return (T) earlySingletonObjects.get(beanName); 
  20.         } 
  21.         ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); 
  22.         if (singletonFactory != null) { 
  23.             return (T) singletonFactory.getObject(); 
  24.         } 
  25.         // 實例化bean 
  26.         Object object = beanClass.getDeclaredConstructor().newInstance(); 
  27.         singletonFactories.put(beanName, () -> { 
  28.             Object proxy = createProxy(object); 
  29.             singletonFactories.remove(beanName); 
  30.             earlySingletonObjects.put(beanName, proxy); 
  31.             return proxy; 
  32.         }); 
  33.         // 開始初始化bean,即填充屬性 
  34.         Field[] fields = object.getClass().getDeclaredFields(); 
  35.         for (Field field : fields) { 
  36.             field.setAccessible(true); 
  37.             // 獲取需要注入字段的class 
  38.             Class<?> fieldClass = field.getType(); 
  39.             field.set(object, getBean(fieldClass)); 
  40.         } 
  41.         createProxy(object); 
  42.         singletonObjects.put(beanName, object); 
  43.         singletonFactories.remove(beanName); 
  44.         earlySingletonObjects.remove(beanName); 
  45.         return (T) object; 
  46.     } 
  47.  
  48.     public static Object createProxy(Object object) { 
  49.         // 因為這個方法有可能被執行2次,所以這里應該有個判斷 
  50.         // 如果之前提前進行過aop操作則直接返回,知道意思就行,不寫了哈 
  51.         // 需要aop的話則返回代理對象,否則返回傳入的對象 
  52.         return object; 
  53.     } 
  54.  
  55.     public static void main(String[] args) { 
  56.         // 假裝掃描出來的類 
  57.         Class[] classes = {A.class, B.class}; 
  58.         for (Class aClass : classes) { 
  59.             getBean(aClass); 
  60.         } 
  61.         System.out.println(getBean(A.class).getB() == getBean(B.class)); 
  62.         System.out.println(getBean(B.class).getA() == getBean(A.class)); 
  63.     } 
  64.  

「為什么要包裝一個ObjectFactory對象?」

如果創建的Bean有對應的aop代理,那其他對象注入時,注入的應該是對應的代理對象;「但是Spring無法提前知道這個對象是不是有循環依賴的情況」,而正常情況下(沒有循環依賴情況),Spring都是在對象初始化后才創建對應的代理。這時候Spring有兩個選擇:

  • 不管有沒有循環依賴,實例化后就直接創建好代理對象,并將代理對象放入緩存,出現循環依賴時,其他對象直接就可以取到代理對象并注入(只需要2級緩存,singletonObjects和earlySingletonObjects即可)
  • 「不提前創建好代理對象,在出現循環依賴被其他對象注入時,才提前生成代理對象(此時只完成了實例化)。這樣在沒有循環依賴的情況下,Bean還是在初始化完成才生成代理對象」(需要3級緩存)
  • 「所以到現在為止你知道3級緩存的作用了把,主要是為了正常情況下,代理對象能在初始化完成后生成,而不用提前生成」
緩存 說明
singletonObjects 第一級緩存,存放初始化完成的Bean
earlySingletonObjects 第二級緩存,存放實例化完成的Bean,有可能被進行了代理
singletonFactories 延遲生成代理對象

源碼解析

獲取Bean的時候先嘗試從3級緩存中獲取,和我們上面的Demo差不多哈!

DefaultSingletonBeanRegistry#getSingleton

Spring 處理循環依賴只使用二級緩存,可以嗎?

當從緩存中獲取不到時,會進行創建 AbstractAutowireCapableBeanFactory#doCreateBean(刪除了部分代碼哈)

Spring 處理循環依賴只使用二級緩存,可以嗎?

發生循環依賴時,會從工廠里獲取代理對象哈!

Spring 處理循環依賴只使用二級緩存,可以嗎?

當開啟aop代理時,SmartInstantiationAwareBeanPostProcessor的一個實現類有AbstractAutoProxyCreator

AbstractAutoProxyCreator#getEarlyBeanReference

Spring 處理循環依賴只使用二級緩存,可以嗎?

getEarlyBeanReference方法提前進行代理,為了防止后面再次進行代理,需要用earlyProxyReferences記錄一下,這個Bean已經被代理過了,不用再代理了。

AbstractAutoProxyCreator#postProcessAfterInitialization

Spring 處理循環依賴只使用二級緩存,可以嗎?

這個方法是進行aop代理的地方,因為有可能提前代理了,所以先根據earlyProxyReferences判斷一下,是否提前代理了,提前代理過就不用代理了。

當bean初始化完畢,會放入一級緩存,并從二三級緩存刪除。

DefaultSingletonBeanRegistry#addSingleton

Spring 處理循環依賴只使用二級緩存,可以嗎?

發生循環依賴時,整體的執行流程如下:

Spring 處理循環依賴只使用二級緩存,可以嗎?

原文鏈接:https://mp.weixin.qq.com/s/WfY0jT2yukXgJ0pOkK6RuA

延伸 · 閱讀

精彩推薦
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 精品视频一区在线观看 | 国产一区二区三区久久精品 | 洗濯屋H纯肉动漫在线观看 武侠艳妇屈辱的张开双腿 午夜在线观看免费观看 视频 | 欧美国产日韩1区俺去了 | 99久久精品免费看国产高清 | 免费人成在线观看 | videos变态极端 | 天天做天天玩天天爽天天 | 秋霞黄色网 | 给我免费观看的视频在线播放 | 久久丫线这里只精品 | 亚洲国产精品综合福利专区 | 国产hd老头老太婆 | 肉大捧一进一出视频免费播放 | 免费观看在线观看 | a级毛片毛片免费观看永久 a级黄色片免费 | 亚洲精品一区二区三区在线看 | 国产一卡二卡3卡4卡四卡在线视频 | 免费看黄色片的网站 | 男女真实无遮挡xx00动态图软件 | 亚洲欧洲日产国码天堂 | 国产乱叫456在线 | 色哟哟在线视频 | 色老板视频在线 | 91国内精品 | 国内自拍网红在线综合 | 国产一区二区三区丶四区 | 天天综合天天影视色香欲俱全 | a级在线看 | 91看片淫黄大片.在线天堂 | 极品美女穴 | 欧美伦乱| 国产剧情一区 | kk4444了欧美 | 亚洲瑟瑟网 | 久久婷婷电影网 | 奇米影视小说 | 日本在线观看www免费 | 免费一级毛片完整版在线看 | video one 中国按摩 | 五月天精品视频在线观看 |