溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring怎么解決循環(huán)依賴的

發(fā)布時(shí)間:2021-11-12 16:21:46 來源:億速云 閱讀:164 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Spring怎么解決循環(huán)依賴的”,在日常操作中,相信很多人在Spring怎么解決循環(huán)依賴的問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Spring怎么解決循環(huán)依賴的”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

介紹

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

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

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

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

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

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

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

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

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

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

@Autowired public ConstructorB(@Lazy ConstructorA constructorA) {  this.constructorA = constructorA; }

因?yàn)槲覀冎饕P(guān)注屬性的循環(huán)依賴,構(gòu)造器的循環(huán)依賴就不做過多分析了

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

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

@Component public class FieldA {   @Autowired  private FieldB fieldB; } @Component public class FieldB {   @Autowired  private FieldA fieldA; } @Configuration @ComponentScan("com.javashitang.dependency.field") public class FieldConfig { } public class FieldMain {   public static void main(String[] args) {   AnnotationConfigApplicationContext context =     new AnnotationConfigApplicationContext(FieldConfig.class);   // com.javashitang.dependency.field.FieldA@3aa9e816   System.out.println(context.getBean(FieldA.class));   // com.javashitang.dependency.field.FieldB@17d99928   System.out.println(context.getBean(FieldB.class));  } }

Spring容器正常啟動(dòng),能獲取到FieldA和FieldB這2個(gè)Bean

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

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

  1. bean的實(shí)例化過程,即調(diào)用構(gòu)造函數(shù)將對象創(chuàng)建出來

  2. bean的初始化過程,即填充bean的各種屬性

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

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

public interface ObjectFactory<T> {  T getObject(); } public class DependencyDemo {   // 初始化完畢的Bean  private final Map<String, Object> singletonObjects =    new ConcurrentHashMap<>(256);   // 正在初始化的Bean對應(yīng)的工廠,此時(shí)對象已經(jīng)被實(shí)例化  private final Map<String, ObjectFactory<?>> singletonFactories =    new HashMap<>(16);   // 存放正在初始化的Bean,對象還沒有被實(shí)例化之前就放進(jìn)來了  private final Set<String> singletonsCurrentlyInCreation =    Collections.newSetFromMap(new ConcurrentHashMap<>(16));   public  <T> T getBean(Class<T> beanClass) throws Exception {   // 類名為Bean的名字   String beanName = beanClass.getSimpleName();   // 已經(jīng)初始化好了,或者正在初始化   Object initObj = getSingleton(beanName, true);   if (initObj != null) {    return (T) initObj;   }   // bean正在被初始化   singletonsCurrentlyInCreation.add(beanName);   // 實(shí)例化bean   Object object = beanClass.getDeclaredConstructor().newInstance();   singletonFactories.put(beanName, () -> {    return object;   });   // 開始初始化bean,即填充屬性   Field[] fields = object.getClass().getDeclaredFields();   for (Field field : fields) {    field.setAccessible(true);    // 獲取需要注入字段的class    Class<?> fieldClass = field.getType();    field.set(object, getBean(fieldClass));   }   // 初始化完畢   singletonObjects.put(beanName, object);   singletonsCurrentlyInCreation.remove(beanName);   return (T) object;  }   /**   * allowEarlyReference參數(shù)的含義是Spring是否允許循環(huán)依賴,默認(rèn)為true   * 所以當(dāng)allowEarlyReference設(shè)置為false的時(shí)候,當(dāng)項(xiàng)目存在循環(huán)依賴,會(huì)啟動(dòng)失敗   */  public Object getSingleton(String beanName, boolean allowEarlyReference) {   Object singletonObject = this.singletonObjects.get(beanName);   if (singletonObject == null      && isSingletonCurrentlyInCreation(beanName)) {    synchronized (this.singletonObjects) {     if (singletonObject == null && allowEarlyReference) {      ObjectFactory<?> singletonFactory =        this.singletonFactories.get(beanName);      if (singletonFactory != null) {       singletonObject = singletonFactory.getObject();      }     }    }   }   return singletonObject;  }   /**   * 判斷bean是否正在被初始化   */  public boolean isSingletonCurrentlyInCreation(String beanName) {   return this.singletonsCurrentlyInCreation.contains(beanName);  }  }

測試一波

public static void main(String[] args) throws Exception {  DependencyDemo dependencyDemo = new DependencyDemo();  // 假裝掃描出來的對象  Class[] classes = {A.class, B.class};  // 假裝項(xiàng)目初始化所有bean  for (Class aClass : classes) {   dependencyDemo.getBean(aClass);  }  // true  System.out.println(    dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class));  // true  System.out.println(    dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class)); }

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

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

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

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

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

public class DependencyDemo {   // 初始化完畢的Bean  private final Map<String, Object> singletonObjects =    new ConcurrentHashMap<>(256);   // 正在初始化的Bean對應(yīng)的工廠,此時(shí)對象已經(jīng)被實(shí)例化  private final Map<String, ObjectFactory<?>> singletonFactories =    new HashMap<>(16);   // 緩存Bean對應(yīng)的工廠生產(chǎn)好的Bean  private final Map<String, Object> earlySingletonObjects =    new HashMap<>(16);   // 存放正在初始化的Bean,對象還沒有被實(shí)例化之前就放進(jìn)來了  private final Set<String> singletonsCurrentlyInCreation =    Collections.newSetFromMap(new ConcurrentHashMap<>(16));   public  <T> T getBean(Class<T> beanClass) throws Exception {   // 類名為Bean的名字   String beanName = beanClass.getSimpleName();   // 已經(jīng)初始化好了,或者正在初始化   Object initObj = getSingleton(beanName, true);   if (initObj != null) {    return (T) initObj;   }   // bean正在被初始化   singletonsCurrentlyInCreation.add(beanName);   // 實(shí)例化bean   Object object = beanClass.getDeclaredConstructor().newInstance();   singletonFactories.put(beanName, () -> {    return object;   });   // 開始初始化bean,即填充屬性   Field[] fields = object.getClass().getDeclaredFields();   for (Field field : fields) {    field.setAccessible(true);    // 獲取需要注入字段的class    Class<?> fieldClass = field.getType();    field.set(object, getBean(fieldClass));   }   singletonObjects.put(beanName, object);   singletonsCurrentlyInCreation.remove(beanName);   earlySingletonObjects.remove(beanName);   return (T) object;  }   /**   * allowEarlyReference參數(shù)的含義是Spring是否允許循環(huán)依賴,默認(rèn)為true   */  public Object getSingleton(String beanName, boolean allowEarlyReference) {   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);       this.singletonFactories.remove(beanName);      }     }    }   }   return singletonObject;  } }

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

到此,關(guān)于“Spring怎么解決循環(huán)依賴的”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI