javascript
女朋友都能看懂,Spring如何解决循环依赖?
介紹
先說一下什么是循環依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成
Spring的循環依賴有兩種場景
構造器的循環依賴
屬性的循環依賴
構造器的循環依賴,可以在構造函數中使用@Lazy注解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再創建對象完成注入
屬性的循環依賴主要是通過3個map來解決的
構造器的循環依賴
@Component public?class?ConstructorA?{private?ConstructorB?constructorB;@Autowiredpublic?ConstructorA(ConstructorB?constructorB)?{this.constructorB?=?constructorB;} } @Component public?class?ConstructorB?{private?ConstructorA?constructorA;@Autowiredpublic?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));} }運行ConstructorMain的main方法的時候會在第一行就報異常,說明Spring沒辦法初始化所有的Bean,即上面這種形式的循環依賴Spring無法解決。
附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,筆者這幾年及春招的總結,github 1.1k star,拿去不謝!下載方式1.?首先掃描下方二維碼 2.?后臺回復「Java面試」即可獲取我們可以在ConstructorA或者ConstructorB構造函數的參數上加上@Lazy注解就可以解決
@Autowired public?ConstructorB(@Lazy?ConstructorA?constructorA)?{this.constructorA?=?constructorA; }因為我們主要關注屬性的循環依賴,構造器的循環依賴就不做過多分析了
屬性的循環依賴
先演示一下什么是屬性的循環依賴
@Component public?class?FieldA?{@Autowiredprivate?FieldB?fieldB; } @Component public?class?FieldB?{@Autowiredprivate?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@3aa9e816System.out.println(context.getBean(FieldA.class));//?com.javashitang.dependency.field.FieldB@17d99928System.out.println(context.getBean(FieldB.class));} }Spring容器正常啟動,能獲取到FieldA和FieldB這2個Bean
屬性的循環依賴在面試中還是經常被問到的。總體來說也不復雜,但是涉及到Spring Bean的初始化過程,所以感覺比較復雜,我寫個demo演示一下整個過程
Spring的Bean的初始化過程其實比較復雜,為了方便理解Demo,我就把Spring Bean的初始化過程分為2部分
bean的實例化過程,即調用構造函數將對象創建出來
bean的初始化過程,即填充bean的各種屬性
bean初始化過程完畢,則bean就能被正常創建出來了
下面開始寫Demo,ObjectFactory接口用來生產Bean,和Spring中定義的接口一樣
public?interface?ObjectFactory<T>?{T?getObject(); } public?class?DependencyDemo?{//?初始化完畢的Beanprivate?final?Map<String,?Object>?singletonObjects?=new?ConcurrentHashMap<>(256);//?正在初始化的Bean對應的工廠,此時對象已經被實例化private?final?Map<String,?ObjectFactory<?>>?singletonFactories?=new?HashMap<>(16);//?存放正在初始化的Bean,對象還沒有被實例化之前就放進來了private?final?Set<String>?singletonsCurrentlyInCreation?=Collections.newSetFromMap(new?ConcurrentHashMap<>(16));public??<T>?T?getBean(Class<T>?beanClass)?throws?Exception?{//?類名為Bean的名字String?beanName?=?beanClass.getSimpleName();//?已經初始化好了,或者正在初始化Object?initObj?=?getSingleton(beanName,?true);if?(initObj?!=?null)?{return?(T)?initObj;}//?bean正在被初始化singletonsCurrentlyInCreation.add(beanName);//?實例化beanObject?object?=?beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName,?()?->?{return?object;});//?開始初始化bean,即填充屬性Field[]?fields?=?object.getClass().getDeclaredFields();for?(Field?field?:?fields)?{field.setAccessible(true);//?獲取需要注入字段的classClass<?>?fieldClass?=?field.getType();field.set(object,?getBean(fieldClass));}//?初始化完畢singletonObjects.put(beanName,?object);singletonsCurrentlyInCreation.remove(beanName);return?(T)?object;}/***?allowEarlyReference參數的含義是Spring是否允許循環依賴,默認為true*?所以當allowEarlyReference設置為false的時候,當項目存在循環依賴,會啟動失敗*/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};//?假裝項目初始化所有beanfor?(Class?aClass?:?classes)?{dependencyDemo.getBean(aClass);}//?trueSystem.out.println(dependencyDemo.getBean(B.class).getA()?==?dependencyDemo.getBean(A.class));//?trueSystem.out.println(dependencyDemo.getBean(A.class).getB()?==?dependencyDemo.getBean(B.class)); }是不是很簡單?我們只用了2個map就搞定了Spring的循環依賴
2個Map就能搞定循環依賴,那為什么Spring要用3個Map呢?
原因其實也很簡單,當我們從singletonFactories中根據BeanName獲取相應的ObjectFactory,然后調用getObject()這個方法返回對應的Bean。在我們的例子中 ObjectFactory的實現很簡單哈,就是將實例化好的對象直接返回,但是在Spring中就沒有這么簡單了,執行過程比較復雜,為了避免每次拿到ObjectFactory然后調用getObject(),我們直接把ObjectFactory創建的對象緩存起來不就行了,這樣就能提高效率了
比如A依賴B和C,B和C又依賴A,如果不做緩存那么初始化B和C都會調用A對應的ObjectFactory的getObject()方法。如果做緩存只需要B或者C調用一次即可。
知道了思路,我們把上面的代碼改一波,加個緩存。
public?class?DependencyDemo?{//?初始化完畢的Beanprivate?final?Map<String,?Object>?singletonObjects?=new?ConcurrentHashMap<>(256);//?正在初始化的Bean對應的工廠,此時對象已經被實例化private?final?Map<String,?ObjectFactory<?>>?singletonFactories?=new?HashMap<>(16);//?緩存Bean對應的工廠生產好的Beanprivate?final?Map<String,?Object>?earlySingletonObjects?=new?HashMap<>(16);//?存放正在初始化的Bean,對象還沒有被實例化之前就放進來了private?final?Set<String>?singletonsCurrentlyInCreation?=Collections.newSetFromMap(new?ConcurrentHashMap<>(16));public??<T>?T?getBean(Class<T>?beanClass)?throws?Exception?{//?類名為Bean的名字String?beanName?=?beanClass.getSimpleName();//?已經初始化好了,或者正在初始化Object?initObj?=?getSingleton(beanName,?true);if?(initObj?!=?null)?{return?(T)?initObj;}//?bean正在被初始化singletonsCurrentlyInCreation.add(beanName);//?實例化beanObject?object?=?beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName,?()?->?{return?object;});//?開始初始化bean,即填充屬性Field[]?fields?=?object.getClass().getDeclaredFields();for?(Field?field?:?fields)?{field.setAccessible(true);//?獲取需要注入字段的classClass<?>?fieldClass?=?field.getType();field.set(object,?getBean(fieldClass));}singletonObjects.put(beanName,?object);singletonsCurrentlyInCreation.remove(beanName);earlySingletonObjects.remove(beanName);return?(T)?object;}/***?allowEarlyReference參數的含義是Spring是否允許循環依賴,默認為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的實現和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的實現一模一樣,這個方法幾乎所有分析Spring循環依賴的文章都會提到,這次你明白工作原理是什么了把
總結一波
拿bean的時候先從singletonObjects(一級緩存)中獲取
如果獲取不到,并且對象正在創建中,就從earlySingletonObjects(二級緩存)中獲取
如果還是獲取不到就從singletonFactories(三級緩存)中獲取,然后將獲取到的對象放到earlySingletonObjects(二級緩存)中,并且將bean對應的singletonFactories(三級緩存)清除
bean初始化完畢,放到singletonObjects(一級緩存)中,將bean對應的earlySingletonObjects(二級緩存)清除
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的女朋友都能看懂,Spring如何解决循环依赖?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 执行COUNT(1)、COUNT(*)
- 下一篇: 在滴滴和头条干了 2 年后端开发,太真实