自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

女朋友都能看懂,Spring如何解決循環(huán)依賴?

開發(fā) 架構(gòu)
先說一下什么是循環(huán)依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成。

 

本文轉(zhuǎn)載自微信公眾號「 Java識堂」,作者李立敏。轉(zhuǎn)載本文請聯(lián)系 Java識堂公眾號。

介紹

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

[[330848]]

Spring的循環(huán)依賴有兩種場景

  1. 構(gòu)造器的循環(huán)依賴
  2. 屬性的循環(huán)依賴

構(gòu)造器的循環(huán)依賴,可以在構(gòu)造函數(shù)中使用@Lazy注解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再創(chuàng)建對象完成注入

屬性的循環(huán)依賴主要是通過3個map來解決的

構(gòu)造器的循環(huán)依賴

  1. @Component 
  2. public class ConstructorA { 
  3.  
  4.  private ConstructorB constructorB; 
  5.  
  6.  @Autowired 
  7.  public ConstructorA(ConstructorB constructorB) { 
  8.   this.constructorB = constructorB; 
  9.  } 
  10. @Component 
  11. public class ConstructorB { 
  12.  
  13.  private ConstructorA constructorA; 
  14.  
  15.  @Autowired 
  16.  public ConstructorB(ConstructorA constructorA) { 
  17.   this.constructorA = constructorA; 
  18.  } 
  19. @Configuration 
  20. @ComponentScan("com.javashitang.dependency.constructor"
  21. public class ConstructorConfig { 
  22. public class ConstructorMain { 
  23.  
  24.  public static void main(String[] args) { 
  25.   AnnotationConfigApplicationContext context = 
  26.     new AnnotationConfigApplicationContext(ConstructorConfig.class); 
  27.   System.out.println(context.getBean(ConstructorA.class)); 
  28.   System.out.println(context.getBean(ConstructorB.class)); 
  29.  } 

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

我們可以在ConstructorA或者ConstructorB構(gòu)造函數(shù)的參數(shù)上加上@Lazy注解就可以解決

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

因為我們主要關(guān)注屬性的循環(huán)依賴,構(gòu)造器的循環(huán)依賴就不做過多分析了

屬性的循環(huán)依賴

先演示一下什么是屬性的循環(huán)依賴

  1. @Component 
  2. public class FieldA { 
  3.  
  4.  @Autowired 
  5.  private FieldB fieldB; 
  6. @Component 
  7. public class FieldB { 
  8.  
  9.  @Autowired 
  10.  private FieldA fieldA; 
  11. @Configuration 
  12. @ComponentScan("com.javashitang.dependency.field"
  13. public class FieldConfig { 
  14. public class FieldMain { 
  15.  
  16.  public static void main(String[] args) { 
  17.   AnnotationConfigApplicationContext context = 
  18.     new AnnotationConfigApplicationContext(FieldConfig.class); 
  19.   // com.javashitang.dependency.field.FieldA@3aa9e816 
  20.   System.out.println(context.getBean(FieldA.class)); 
  21.   // com.javashitang.dependency.field.FieldB@17d99928 
  22.   System.out.println(context.getBean(FieldB.class)); 
  23.  } 

Spring容器正常啟動,能獲取到FieldA和FieldB這2個Bean

屬性的循環(huán)依賴在面試中還是經(jīng)常被問到的??傮w來說也不復雜,但是涉及到Spring Bean的初始化過程,所以感覺比較復雜,我寫個demo演示一下整個過程

Spring的Bean的初始化過程其實比較復雜,為了方便理解Demo,我就把Spring Bean的初始化過程分為2部分

  1. bean的實例化過程,即調(diào)用構(gòu)造函數(shù)將對象創(chuàng)建出來
  2. bean的初始化過程,即填充bean的各種屬性

bean初始化過程完畢,則bean就能被正常創(chuàng)建出來了

下面開始寫Demo,ObjectFactory接口用來生產(chǎn)Bean,和Spring中定義的接口一樣

  1. public interface ObjectFactory<T> { 
  2.  T getObject(); 
  3. public class DependencyDemo { 
  4.  
  5.  // 初始化完畢的Bean 
  6.  private final Map<String, Object> singletonObjects = 
  7.    new ConcurrentHashMap<>(256); 
  8.  
  9.  // 正在初始化的Bean對應的工廠,此時對象已經(jīng)被實例化 
  10.  private final Map<String, ObjectFactory<?>> singletonFactories = 
  11.    new HashMap<>(16); 
  12.  
  13.  // 存放正在初始化的Bean,對象還沒有被實例化之前就放進來了 
  14.  private final Set<String> singletonsCurrentlyInCreation = 
  15.    Collections.newSetFromMap(new ConcurrentHashMap<>(16)); 
  16.  
  17.  public  <T> T getBean(Class<T> beanClass) throws Exception { 
  18.   // 類名為Bean的名字 
  19.   String beanName = beanClass.getSimpleName(); 
  20.   // 已經(jīng)初始化好了,或者正在初始化 
  21.   Object initObj = getSingleton(beanName, true); 
  22.   if (initObj != null) { 
  23.    return (T) initObj; 
  24.   } 
  25.   // bean正在被初始化 
  26.   singletonsCurrentlyInCreation.add(beanName); 
  27.   // 實例化bean 
  28.   Object object = beanClass.getDeclaredConstructor().newInstance(); 
  29.   singletonFactories.put(beanName, () -> { 
  30.    return object; 
  31.   }); 
  32.   // 開始初始化bean,即填充屬性 
  33.   Field[] fields = object.getClass().getDeclaredFields(); 
  34.   for (Field field : fields) { 
  35.    field.setAccessible(true); 
  36.    // 獲取需要注入字段的class 
  37.    Class<?> fieldClass = field.getType(); 
  38.    field.set(object, getBean(fieldClass)); 
  39.   } 
  40.   // 初始化完畢 
  41.   singletonObjects.put(beanName, object); 
  42.   singletonsCurrentlyInCreation.remove(beanName); 
  43.   return (T) object; 
  44.  } 
  45.  
  46.  /** 
  47.   * allowEarlyReference參數(shù)的含義是Spring是否允許循環(huán)依賴,默認為true 
  48.   * 所以當allowEarlyReference設置為false的時候,當項目存在循環(huán)依賴,會啟動失敗 
  49.   */ 
  50.  public Object getSingleton(String beanName, boolean allowEarlyReference) { 
  51.   Object singletonObject = this.singletonObjects.get(beanName); 
  52.   if (singletonObject == null  
  53.     && isSingletonCurrentlyInCreation(beanName)) { 
  54.    synchronized (this.singletonObjects) { 
  55.     if (singletonObject == null && allowEarlyReference) { 
  56.      ObjectFactory<?> singletonFactory = 
  57.        this.singletonFactories.get(beanName); 
  58.      if (singletonFactory != null) { 
  59.       singletonObject = singletonFactory.getObject(); 
  60.      } 
  61.     } 
  62.    } 
  63.   } 
  64.   return singletonObject; 
  65.  } 
  66.  
  67.  /** 
  68.   * 判斷bean是否正在被初始化 
  69.   */ 
  70.  public boolean isSingletonCurrentlyInCreation(String beanName) { 
  71.   return this.singletonsCurrentlyInCreation.contains(beanName); 
  72.  } 
  73.  

測試一波

  1. public static void main(String[] args) throws Exception { 
  2.  DependencyDemo dependencyDemo = new DependencyDemo(); 
  3.  // 假裝掃描出來的對象 
  4.  Class[] classes = {A.class, B.class}; 
  5.  // 假裝項目初始化所有bean 
  6.  for (Class aClass : classes) { 
  7.   dependencyDemo.getBean(aClass); 
  8.  } 
  9.  // true 
  10.  System.out.println( 
  11.    dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class)); 
  12.  // true 
  13.  System.out.println( 
  14.    dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class)); 

是不是很簡單?我們只用了2個map就搞定了Spring的循環(huán)依賴

2個Map就能搞定循環(huán)依賴,那為什么Spring要用3個Map呢?

原因其實也很簡單,當我們從singletonFactories中根據(jù)BeanName獲取相應的ObjectFactory,然后調(diào)用getObject()這個方法返回對應的Bean。在我們的例子中 ObjectFactory的實現(xiàn)很簡單哈,就是將實例化好的對象直接返回,但是在Spring中就沒有這么簡單了,執(zhí)行過程比較復雜,為了避免每次拿到ObjectFactory然后調(diào)用getObject(),我們直接把ObjectFactory創(chuàng)建的對象緩存起來不就行了,這樣就能提高效率了

比如A依賴B和C,B和C又依賴A,如果不做緩存那么初始化B和C都會調(diào)用A對應的ObjectFactory的getObject()方法。如果做緩存只需要B或者C調(diào)用一次即可。

知道了思路,我們把上面的代碼改一波,加個緩存。

  1. public class DependencyDemo { 
  2.  
  3.  // 初始化完畢的Bean 
  4.  private final Map<String, Object> singletonObjects = 
  5.    new ConcurrentHashMap<>(256); 
  6.  
  7.  // 正在初始化的Bean對應的工廠,此時對象已經(jīng)被實例化 
  8.  private final Map<String, ObjectFactory<?>> singletonFactories = 
  9.    new HashMap<>(16); 
  10.  
  11.  // 緩存Bean對應的工廠生產(chǎn)好的Bean 
  12.  private final Map<String, Object> earlySingletonObjects = 
  13.    new HashMap<>(16); 
  14.  
  15.  // 存放正在初始化的Bean,對象還沒有被實例化之前就放進來了 
  16.  private final Set<String> singletonsCurrentlyInCreation = 
  17.    Collections.newSetFromMap(new ConcurrentHashMap<>(16)); 
  18.  
  19.  public  <T> T getBean(Class<T> beanClass) throws Exception { 
  20.   // 類名為Bean的名字 
  21.   String beanName = beanClass.getSimpleName(); 
  22.   // 已經(jīng)初始化好了,或者正在初始化 
  23.   Object initObj = getSingleton(beanName, true); 
  24.   if (initObj != null) { 
  25.    return (T) initObj; 
  26.   } 
  27.   // bean正在被初始化 
  28.   singletonsCurrentlyInCreation.add(beanName); 
  29.   // 實例化bean 
  30.   Object object = beanClass.getDeclaredConstructor().newInstance(); 
  31.   singletonFactories.put(beanName, () -> { 
  32.    return object; 
  33.   }); 
  34.   // 開始初始化bean,即填充屬性 
  35.   Field[] fields = object.getClass().getDeclaredFields(); 
  36.   for (Field field : fields) { 
  37.    field.setAccessible(true); 
  38.    // 獲取需要注入字段的class 
  39.    Class<?> fieldClass = field.getType(); 
  40.    field.set(object, getBean(fieldClass)); 
  41.   } 
  42.   singletonObjects.put(beanName, object); 
  43.   singletonsCurrentlyInCreation.remove(beanName); 
  44.   earlySingletonObjects.remove(beanName); 
  45.   return (T) object; 
  46.  } 
  47.  
  48.  /** 
  49.   * allowEarlyReference參數(shù)的含義是Spring是否允許循環(huán)依賴,默認為true 
  50.   */ 
  51.  public Object getSingleton(String beanName, boolean allowEarlyReference) { 
  52.   Object singletonObject = this.singletonObjects.get(beanName); 
  53.   if (singletonObject == null 
  54.     && isSingletonCurrentlyInCreation(beanName)) { 
  55.    synchronized (this.singletonObjects) { 
  56.     singletonObject = this.earlySingletonObjects.get(beanName); 
  57.     if (singletonObject == null && allowEarlyReference) { 
  58.      ObjectFactory<?> singletonFactory = 
  59.        this.singletonFactories.get(beanName); 
  60.      if (singletonFactory != null) { 
  61.       singletonObject = singletonFactory.getObject(); 
  62.       this.earlySingletonObjects.put(beanName, singletonObject); 
  63.       this.singletonFactories.remove(beanName); 
  64.      } 
  65.     } 
  66.    } 
  67.   } 
  68.   return singletonObject; 
  69.  } 

我們寫的getSingleton的實現(xiàn)和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的實現(xiàn)一模一樣,這個方法幾乎所有分析Spring循環(huán)依賴的文章都會提到,這次你明白工作原理是什么了把

總結(jié)一波

拿bean的時候先從singletonObjects(一級緩存)中獲取

如果獲取不到,并且對象正在創(chuàng)建中,就從earlySingletonObjects(二級緩存)中獲取

如果還是獲取不到就從singletonFactories(三級緩存)中獲取,然后將獲取到的對象放到earlySingletonObjects(二級緩存)中,并且將bean對應的singletonFactories(三級緩存)清除

bean初始化完畢,放到singletonObjects(一級緩存)中,將bean對應的earlySingletonObjects(二級緩存)清除

 

責任編輯:武曉燕 來源: Java識堂
相關(guān)推薦

2023-01-26 00:22:01

分布式架構(gòu)大文件

2020-02-15 17:16:05

Kubernetes容器

2020-09-28 14:25:39

HTTPS加密算法

2021-10-21 08:31:31

Spring循環(huán)依賴面試

2019-12-27 09:47:05

大數(shù)據(jù)TomcatWeb

2022-07-04 08:31:42

GitOpsGit基礎(chǔ)設施

2020-01-21 10:16:15

Kubernetes教程容器

2019-10-08 10:10:52

中臺 IT后臺

2020-12-01 09:03:22

分庫分表MySQL

2019-11-26 14:30:20

Spring循環(huán)依賴Java

2018-11-21 09:40:57

熔斷實踐AOP

2018-11-21 15:40:08

HTTP協(xié)議前端

2023-10-07 08:40:57

緩存屬性Spring

2021-09-27 13:50:13

Python裝飾器函數(shù)

2019-09-05 11:14:12

監(jiān)控系統(tǒng)拓撲圖

2020-12-29 08:34:08

spring循環(huán)依賴開發(fā)

2022-08-17 07:52:31

Spring循環(huán)依賴單例池

2019-01-22 09:37:47

紅黑樹數(shù)據(jù)二叉樹

2021-09-01 10:13:07

數(shù)據(jù)庫面試節(jié)點

2023-11-28 08:00:00

SpringJava
點贊
收藏

51CTO技術(shù)棧公眾號