Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)
本篇文章解决以下问题:
- [1] . Spring循环依赖指的是什么?
- [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况?
- [3] . Spring能解决的循环依赖原理(三级缓存)
一、Spring 循环依赖可能出现的三种方式
第一种:构造器参数循环依赖
第二种:setter方式单例,默认方式
第三种:setter方式原型,prototype
第一种:构造器参数循环依赖
首先我们先初始化三个Bean。
public class StudentA {
private StudentB studentB;
public void setStudentB(StudentB studentB) {
this.studentB = studentB;
}
public StudentA() {
}
public StudentA(StudentB studentB) {
this.studentB = studentB;
}
}
public class StudentB {
private StudentC studentC ;
public void setStudentC(StudentC studentC) {
this.studentC = studentC;
}
public StudentB() {
}
public StudentB(StudentC studentC) {
this.studentC = studentC;
}
}
public class StudentC {
private StudentA studentA;
public void setStudentA(StudentA studentA) {
this.studentA = studentA;
}
public StudentC() {
}
public StudentC(StudentA studentA) {
this.studentA = studentA;
}
}
application1.xml
<bean id="a" class="cn.mayday.springrecycledp.demo1.StudentA">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="cn.mayday.springrecycledp.demo1.StudentB">
<constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="cn.mayday.springrecycledp.demo1.StudentC">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>
测试类
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext1.xml");
}
测试结果
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
- 结果分析
Spring容器会将每一个正在创建的Bean
标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
- 根据上述源码实现分析:Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB, StudentB依赖StudentC ,然后将B放在“当前创建Bean池”中,此时创建StudentC,StudentC又依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)
第二种:setter方式单例,默认方式
application2.xml
<!--scope="singleton"(默认就是单例方式) -->
<bean id="a" class="cn.mayday.springrecycledp.demo1.StudentA" scope="singleton">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="cn.mayday.springrecycledp.demo1.StudentB" scope="singleton">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="cn.mayday.springrecycledp.demo1.StudentC" scope="singleton">
<property name="studentA" ref="a"></property>
</bean>
- 测试类
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext2.xml");
System.out.println(context.getBean("a", StudentA.class));
}
- 测试结果
没有报错
- 结果分析
如果要说setter方式注入的话,我们最好先看一张Spring中Bean实例化的图

如图中前两步骤得知:Spring是先将Bean对象实例化之后再设置对象属性的
- 为什么用set方式就不报错了呢 ?
结合上面那张图看,Spring先是用构造实例化Bean对象 ,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。
结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。
第三种:setter方式原型,prototype
application3.xml
<!--scope="prototype" -->
<bean id="a" class="cn.mayday.springrecycledp.demo1.StudentA" scope="prototype">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="cn.mayday.springrecycledp.demo1.StudentB" scope="prototype">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="cn.mayday.springrecycledp.demo1.StudentC" scope="prototype">
<property name="studentA" ref="a"></property>
</bean>
测试类
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext3.xml");
System.out.println(context.getBean("a", StudentA.class));
}
测试结果
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
- 为什么原型模式就报错了呢 ?
scope="prototype" 意思是 每次请求都会创建一个实例对象。
两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
总结
1、 Spring bean初始化的循环依赖只能解决单例模式的set方式(依靠==第三级缓存==提前暴露==无参构造函数==new出的对象)
2、 scope="prototype"时,三级缓存不保存非单例模式的bean对象,所以无法解决。
二、 Spring解决单例模式循环依赖原理(三级缓存)
Spring三级缓存请参考文章
Spring源码初探-IOC(4)-Bean的初始化-循环依赖的解决(讲的很明白)
/** Cache of singleton objects: bean name --> bean instance(缓存单例实例化对象的Map集合) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
/** Cache of singleton factories: bean name --> ObjectFactory(单例的工厂Bean缓存集合) */
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);
/** Cache of early singleton objects: bean name --> bean instance(早期的单身对象缓存集合) */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Set of registered singletons, containing the bean names in registration order(单例的实例化对象名称集合) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/**
* 添加单例实例
* 解决循环引用的问题
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
参考文章:
1 面试必问:Spring 循环依赖的三种方式
2 Spring源码初探-IOC(4)-Bean的初始化-循环依赖的解决(讲的很明白)
欢迎关注微信公众号:shoshana
Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)的更多相关文章
- 浅谈Spring解决循环依赖的三种方式
引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...
- Spring 使用AspectJ的三种方式
Spring 使用AspectJ 的三种方式 一,使用JavaConfig 二,使用注解隐式配置 三,使用XML 配置 背景知识: 注意 使用AspectJ 的 时候 要导入相应的Jar 包 嗯 昨天 ...
- Spring静态注入的三种方式
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/chen1403876161/article/details/53644024Spring静态注入的三 ...
- spring生成EntityManagerFactory的三种方式
spring生成EntityManagerFactory的三种方式 1.LocalEntityManagerFactoryBean只是简单环境中使用.它使用JPA PersistenceProvide ...
- spring创建bean的三种方式
spring创建bean的三种方式: 1通过构造方法创建bean(最常用) 1.1 spring默认会通过无参构造方法来创建bean,如果xml文件是这样配置,则实体类中必须要有无参构造方法,无参构造 ...
- Spring循环依赖的三种方式以及解决办法
一. 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的 ...
- Spring依赖注入三种方式详解
在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...
- Spring 实现事务的三种方式
事务:保证数据的运行不会说A给B钱,A钱给了B却没收到. 实现事务的三种方式(重要代码): 1.aspectJ AOP实现事务: <bean id="dataSourceTransac ...
- Spring配置dataSource的三种方式 数据库连接池
1.使用org.springframework.jdbc.dataSource.DriverManagerDataSource 说明:DriverManagerDataSource建立连接是只要有连接 ...
随机推荐
- Java进阶知识16 Spring创建IOC容器的两种方式
1.直接得到 IOC 容器对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app ...
- linux虚拟机安装centos6.x
安装系统,作为每一个it技术控们的基本功,对于各位大神和技术大牛们应该是易如反掌或者是家常便饭啦,都是从无数次安装,重装,刷机中一步步走来的.那么今天,我也分享一套装机教程,共各位和我一样的小白参考或 ...
- async for 在爬虫中的使用例子
import asyncio import re import typing from concurrent.futures import Executor, ThreadPoolExecutor f ...
- CDN之简介
1. 什么是 CDN? 来自 <什么是 CDN?> CDN(内容交付网络)是一种高度分布式服务器平台,为交付 Web 应用程序.流媒体等内容专门优化.服务器网络分布于众多物理和网络位置,对 ...
- Qt DLL总结【一】-链接库预备知识
1.链接库概念 静态链接库和动态链接库介绍 我们可以创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从文件中查找符合要求的函数和变量进行链接,整个查找过程 ...
- koa 项目实战(六)注册接口加密
1.创建工具类 根目录/config/tools.js const bcrypt = require('bcryptjs'); const tools = { enbcrypt(password) { ...
- 深度学习之NLP获取词向量
1.代码 def clean_text(text, remove_stopwords=False): """ 数据清洗 """ text = ...
- 010-数据结构-树形结构-B树[B-树]
一.概述 B 树就是常说的“B 减树(B- 树)”,又名平衡多路(即不止两个子树)查找树. 在计算机科学中,B树(英语:B-tree)是一种自平衡的树,能够保持数据有序.这种数据结构能够让查找数据.顺 ...
- 【Rxjs】 - 解析四种主题Subject
原文地址: https://segmentfault.com/a/1190000012669794 引言 开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等 ...
- Python-sympy科学计算与数据处理(方程,微分,微分方程,积分)
方程 a,b,c,x = symbols("a b c x") my_eq = Eq(a*x**2+b*x+c,0) solve(my_eq,x) Out[12]: [(-b + ...