从源码解读Spring如何解决bean循环依赖
1 什么是bean的循环依赖
循环依赖的原文是circular reference,指多个对象相互引用,形成一个闭环。
以两个对象的循环依赖为例:
Spring中的循环依赖有 3 种情况:
- 构造器(constructor)的循环依赖;
- 字段(field)的循环依赖;
- 构造器与字段的循环依赖。
其中的第 2 、第 3 种情况Spring可以解决,但第 1 情况Spring无法解决。当出现构造器循环依赖的时候,会抛出异常:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cat' defined in class path resource [constructor.xml]: Cannot resolve reference to bean 'dog' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dog': Requested bean is currently in creation: Is there an unresolvable circular reference?
或
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| securityConfig defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\config\SecurityConfig.class]
↑ ↓
| userServiceImpl defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\service\impl\UserServiceImpl.class]
└─────┘
2 准备
准备两个POJO
public class Dog {
private String name;
private Cat friend;
public Dog() {
}
public Dog(String name, Cat friend) {
this.name = name;
this.friend = friend;
}
// 省略getter、setter
}
public class Cat {
private String name;
private Dog friend;
public Cat() {
}
public Cat(String name, Dog friend) {
this.name = name;
this.friend = friend;
}
// 省略getter、setter
}
Cat类里有个成员变量的类型是Dog类,同时Dog类里有个成员变量的类型是Cat类,满足了循环依赖的条件。
3 字段循环依赖的源码分析
3.1 field.xml
准备一个field.xml,这个xml配置了两个bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="Dog">
<property name="name" value="dog"/>
<property name="friend" ref="cat"/>
</bean>
<bean id="cat" class="Cat">
<property name="name" value="cat"/>
<property name="friend" ref="dog"/>
</bean>
</beans>
xml里声明了两个bean,通过属性注入的方式将另一个bean引用到自己的成员变量中。在主类中加载xml:
ClassPathXmlApplicationContext ac
= new ClassPathXmlApplicationContext("field.xml");
3.2 方法的调用链
方法调用过程总结可以直接跳转到3.12。
3.3 AbstractBeanFactory#doGetBean()
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 省略部分代码
Object bean;
// 重要步骤
// 检查单例缓存中是否有手动注册单例bean
// 它其实是调了DefaultSingletonBeanRegistry#getSingleton(beanName, true)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 省略部分代码
}
else {
// 省略部分代码
try {
// 省略部分代码
if (mbd.isSingleton()) {
// 重要步骤
sharedInstance = getSingleton(beanName, () -> {
try {
// 重要步骤
// singletonFactory.getObject()的具体实现。AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
return createBean(beanName, mbd, args);
}
// 省略部分代码
});
// 省略部分代码
}
// 省略部分代码
}
// 省略部分代码
return (T) bean;
}
这里有两段代码是重点。
Object sharedInstance = getSingleton(beanName);
这个方法调用的实际上是
getSingleton(beanName, true);
。详见3.4。sharedInstance = getSingleton(beanName, () -> {
try {
// 重要步骤
// singletonFactory.getObject()的具体实现。AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
return createBean(beanName, mbd, args);
}
// 省略部分代码
});
详见3.5。
3.4 DefaultSingletonBeanRegistry#getSingleton(String, boolean)
DefaultSingletonBeanRegistry类的三个Map类型的成员变量,这三个成员变量也就是所谓的三级缓存。
// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 除了三级缓存以外,还有一个Set,beanName保存在这个Set里表示这个bean正在创建中
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 三级缓存其实就是三个Map,键是beanName
// 1 去一级缓存singletonObjects里获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果 没有从一级缓存中获取到,且对象在正在创建中
// 判断对象正在创建中的方法是判断beanName是不是在singletonCurrentlyInCreation这个Set里
synchronized (this.singletonObjects) {
// 2 从二级缓存earlySingletonObjects中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 如果 二级缓存中没有获取到,且allowEarlyReference为true
// 3.1 从三级缓存中获取,这时候获取到的是ObjectFactory,这是个工厂,并不是我们要实例
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 3.2 从对象工厂中获取实例
singletonObject = singletonFactory.getObject();
// 3.3 将获取到的实例保存到二级缓存(为什么不用保存到一级缓存)
this.earlySingletonObjects.put(beanName, singletonObject);
// 3.4 将工厂从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3.5 DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)
3.5其实是3.4的重载。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// 省略部分代码
synchronized (this.singletonObjects) {
// 去一级缓存中拿bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 省略部分代码
// 重要步骤
// 这个方法会将beanName添加到singletonsCurrentlyInCreation这个Set中
// 这步添加操作会在后面调用getSingletion(String, boolean)的时候起作用
beforeSingletonCreation(beanName);
// 省略部分代码
try {
// 重要步骤
// 在这一步创建了对象
// singletonFactory是作为参数传进来的匿名内部类
singletonObject = singletonFactory.getObject();
// 省略部分代码
}
// 省略部分代码
}
return singletonObject;
}
}
这里有两段代码是重点。
beforeSingletonCreation(beanName);
这个方法会将beanName添加到
singletonsCurrentlyInCreation
这个Set中。方法见3.6。singletonsCurrentlyInCreation
的定义见3.4。singletonObject = singletonFactory.getObject();
singletonFactory
是方法的参数,实际上是3.3第二段重要代码中用lambda创建的匿名内部类,实际上调用的是AbstractAutowireCapableBeanFactory类的doCreateBean(String, RootBeanDefinition, jObject[])
3.6 DefaultSingletonBeanRegistry#beforeSingletonCreation()java
protected void beforeSingletonCreation(String beanName) {
// inCreationCheckExclusions singletonsCurrentlyInCreation都是Set
// 这里在做判断的同时,调用了Set的add()方法
// 所以beanName被添加到了singletonsCurrentlyInCreation中
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
3.7 AbstractAutowireCapableBeanFactory#doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
// 省略部分代码
if (instanceWrapper == null) {
// 重要步骤
// 在这里调用合适的构造方法生成实例,并将实例放在一个包装类中。这里就不详细展开了
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 从包装类中将实例取出来
final Object bean = instanceWrapper.getWrappedInstance();
// 省略部分代码
// 这个boolean值的结果取决于->单例、allowCircularReferences为true(默认就是true)、对象正在创建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 省略部分代码
// 重要步骤
// 这个方法里会将bean添加到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 重要步骤
// 给属性赋值
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 省略部分代码
}
这里有三段重要代码:
instanceWrapper = createBeanInstance(beanName, mbd, args);
这个方法选择了合适的构造函数来构建实例。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在这个方法里将beanName和beanFactory放到了三级缓存中。详见3.8。
populateBean(beanName, mbd, instanceWrapper);
3.8 AbstractAutowireCapableBeanFactory#createBeanInstance()
3.9 DefaultSingletonBeanRegistry#addSingletonFactory()
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 如果 一级缓存中还没有这个beanName
// 在三级缓存中做一下保存
this.singletonFactories.put(beanName, singletonFactory);
// 在二级缓存中移除
this.earlySingletonObjects.remove(beanName);
// 在registeredSingletons里添加
this.registeredSingletons.add(beanName);
}
}
}
在这个方法里将beanName和beanFactory放到了三级缓存中。这时候,三级缓存里面已经有我要创建的dog
对象(其实是有了创建这个对象的工厂)。
3.10 AbstractAutowireCapableBeanFactory#populateBean()
这个方法调用了AbstractAutowireCapableBeanFactory类applyPropertyValues()方法
,applyPropertyValues()方法又调用了BeanDefinitionValueResolver类resolveReference()方法
。最后又调用了doGetBean()
,也就是3.3的方法。
doGetBean()
在这里是为了给dog对象创建它的Cat类成员变量。
3.11 补充
在DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)方法中,还调用了addSingleton(beanName, singletonObject);
,这个方法会将bean添加到第一级缓存中,并从第二、第三级缓存中移除。当然这一步跟解决循环依赖没有关系。
3.12 小总结
总结一下整个过程:
- 根据field.xml里声明的bean的顺序,先进行dog对象的创建;
- 去三级缓存中获取dog对象,这时候当然还没有,所以返回null;
- 将beanName(也就是"dog")添加到了singletonsCurrentlyInCreation这个Set中;
- 通过一些列的判断条件,选择了合适的构造函数(这里是无参构造)来构建dog实例,此时的dog对象的成员变量都是null;
- 将beanName(也就是"dog")放入第三级缓存中;
- 给dog对象的属性赋值。这时候发现dog对象的成员变量需要注入一个Cat类的对象。这时候就去创建cat对象,创建的过程相当于第2步到第6步;
- 同第2步,去三级缓存中获取cat对象,这时候当然也没有,所以返回null;
- 同第3步,将beanName(也就是"cat")添加到了singletonsCurrentlyInCreation这个Set中;
- 同第4步,选择了合适的构造函数(这里是无参构造)来构建cat实例,此时的cat对象的成员变量都是null;
- 同第5步,将beanName(也就是"cat")放入第三级缓存中;
- 同第6步,给cat对象的属性赋值。这时候发现cat对象的成员变量需要注入一个Dog类的对象。执行第2步,发现第三级缓存中,已经可以获取到dog对象了,就将dog对象取出来,并保存到第二级缓存中,同时从第三级缓存中移除dog对象。现在可以将dog对象赋值到cat对象的成员变量中了。cat对象创建完成;
- 回到第6步,将cat对象赋值到dog对象的成员变量中,dog对象也创建完成。
总的来说,Spring是依靠第三级缓存解决了循环依赖的问题,通过递归创建当前bean依赖的bean。
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
从源码解读Spring如何解决bean循环依赖的更多相关文章
- 手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖
授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行. 本次调试 是 ...
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring:源码解读Spring IOC原理
Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...
- Spring源码解读Spring IOC原理
一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...
- 源码解读 Spring Boot Profiles
前言 上文<一文掌握 Spring Boot Profiles> 是对 Spring Boot Profiles 的介绍和使用,因此本文将从源码角度探究 Spring Boot Profi ...
- Spring5.0源码学习系列之浅谈循环依赖问题
前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文浅谈Spring循环依赖问题,这是一个面试比较常见的问题 1.什么是循环依赖? 所谓的循环依 ...
- Springboot Bean循环依赖问题
参考博客原文地址: https://www.jb51.net/article/168398.htm https://www.cnblogs.com/mianteno/p/10692633.html h ...
- 一文带你解读Spring5源码解析 IOC之开启Bean的加载,以及FactoryBean和BeanFactory的区别。
前言 通过往期的文章我们已经了解了Spring对XML配置文件的解析,将分析的信息组装成BeanDefinition,并将其保存到相应的BeanDefinitionRegistry中,至此Spring ...
- Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)
本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...
随机推荐
- 学习 MyBatis 的一点小总结 —— 底层源码初步分析
目录 MyBatis 如何获取数据库源? MyBatis 如何获取 sql 语句? MyBatis 如何执行 sql 语句? MyBatis 如何实现不同类型数据之间的转换? 在过去程序员使用 JDB ...
- NKOJ4241 蚯蚓 (【NOIP2016 DAY2】)
问题描述 输入格式 第一行包含六个整数n,m,q,u,v,t,其中:n,m,q的意义见问题描述: u,v,t均为正整数:你需要自己计算p=u/v(保证0<u<v)t是输出参数,其含义将会在 ...
- 关于C#三层架构增删改查中的“查询”问题
序:问题总是反复出现,可能只是一个小小的问题,但是就像肉中刺. 问题: 关于“姓名”字段的拼接问题 姓名字段的拼接:this.Repeater1.DataSource = db.GetList(&qu ...
- 《Three.js 入门指南》3.1.1 - 基本几何形状 -圆柱体(CylinderGeometry)
3.1 基本几何形状 圆柱体(CylinderGeometry) 构造函数: THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiu ...
- 如何优雅的将文件转换为字符串(环绕执行模式&行为参数化&函数式接口|Lambda表达式)
首先我们讲几个概念: 环绕执行模式: 简单的讲,就是对于OI,JDBC等类似资源,在用完之后需要关闭的,资源处理时常见的一个模式是打开一个资源,做一些处理,然后关闭资源,这个设置和清理阶段类似,并且会 ...
- Shell脚本的编写及测试
Shell脚本的编写及测试 1.1问题 本例要求两个简单的Shell脚本程序,任务目标如下: 编写一 ...
- shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上
shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上 naonao_127关注2人评论19401人阅读2012-06-08 11:26:16 生产环境下脚本自动备份脚本是 ...
- Mongo日期
当通过mongo shell来插入日期类型数据时,使用new Date()和使用Date()是不一样的: > db.tianyc04.insert({mark:, mark_time:new D ...
- android之间的各项信息传输类型
首先是activity想fragment怎样动态的传输数据: 一:activity与fragment之间进行数据传递是,在Activity中将要传递的数据封装在一Bundle中,使用setArgume ...
- docker-compose容器中redis权限问题
遇到的问题:aof文件不断变大,导致服务器卡崩溃. 1.在服务器上拉取Bitnami/redis的镜像 2.出现aof权限不够问题,所以直接给aof文件加了权限,导致aof不断变大,最终服务器宕机. ...