电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Spring循環依賴的解決辦法,你真的懂了嗎

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

Spring循環依賴的解決辦法,你真的懂了嗎

介紹

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

Spring循環依賴的解決辦法,你真的懂了嗎

Spring的循環依賴有兩種場景

構造器的循環依賴 屬性的循環依賴

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

屬性的循環依賴主要是通過3個map來解決的

構造器的循環依賴

@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)); }}

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

我們可以在ConstructorA或者ConstructorB構造函數的參數上加上@Lazy注解就可以解決

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

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

屬性的循環依賴

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

@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容器正常啟動,能獲取到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 { // 初始化完畢的Bean private 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); // 實例化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參數的含義是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(二級緩存)清除

參考博客

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

到此這篇關于Spring循環依賴的解決辦法,你真的懂了嗎的文章就介紹到這了,更多相關Spring循環依賴內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Spring
相關文章:
主站蜘蛛池模板: 吊篮式|移动式冷热冲击试验箱-二槽冷热冲击试验箱-广东科宝 | 飞利浦LED体育场灯具-吸顶式油站灯-飞利浦LED罩棚灯-佛山嘉耀照明有限公司 | FAG轴承,苏州FAG轴承,德国FAG轴承-恩梯必传动设备(苏州)有限公司 | 胶水,胶粘剂,AB胶,环氧胶,UV胶水,高温胶,快干胶,密封胶,结构胶,电子胶,厌氧胶,高温胶水,电子胶水-东莞聚力-聚厉胶粘 | 理化生实验室设备,吊装实验室设备,顶装实验室设备,实验室成套设备厂家,校园功能室设备,智慧书法教室方案 - 东莞市惠森教学设备有限公司 | 工业硝酸钠,硝酸钠厂家-淄博「文海工贸」| 高扬程排污泵_隔膜泵_磁力泵_节能自吸离心水泵厂家-【上海博洋】 | 口臭的治疗方法,口臭怎么办,怎么除口臭,口臭的原因-口臭治疗网 | 警方提醒:赣州约炮论坛真的安全吗?2025年新手必看的网络交友防坑指南 | 武汉天安盾电子设备有限公司 - 安盾安检,武汉安检门,武汉安检机,武汉金属探测器,武汉测温安检门,武汉X光行李安检机,武汉防爆罐,武汉车底安全检查,武汉液体探测仪,武汉安检防爆设备 | 二维运动混料机,加热型混料机,干粉混料机-南京腾阳干燥设备厂 | 专业的新乡振动筛厂家-振动筛品质保障-环保振动筛价格—新乡市德科筛分机械有限公司 | 气动绞车,山东气动绞车,气动绞车厂家-烟台博海石油机械有限公司 气动隔膜泵厂家-温州永嘉定远泵阀有限公司 | 雷蒙磨,雷蒙磨粉机,雷蒙磨机 - 巩义市大峪沟高峰机械厂 | 上海单片机培训|重庆曙海培训分支机构—CortexM3+uC/OS培训班,北京linux培训,Windows驱动开发培训|上海IC版图设计,西安linux培训,北京汽车电子EMC培训,ARM培训,MTK培训,Android培训 | 郑州大巴车出租|中巴车租赁|旅游大巴租车|包车|郑州旅游大巴车租赁有限公司 | 餐饮小吃技术培训-火锅串串香培训「何小胖培训」_成都点石成金[官网] | 篮球架_乒乓球台_足球门_校园_竞技体育器材_厂家_价格-沧州浩然体育器材有限公司 | 蓝牙音频分析仪-多功能-四通道-八通道音频分析仪-东莞市奥普新音频技术有限公司 | 强效碱性清洗剂-实验室中性清洗剂-食品级高纯氮气发生器-上海润榕科学器材有限公司 | 100_150_200_250_300_350_400公斤压力空气压缩机-舰艇航天配套厂家 | 食品机械专用传感器-落料放大器-低价接近开关-菲德自控技术(天津)有限公司 | IPO咨询公司-IPO上市服务-细分市场研究-龙马咨询 | 高温热泵烘干机,高温烘干热泵,热水设备机组_正旭热泵 | 汽液过滤网厂家_安平县银锐丝网有限公司 | 钢木实验台-全钢实验台-化验室通风柜-实验室装修厂家-杭州博扬实验设备 | 电磁流量计厂家_涡街流量计厂家_热式气体流量计-青天伟业仪器仪表有限公司 | 在线钠离子分析仪-硅酸根离子浓度测定仪-油液水分测定仪价格-北京时代新维测控设备有限公司 | 高低温万能试验机-复合材料万能试验机-馥勒仪器 | 油液红外光谱仪-油液监测系统-燃油嗅探仪-上海冉超光电科技有限公司 | 液晶拼接屏厂家_拼接屏品牌_拼接屏价格_监控大屏—北京维康 | 开平机_纵剪机厂家_开平机生产厂家|诚信互赢-泰安瑞烨精工机械制造有限公司 | 成都APP开发-成都App定制-成都app开发公司-【未来久】 | 长沙广告公司_制作,长沙喷绘_发光字_招牌制作_长沙泓润广告官网 长城人品牌官网 | 活性氧化铝球|氧化铝干燥剂|分子筛干燥剂|氢氧化铝粉-淄博同心材料有限公司 | 无锡网站建设-做网站-建网站-网页设计制作-阿凡达建站公司 | 招商帮-一站式网络营销服务|搜索营销推广|信息流推广|短视视频营销推广|互联网整合营销|网络推广代运营|招商帮企业招商好帮手 | 瓶盖扭矩测试仪-瓶盖扭力仪-全自动扭矩仪-济南三泉中石单品站 | 压片机_高速_单冲_双层_花篮式_多功能旋转压片机-上海天九压片机厂家 | 大_小鼠elisa试剂盒-植物_人Elisa试剂盒-PCR荧光定量试剂盒-上海一研生物科技有限公司 | 座椅式升降机_无障碍升降平台_残疾人升降平台-南京明顺机械设备有限公司 |