色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁技術(shù)文章
文章詳情頁

Spring循環(huán)依賴的解決辦法,你真的懂了嗎

瀏覽:4日期:2023-09-01 09:55:50

Spring循環(huán)依賴的解決辦法,你真的懂了嗎

介紹

先說一下什么是循環(huán)依賴,循壞依賴即循環(huán)引用,兩個(gè)或多個(gè)bean相互引用,最終形成一個(gè)環(huán)。Spring在初始化A的時(shí)候需要注入B,而初始化B的時(shí)候需要注入A,在Spring啟動(dòng)后這2個(gè)Bean都要被初始化完成

Spring循環(huán)依賴的解決辦法,你真的懂了嗎

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

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

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

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

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

@Componentpublic class ConstructorA { private ConstructorB constructorB; @Autowired public ConstructorA(ConstructorB constructorB) { this.constructorB = constructorB; }}

@Componentpublic 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注解就可以解決

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

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

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

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

@Componentpublic class FieldA { @Autowired private FieldB fieldB;}

@Componentpublic 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部分

bean的實(shí)例化過程,即調(diào)用構(gòu)造函數(shù)將對(duì)象創(chuàng)建出來 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對(duì)應(yīng)的工廠,此時(shí)對(duì)象已經(jīng)被實(shí)例化 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 存放正在初始化的Bean,對(duì)象還沒有被實(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();// 假裝掃描出來的對(duì)象Class[] classes = {A.class, B.class};// 假裝項(xiàng)目初始化所有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個(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è)方法返回對(duì)應(yīng)的Bean。在我們的例子中ObjectFactory的實(shí)現(xiàn)很簡單哈,就是將實(shí)例化好的對(duì)象直接返回,但是在Spring中就沒有這么簡單了,執(zhí)行過程比較復(fù)雜,為了避免每次拿到ObjectFactory然后調(diào)用getObject(),我們直接把ObjectFactory創(chuàng)建的對(duì)象緩存起來不就行了,這樣就能提高效率了

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

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

public class DependencyDemo {// 初始化完畢的Beanprivate final Map<String, Object> singletonObjects =new ConcurrentHashMap<>(256);// 正在初始化的Bean對(duì)應(yīng)的工廠,此時(shí)對(duì)象已經(jīng)被實(shí)例化private final Map<String, ObjectFactory<?>> singletonFactories =new HashMap<>(16);// 緩存Bean對(duì)應(yīng)的工廠生產(chǎn)好的Beanprivate final Map<String, Object> earlySingletonObjects =new HashMap<>(16);// 存放正在初始化的Bean,對(duì)象還沒有被實(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í)例化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參數(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ì)提到,這次你明白工作原理是什么了把

總結(jié)一波

拿bean的時(shí)候先從singletonObjects(一級(jí)緩存)中獲取 如果獲取不到,并且對(duì)象正在創(chuàng)建中,就從earlySingletonObjects(二級(jí)緩存)中獲取 如果還是獲取不到就從singletonFactories(三級(jí)緩存)中獲取,然后將獲取到的對(duì)象放到earlySingletonObjects(二級(jí)緩存)中,并且將bean對(duì)應(yīng)的singletonFactories(三級(jí)緩存)清除 bean初始化完畢,放到singletonObjects(一級(jí)緩存)中,將bean對(duì)應(yīng)的earlySingletonObjects(二級(jí)緩存)清除

參考博客

[1]https://mp.weixin.qq.com/s/gBr3UfC1HRcw4U-ZMmtRaQ[2]https://mp.weixin.qq.com/s/5mwkgJB7GyLdKDgzijyvXw比較詳細(xì)[1]https://zhuanlan.zhihu.com/p/84267654[2]https://juejin.im/post/5c98a7b4f265da60ee12e9b2

到此這篇關(guān)于Spring循環(huán)依賴的解決辦法,你真的懂了嗎的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 日本三级香港三级三级人!妇久 | 私人午夜影院 | 91久久精品国产亚洲 | 久久精品亚洲精品国产欧美 | 亚洲国产成人久久综合一区77 | 日本在线观看www免费 | 99在线观看免费视频 | 久草在线新视觉 | 久久思思爱 | 成人韩免费网站 | 欧美午夜视频在线 | 成年大片免费视频播放手机不卡 | 欧美精品亚洲一区二区在线播放 | 亚洲国产综合精品 | 999国内精品永久免费视频 | 亚洲haose在线观看 | 久久久99精品免费观看 | 伊大人香蕉久久网欧美 | 成人性色生活影片 | 99爱在线视频 | 日本高清www片 | 久久一本精品久久精品66 | 欧美精品一区二区在线观看播放 | 乱子伦农村xxxx | 国产99久9在线视频 国产99久久 | 亚洲欧洲一区二区三区在线 | 国产精品视频免费一区二区三区 | 免费看一级做a爰片久久 | 国产精品99在线观看 | 中文字幕人成乱码在线观看 | 91视频久久| 欧美aaa视频 | 中国高清色视频www 中国黄色网址大全 | 国产日韩精品欧美一区喷 | 成人自拍视频网站 | 女人张开腿 让男人桶视频 女人张开腿等男人桶免费视频 | 久久精品国产亚洲片 | 亚洲成a人片在线观看中 | 日韩在线手机看片免费看 | 一级午夜a毛片免费视频 | 全国男人的天堂网站 |