`
高级java工程师
  • 浏览: 413169 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【第三章】 DI 之 3.2 循环依赖 ——跟我学spring3 .0

 
阅读更多

3.2.1  什么是循环依赖

       循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。如图3-5所示:

 

图3-5 循环引用

       循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

       Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类:

 

java代码:
  1. package cn.javass.spring.chapter3.bean;  
  2. public class CircleA {  
  3.     private CircleB circleB;  
  4.     public CircleA() {  
  5.     }  
  6.     public CircleA(CircleB circleB) {  
  7.         this.circleB = circleB;  
  8.     }  
  9. public void setCircleB(CircleB circleB)   
  10. {  
  11.         this.circleB = circleB;  
  12.     }  
  13. public void a() {  
  14.    circleB.b();  
  15. }  
  16. }  

 

java代码:
  1. package cn.javass.spring.chapter3.bean;  
  2. public class CircleB {  
  3.     private CircleC circleC;  
  4.     public CircleB() {  
  5.     }  
  6.     public CircleB(CircleC circleC) {  
  7.         this.circleC = circleC;  
  8.     }  
  9. public void setCircleC(CircleC circleC)   
  10. {  
  11.         this.circleC = circleC;  
  12.     }  
  13.     public void b() {  
  14.         circleC.c();  
  15.     }  
  16. }  

 

java代码:
  1. package cn.javass.spring.chapter3.bean;  
  2. public class CircleC {  
  3.     private CircleA circleA;  
  4.     public CircleC() {  
  5.     }  
  6.     public CircleC(CircleA circleA) {  
  7.         this.circleA = circleA;  
  8.     }  
  9. public void setCircleA(CircleA circleA)   
  10. {  
  11.         this.circleA = circleA;  
  12.     }  
  13.     public void c() {  
  14.         circleA.a();  
  15.     }  
  16. }  

 

3.2.2        Spring如何解决循环依赖

一、构造器循环依赖:表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。

Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

       1)首先让我们看一下配置文件(chapter3/circleInjectByConstructor.xml):

 

java代码:
  1.       
  2. <bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">  
  3. <constructor-arg index="0" ref="circleB"/>  
  4. </bean>  
  5. <bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">  
  6. <constructor-arg index="0" ref="circleC"/>  
  7. </bean>  
  8. <bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">  
  9. <constructor-arg index="0" ref="circleA"/>  
  10. </bean>  
  11.    

 

       2)写段测试代码(cn.javass.spring.chapter3.CircleTest)测试一下吧:

 

java代码:
  1. @Test(expected = BeanCurrentlyInCreationException.class)  
  2. public void testCircleByConstructor() throws Throwable {  
  3. try {  
  4.       new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");  
  5.     }  
  6.     catch (Exception e) {  
  7.       //因为要在创建circle3时抛出;  
  8.       Throwable e1 = e.getCause().getCause().getCause();  
  9.       throw e1;  
  10.     }  
  11. }  

 

 

       让我们分析一下吧:

       1、Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA” 标识符放到“当前创建Bean池”;

       2、Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB” 标识符放到“当前创建Bean池”;

3、Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC” 标识符放到“当前创建Bean池”;

4、到此为止Spring容器要去创建“circleA”Bean,发现该Bean 标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。

  

二、setter循环依赖:表示通过setter注入方式构成的循环依赖。

对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

       如下代码所示,通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。

 

java代码:
  1. addSingletonFactory(beanName, new ObjectFactory() {  
  2.     public Object getObject() throws BeansException {  
  3.         return getEarlyBeanReference(beanName, mbd, bean);  
  4.     }  
  5. });  
  6.    

 

       具体步骤如下:

       1、Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;

       2、Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;

       3、Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;

4、最后在依赖注入“circleB”和“circleA”,完成setter注入。

 

       对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

 

java代码:
  1. <!-- 定义Bean配置文件,注意scope都是“prototype”-->  
  2. <bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA" scope="prototype">  
  3.         <property name="circleB" ref="circleB"/>  
  4.    </bean>  
  5.    <bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB" scope="prototype">  
  6.        <property name="circleC" ref="circleC"/>  
  7.    </bean>  
  8.    <bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC" scope="prototype">  
  9.        <property name="circleA" ref="circleA"/>  
  10.    </bean>  

 

 

java代码:
  1. //测试代码cn.javass.spring.chapter3.CircleTest  
  2. @Test(expected = BeanCurrentlyInCreationException.class)  
  3. public void testCircleBySetterAndPrototype () throws Throwable {  
  4.     try {  
  5.         ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(  
  6. "chapter3/circleInjectBySetterAndPrototype.xml");  
  7.         System.out.println(ctx.getBean("circleA"));  
  8.     }  
  9.     catch (Exception e) {  
  10.         Throwable e1 = e.getCause().getCause().getCause();  
  11.         throw e1;  
  12.     }  
  13. }  

 

       对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用:

 

java代码:
  1. @Test(expected = BeanCurrentlyInCreationException.class)  
  2. public void testCircleBySetterAndSingleton2() throws Throwable {  
  3.     try {  
  4.         ClassPathXmlApplicationContext ctx =  
  5. new ClassPathXmlApplicationContext();  
  6.         ctx.setConfigLocation("chapter3/circleInjectBySetterAndSingleton.xml");  
  7.         ctx.refresh();  
  8.     }  
  9.     catch (Exception e) {  
  10.         Throwable e1 = e.getCause().getCause().getCause();  
  11.         throw e1;  
  12.     }  
  13. }  

 

补充:出现循环依赖是设计上的问题,一定要避免!

请参考《敏捷软件开发:原则、模式与实践》中的“无环依赖”原则

包之间的依赖结构必须是一个直接的无环图形(DAG)。也就是说,在依赖结构中不允许出现环(循环依赖)。 
 
分享到:
评论

相关推荐

    跟开涛学Spring

    1.5 【第三章】 DI 之 3.2 循环依赖 ——跟我学spring3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 1.6 【第三章】 DI 之 3.1 DI的配置使用 ——跟我学spring3 . . . . . . ...

    跟我学spring

    标题“跟我学spring”和描述“spring 的使用,每个知识点和项目中的运用,20章的介绍。”暗示这是一份详细介绍Spring框架使用方法的教程或手册,覆盖了Spring的各个方面,并以实例为导向,深入探讨了每个知识点在...

    python大学生就业信息管理系统(django(完整前后端+mysql+说明文档+LW).zip

    python大学生就业信息管理系统(django(完整前后端+mysql+说明文档+LW).zip

    基于C语言的通讯录文件管理设计源码

    该项目为基于C语言的通讯录文件管理系统设计源码,共包含40个文件,其中包括6个日志文件、5个索引文件、5个编译信息文件、2个项目文件、2个数据库文件、2个可执行文件、2个源代码文件、1个可执行文件和1个解决方案文件。系统旨在提供高效的通讯录文件管理功能。

    计算机毕业设计:基于java web的公益网站的设计.zip

    计算机科学与技术专业毕业设计项目的核心是基于 Java 与 MySQL 开发,前端运用 jQuery、HTML、CSS 等技术的源码。它涵盖供应链系统、网上书城、ERP 系统、博客系统等众多典型项目,几乎覆盖计算机专业应用的各个领域。 这些源码价值极高,能助力学生快速掌握相关技术知识,提供参考范例与创新思路。其包含完整代码、详细文档、直观演示和全面说明,帮助学生深入了解项目实现过程与技术细节。 这些珍贵的毕业设计源码,大多由学生或导师无私上传至互联网供人自由使用,形成知识共享、共同进步的生态,推动了计算机技术在教育领域的传承与发展。

    JAVA源码 + SpringBoot+vue+mysql 课程答疑系统 + 数据库 + 文档

    本课程答疑系统有管理员,老师,学生三个角色。管理员功能有,个人中心,学生管理,老师过来,科目类型管理,学生问题管理,老师回答管理,老师信息管理,关注列表管理,交流区,系统管理。学生功能有,个人中心,科目类型管理,学生问题管理,老师回答管理,老师信息管理,关注列表管理,我的收藏管理。老师功能有,个人中心,老师回答管理,老师信息管理,关注列表管理,我的收藏管理。 内含文档,可轻松上手。

    【weixin9181】基于微信小程序的乡村政务服务系统springboot.zip

    技术选型 【后端】:Java 【框架】:springboot/ssm 【前端】:小程序 【JDK版本】:JDK1.8 【服务器】:tomcat7+ 【数据库】:mysql 5.7+ 资源包含项目源码+数据库脚本+万字文档。 项目包含前后台完整源码,都经过本人调试,确保可以正常运行! 具体项目介绍可查看博主文章或私聊获取。 也可提供远程调试、二次开发、项目讲解服务,有意向可私聊。 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧!

    TRichView v23.0.1 for Delphi 11-12 Athens Full Source.7z

    TRichView v23.0.1 for Delphi 11-12 Athens Full Source.7z

    【weixin9229】学生资助在线管理软件开发微信小程序ssm.zip

    技术选型 【后端】:Java 【框架】:springboot/ssm 【前端】:小程序 【JDK版本】:JDK1.8 【服务器】:tomcat7+ 【数据库】:mysql 5.7+ 压缩包包含项目源码+数据库脚本+万字文档。 项目包含前后台完整源码,都经过本人调试,确保可以正常运行! 具体项目介绍可查看博主文章或私聊获取。 也可提供远程调试、二次开发、项目讲解服务,有意向可私聊。 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧!

    风光柴储微电网经济调度优化:基于Matlab的智能求解方法与环保经济双赢之道,风光柴储微电网经济调度优化研究:基于Matlab平台的求解与应用,风光柴储微电网经济调度(Matlab) 微电网优化调度作

    风光柴储微电网经济调度优化:基于Matlab的智能求解方法与环保经济双赢之道,风光柴储微电网经济调度优化研究:基于Matlab平台的求解与应用,风光柴储微电网经济调度(Matlab) 微电网优化调度作为智能电网优化的重要组成部分,对降低能耗、环境污染具有重要 意义。 微电网的发展目标既要满足电力供应的基本需求,又要提高经济效益和环境保护。 本文基于Matlab平台,使用Yalmip+Cplex对微电网进行最优化调度问题求解,调度求解的过程较为简洁方便,可修改性强。 ,微电网优化调度;风光柴储系统;经济调度;Matlab平台;Yalmip+Cplex求解;简洁方便可修改性强;环境保护。,Matlab下的风光柴储微电网经济调度优化研究

    制作PPT指南:构建高效的人工智能主题演示文稿的要素与步骤

    内容概要:本文详细介绍了制作PPT所需的基本要素与具体的结构范例。从PPT各部分构成要素出发——封面页、目录页、内容页、总结页到问答页逐一进行了阐述并给出了具体案例。以“人工智能与未来”为主题,提供了每一页可能涵盖的内容范围和结构安排,有助于提高PPT制作者对于内容规划、素材选取以及页面布局的理解。

    耳机拆机视频,可以看看

    拆机视频耶耶耶

    linux环境下Levmar-2.6编译好静态库

    linux环境下Levmar-2.6编译好静态库,测试成功

    【weixin9221】新冠肺炎服务预约微信小程序的设计与实现ssm.zip

    技术选型 【后端】:Java 【框架】:springboot/ssm 【前端】:小程序 【JDK版本】:JDK1.8 【服务器】:tomcat7+ 【数据库】:mysql 5.7+ 压缩包包含项目源码+数据库脚本+万字文档。 项目包含前后台完整源码,都经过本人调试,确保可以正常运行! 具体项目介绍可查看博主文章或私聊获取。 也可提供远程调试、二次开发、项目讲解服务,有意向可私聊。 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧!

    智慧产业园区解决方案Word(181页).docx

    智慧产业园区作为城市发展的新亮点,正逐步成为推动区域经济转型升级和创新增长的重要引擎。在国家数字红利持续释放和智慧城市发展环境不断优化的背景下,智慧产业园区的建设迎来了前所未有的发展机遇。从《国家新型城镇化规划(2014-2020)》到《中国制造2025》,再到中央城市工作会议和十三五规划中提出的建设一批新型示范性智慧城市,一系列政策文件的出台为智慧产业园区的建设提供了坚实的政策支撑。 智慧产业园区的核心在于“硬”与“软”基础设施的全面建设。硬件设备方面,物联网、大数据、云计算、人工智能等先进技术的应用,为园区打造了高效、智能的信息基础设施。这些技术不仅提升了园区的运营效率,还为企业提供了便捷、高效的服务。例如,通过智能感知网和物联感知平台,园区能够实时采集和传输各类数据,实现全量数据的互联共享。而软件系统的建设,则更加注重服务的人性化和管理的科学化。智慧园区通过构建公共服务平台、公共技术平台以及各领域的智慧系统,为园区企业、从业人员提供了全方位的公共服务。这些服务不仅涵盖了基础服务和增值服务,还通过线上线下相结合的方式,满足了不同用户群体的多样化需求。同时,智慧园区还注重生态节能环保的基础设施资源建设,以及规划法规标准构成的政策资源、服务组织构成的社会资源等多元资源的整合利用,为园区的可持续发展奠定了坚实基础。 在智慧产业园区的运营过程中,管理者充分利用大数据应用与分析、预测预警、可视指挥等智能化手段,实现了园区管理的科学化、精准化。通过一张图直观展示人、地、物、事、情、组织等资源分布,管理者能够足不出户就能知晓园区资源使用情况。而通过对园区产业数据的深度挖掘、分析、研判,管理者能够掌握园区产业趋势,为科学决策提供有力支撑。此外,智慧园区还注重人文社区的建设,通过提供园区服务通知、园区活动在线咨询、政策法规资料下载等多元化服务,营造了良好的社区氛围。在车辆管理、物业管理等方面,智慧园区也引入了智能化手段,提升了用户体验和园区运营效率。例如,通过智能停车管理系统,园区能够快速寻找停车位,减少停车场拥堵和人为指引调度工作;而端到端的线上物业管理应用,则让物业服务更加便捷、高效。 综上所述,智慧产业园区作为智慧城市的重要组成部分,正以其独特的魅力和无限的潜力吸引着越来越多的关注。通过全面建设“硬”与“软”基础设施、整合多元资源、利用智能化手段提升管理效率,智慧产业园区正逐步成为推动区域经济高质量发展的新引擎。未来,随着技术的不断进步和应用场景的不断拓展,智慧产业园区将迎来更加广阔的发展前景。

    顶顶顶的方法2233111

    fff烦烦烦

    自动驾驶时代的数据管理与服务框架:数据采集、存储及服务一体化解决方案,自动驾驶数据采集、存储与服务框架:技术领导者的必备指南,自动驾驶数据采集及数据服务框架 , 倘若你是产品经理或者项目经理又或者是

    自动驾驶时代的数据管理与服务框架:数据采集、存储及服务一体化解决方案,自动驾驶数据采集、存储与服务框架:技术领导者的必备指南,自动驾驶数据采集及数据服务框架 , 倘若你是产品经理或者项目经理又或者是技术leader,这个将帮您梳理数据采集、数据存储及数据服务的大框架。 倘若您是追求具体技术实现算法细节而不太关注大架构的技术人员,可能这个不太适合,这也是需要仔细考虑的。 ,核心关键词:自动驾驶;数据采集;数据存储;数据服务;框架;产品经理/项目经理/技术leader。,自动驾驶数据管理与服务架构解决方案

    基于FPGA的Cortex-M3软核OV5640摄像头采集与HDMI输出工程实现及开发文档附带,基于FPGA的Cortex-M3软核OV5640摄像头采集与HDMI输出工程实现及开发文档分享,基于FP

    基于FPGA的Cortex-M3软核OV5640摄像头采集与HDMI输出工程实现及开发文档附带,基于FPGA的Cortex-M3软核OV5640摄像头采集与HDMI输出工程实现及开发文档分享,基于FPGA的Cortex-M3软核OV5640摄像头采集工程 实现基于FPGA的Cortex-M3软核基本SOC,系统外设包括GPIO和UART串口和OV5640摄像头,实现摄像头采集和HDMI接口图像输出。 开发基于vivado2019.2和vitis,理论上可适用于任何版本的vivado,软件工程基于Keil设计,并且附带本人编写的详细开发文档,能够快速完成工程的移植。 在该工程基础上可进一步开发更多的功能。 ,基于FPGA的Cortex-M3软核; OV5640摄像头采集; HDMI图像输出; vivado2019.2开发; vitis软件工程; Keil设计。,基于FPGA的Cortex-M3软核与OV5640摄像头采集系统开发工程:HDMI输出,全面移植可升级

    基于Objective-C和C语言的starrtc-ios即时通讯系统设计源码

    该项目为基于Objective-C核心的starrtc-ios即时通讯系统设计源码,涵盖898个文件,其中包括247个头文件(h)、211个PNG图片文件、195个Objective-C源文件(m)、163个JSON文件、46个XIB界面文件、9个JPG图片文件、5个Property List文件(plist)、3个Storyboard文件以及2个Markdown文件(md)。系统支持单聊、群聊、聊天室、文件传输等功能,并集成了免费一对一视频聊天、VOIP语音对讲、直播连麦、视频直播、RTSP/RTMP流媒体处理以及WebRTC服务等先进技术,适用于在线教育、会议、小班课等多种场景。

    基于Python的Django-html特色饮食情感分析语料库模型建立源码.zip

    基于Python的Django-html特色饮食情感分析语料库模型 系统选用B/S模式,应用Django框架, MySQL为后台数据库。

Global site tag (gtag.js) - Google Analytics