写作时间:2019-10-07
Spring Boot: 2.1 ,JDK: 1.8, IDE: IntelliJ IDEA

说明

什么是循环引用?
Bean A --> Bean B --> Bean A
当然如果引用的圈大一点也可以
Bean A --> Bean B --> Bean C --> Bean D --> Bean E --> Bean A

要解决循环引用的问题,要么设计上就禁止出现互相依赖的问题;要么就是把依赖圈中的某个节点设置为弱引用,也就是必须优先设置依赖的对方,如果依赖方已经释放,则弱引用方也被释放。

1. Spring中怎么会出现循环引用

当Spring context加载所有的beans时,它尝试按照顺序创建beans。比如,如果没有循环依赖的情况下,类似下面:

Bean A --> Bean B --> Bean C

Spring 会先创建bean C, 然后创建bean B(同时注入bean C到B),最后创建bean A(同时注入bean B到A)。

但是,如果是循环引用,Spring就决定不了应该先创建哪个bean。这种情况下,Spring在加载context的时候,就会抛出异常BeanCurrentlyInCreationException.

循环依赖只会发生在构造函数注入constructor injection; 如果你用其它方式的注入(比如属性注入),就不会出现循环依赖。因为依赖注入不是在context loading阶段,context加载结束后按需加载。

2. 工程建立

参照教程【SpringBoot 2.1 | 第一篇:构建第一个SpringBoot工程】新建一个Spring Boot项目,名字叫CircularDependency, 在目录src/main/java/resources 下找到配置文件application.properties,重命名为application.yml

3. 循环引用的例子

创建两个通过构造函数注入相互依赖的两个类
CircularDependencyA, CircularDependencyB

@Component
public class CircularDependencyA {private CircularDependencyB circB;@Autowiredpublic CircularDependencyA(CircularDependencyB circB) {this.circB = circB;}
}
@Component
public class CircularDependencyB {private CircularDependencyA circA;@Autowiredpublic CircularDependencyB(CircularDependencyA circA) {this.circA = circA;}
}

现在我们一个Configuration class来测试,命名为TestConfig。
这个类扫描指定package成为components。

@Configuration
@ComponentScan(basePackages = { "zgpeace.spring.circulardependency.circular" })
public class TestConfig {}

最后写个JUnit去测试循环依赖。 测试内容为空,因为循环依赖在context loading阶段就会检测到。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {@Testpublic void givenCircularDependency_whenConstructorInjection_thenItFails() {// Empty test; we just want the context to load}
}

运行Unit Test, 将会得到下面的报错信息

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4.1 解决方案:重新设计

如果你有一个循环依赖,说明设计有问题,从根源上重新设计就好。

4.2 解决方案:@Lazy

一个简单的方法就是在loading context的时候打断循环依赖,把其中一个beans声明为lazily。这样子就可以其中一环只有在第一次调用的时候,才会去创建。

@Component
public class CircularDependencyA {private CircularDependencyB circB;@Autowiredpublic CircularDependencyA(@Lazy CircularDependencyB circB) {this.circB = circB;}
}

4.3 解决方案:用Setter/Field Injeciton

比较流行的解决方案是,用Setter属性的注入,参考 Spring documentation proposes。把原来构造函数注入的方式,改为属性注入的方式,这样子beans只有在第一次调用的时候才会去加载。

两个Beans的实现改为如下

@Component
public class CircularDependencyA {private CircularDependencyB circB;@Autowiredpublic void setCircB(CircularDependencyB circB) {this.circB = circB;}public CircularDependencyB getCircB() {return circB;}
}
@Component
public class CircularDependencyB {private CircularDependencyA circA;private String message = "Hi!";@Autowiredpublic void setCircA(CircularDependencyA circA) {this.circA = circA;}public String getMessage() {return message;}
}

Unit Test 改写为如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {@AutowiredApplicationContext context;@Beanpublic CircularDependencyA getCircularDependencyA() {return new CircularDependencyA();}@Beanpublic CircularDependencyB getCircularDependencyB() {return new CircularDependencyB();}@Testpublic void givenCircularDependency_whenSetterInjection_thenItWorks() {CircularDependencyA circA = context.getBean(CircularDependencyA.class);Assert.assertEquals("Hi!", circA.getCircB().getMessage());}
}

解析:
@Bean: 告诉Spring framework,这些方法用来检索bean注入的初始化。
@Test:从context获取CircularDependencyA bean,断言CircularDependencyB已经被注入为A的属性,检查它的message属性。

4.4 解决方案:用@PostConstruct

另一种方式去打断循环依赖圈,一个bean声明@Autowired,另一个bean的方法声明@PostConstruct

@Component
public class CircularDependencyA {@Autowiredprivate CircularDependencyB circB;@PostConstructpublic void init() {circB.setCircA(this);}public CircularDependencyB getCircB() {return circB;}
}
@Component
public class CircularDependencyB {private CircularDependencyA circA;private String message = "Hi!";public void setCircA(CircularDependencyA circA) {this.circA = circA;}public String getMessage() {return message;}
}

4.5 解决方案:实现ApplicationContextAware 和 InitializingBean

如果一个bean实现了接口ApplicationContextAware,bean就可以获取到Spring context,并且能够解析bean。实现接口InitializingBean表明会有一些action,等所有的属性被设置以后。这样子,我们就可以手动设置依赖。

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {private CircularDependencyB circB;private ApplicationContext context;public CircularDependencyB getCircB() {return circB;}@Overridepublic void afterPropertiesSet() throws Exception {circB = context.getBean(CircularDependencyB.class);}@Overridepublic void setApplicationContext(final ApplicationContext ctx) throws BeansException {context = ctx;}
}
@Component
public class CircularDependencyB {private CircularDependencyA circA;private String message = "Hi!";@Autowiredpublic void setCircA(CircularDependencyA circA) {this.circA = circA;}public String getMessage() {return message;}
}

总结

恭喜你,学会了Circular Dependency循环依赖的产生原因,已经解决方案。
代码下载:

https://github.com/zgpeace/Spring-Boot2.1/tree/master/basic/CircularDependency

参考

https://www.baeldung.com/circular-dependencies-in-spring

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans

易筋SpringBoot 2.1 | 第廿八篇:SpringBoot之循环引用Circular Dependency相关推荐

  1. 第八篇!95后天才少年曹原再发Nature!

    来源:自科在线 编辑:nhyilin 2021年7月21日,"石墨烯驾驭者"曹原作为第一作者和通讯作者在国际顶尖学术期刊 Nature 发表了题为:Pauli-limit viol ...

  2. 八篇 NeurIPS 2019 最新图神经网络相关论文

    最近,人工智能和机器学习领域的国际顶级会议 NeurIPS 2019 接收论文公布,共有 1428 篇论文被接收.为了带大家抢先领略高质量论文,本文整理了八篇 NeurIPS 2019 最新 GNN ...

  3. flask 第八篇 实例化flask时的参数配置

    Flask 是一个非常灵活且短小精干的web框架 , 那么灵活性从什么地方体现呢? 有一个神奇的东西叫 Flask配置 , 这个东西怎么用呢? 它能给我们带来怎么样的方便呢? 首先展示一下: from ...

  4. [老老实实学WCF] 第八篇 实例化

    老老实实学WCF 第八篇 实例化 通过上一篇的学习,我们简单地了解了会话,我们知道服务端和客户端之间可以建立会话连接,也可以建立非会话连接,通信的绑定和服务协定的 ServiceContract 的S ...

  5. Python之路【第八篇】:堡垒机实例以及数据库操作

    Python之路[第八篇]:堡垒机实例以及数据库操作 堡垒机前戏 开发堡垒机之前,先来学习Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 SSHClient 用于 ...

  6. CCIE-LAB-第八篇-SDWAN-Branch1_Branch2_Vmanage

    CCIE-LAB-第八篇-SDWAN-Branch1_Branch2_Vmanage 实际中,思科只会给你5个小时去做下面的全部配置 这个是CCIE-LAB的拓扑图 题目 SECTION 2.5: H ...

  7. CCIE理论-第八篇-SD-WAN(三)+DAI(动态ARP检测)

    CCIE理论-第八篇-SD-WAN(三) 首先来说上一章的问题, vbound和vsmart还没出来 . 指定域和vbound 初始化搞完了,那么需要去添加vsmart和vbound了 添加设备 添加 ...

  8. CCIE-LAB-第八篇-OSPF前缀压制+MTU+路由汇总

    CCIE-LAB-第八篇-OSPF前缀压制+MTU+路由汇总 实际中,思科只会给你5个小时去做下面的全部配置 这个是CCIE-LAB的拓扑图 问题 翻译:1.交换机sw20l和sw202必须在全状态下 ...

  9. CCNA-第八篇-OSPF-上

    CCNA-第八篇-OSPF OSPF,最常用的路由协议,他来了他来了 OSPF呢怎么说呢 是一个比较重要而且比较基础的点,出到去外面要是说不会OSPF,那还算啥网络工程师 但是呢,他也不是那么的完全重 ...

  10. 玩转 SpringBoot 2 快速整合 | RESTful Api 篇

    概述 RESTful 是一种架构风格,任何符合 RESTful 风格的架构,我们都可以称之为 RESTful 架构.我们常说的 RESTful Api 是符合 RESTful 原则和约束的 HTTP ...

最新文章

  1. Redis3.0 集群
  2. c++ 遍历多级目录
  3. c/c++对象模型大总结:第5-8章、数据成员的存取与布局
  4. java选择循环_java选择和循环结构
  5. 解决点击事件让背景变红,在点击背景变正常,并且可以多选的效果
  6. 详解学习C#的方法和步骤
  7. 锁到底是一种怎样的存在?
  8. 车牌识别及验证码识别的一般思路
  9. 微信小程序跳过第三方的_微信小程序工具 第三方平台
  10. 量子计算机组运算极限,拓扑量子计算
  11. BUUCTF------相册
  12. python下载快手视频教程_python如何下载快手视频
  13. HDU操作系统课程设计实验一
  14. alphacam图库@橱柜门专用alphacam图
  15. 创业公司早期融资应该怎么写商业计划书
  16. pdfobject.js和pdf.js的详解
  17. document.writeln
  18. Failure recovering jobs: Lock wait timeout exceeded; try restarting transaction
  19. 涛思数据创始人陶建辉荣获“2020中国开源杰出贡献人物”奖
  20. geo读取表达矩阵 RNA-seq R语言部分(表达矩阵合并及id转换)

热门文章

  1. LintCode—链表求和(167)
  2. 基础都掌握了却还是敲不出代码?编程新手如何快速提升coding能力?
  3. jquery层级原则器(匹配前一个元素后的下一个元素,必须是挨着的)
  4. Clob,Blob,InputStream,byte 互转
  5. SpringBoot轻量级博客/论坛(包含 SpringBoot、SSM、Dubbo多个版本实现代码) 初云博客
  6. WARN No appenders could be found for logger的解决方法
  7. Mybatis原生dao开发方法实现增删改查
  8. 安徽工业大学java实验报告_安徽工业大学java实验报告.doc
  9. java怎么返回string_黄瓜Java-如何在下一步中使用返回的String?
  10. java密码加密方式_我去,同事居然用明文存储密码!!!