1 什么是bean的循环依赖

循环依赖的原文是circular reference,指多个对象相互引用,形成一个闭环。

以两个对象的循环依赖为例:

Spring中的循环依赖有 3 种情况:

  1. 构造器(constructor)的循环依赖;
  2. 字段(field)的循环依赖;
  3. 构造器与字段的循环依赖。

其中的第 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 小总结

总结一下整个过程:

  1. 根据field.xml里声明的bean的顺序,先进行dog对象的创建;
  2. 去三级缓存中获取dog对象,这时候当然还没有,所以返回null;
  3. 将beanName(也就是"dog")添加到了singletonsCurrentlyInCreation这个Set中;
  4. 通过一些列的判断条件,选择了合适的构造函数(这里是无参构造)来构建dog实例,此时的dog对象的成员变量都是null;
  5. 将beanName(也就是"dog")放入第三级缓存中;
  6. 给dog对象的属性赋值。这时候发现dog对象的成员变量需要注入一个Cat类的对象。这时候就去创建cat对象,创建的过程相当于第2步到第6步;
  7. 同第2步,去三级缓存中获取cat对象,这时候当然也没有,所以返回null;
  8. 同第3步,将beanName(也就是"cat")添加到了singletonsCurrentlyInCreation这个Set中;
  9. 同第4步,选择了合适的构造函数(这里是无参构造)来构建cat实例,此时的cat对象的成员变量都是null;
  10. 同第5步,将beanName(也就是"cat")放入第三级缓存中;
  11. 同第6步,给cat对象的属性赋值。这时候发现cat对象的成员变量需要注入一个Dog类的对象。执行第2步,发现第三级缓存中,已经可以获取到dog对象了,就将dog对象取出来,并保存到第二级缓存中,同时从第三级缓存中移除dog对象。现在可以将dog对象赋值到cat对象的成员变量中了。cat对象创建完成;
  12. 回到第6步,将cat对象赋值到dog对象的成员变量中,dog对象也创建完成。

总的来说,Spring是依靠第三级缓存解决了循环依赖的问题,通过递归创建当前bean依赖的bean。

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

从源码解读Spring如何解决bean循环依赖的更多相关文章

  1. 手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖

    授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行. 本次调试 是 ...

  2. Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  3. Spring:源码解读Spring IOC原理

    Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...

  4. Spring源码解读Spring IOC原理

    一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...

  5. 源码解读 Spring Boot Profiles

    前言 上文<一文掌握 Spring Boot Profiles> 是对 Spring Boot Profiles 的介绍和使用,因此本文将从源码角度探究 Spring Boot Profi ...

  6. Spring5.0源码学习系列之浅谈循环依赖问题

    前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文浅谈Spring循环依赖问题,这是一个面试比较常见的问题 1.什么是循环依赖? 所谓的循环依 ...

  7. Springboot Bean循环依赖问题

    参考博客原文地址: https://www.jb51.net/article/168398.htm https://www.cnblogs.com/mianteno/p/10692633.html h ...

  8. 一文带你解读Spring5源码解析 IOC之开启Bean的加载,以及FactoryBean和BeanFactory的区别。

    前言 通过往期的文章我们已经了解了Spring对XML配置文件的解析,将分析的信息组装成BeanDefinition,并将其保存到相应的BeanDefinitionRegistry中,至此Spring ...

  9. Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)

    本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...

随机推荐

  1. 学习 MyBatis 的一点小总结 —— 底层源码初步分析

    目录 MyBatis 如何获取数据库源? MyBatis 如何获取 sql 语句? MyBatis 如何执行 sql 语句? MyBatis 如何实现不同类型数据之间的转换? 在过去程序员使用 JDB ...

  2. NKOJ4241 蚯蚓 (【NOIP2016 DAY2】)

    问题描述 输入格式 第一行包含六个整数n,m,q,u,v,t,其中:n,m,q的意义见问题描述: u,v,t均为正整数:你需要自己计算p=u/v(保证0<u<v)t是输出参数,其含义将会在 ...

  3. 关于C#三层架构增删改查中的“查询”问题

    序:问题总是反复出现,可能只是一个小小的问题,但是就像肉中刺. 问题: 关于“姓名”字段的拼接问题 姓名字段的拼接:this.Repeater1.DataSource = db.GetList(&qu ...

  4. 《Three.js 入门指南》3.1.1 - 基本几何形状 -圆柱体(CylinderGeometry)

    3.1 基本几何形状 圆柱体(CylinderGeometry) 构造函数: THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiu ...

  5. 如何优雅的将文件转换为字符串(环绕执行模式&行为参数化&函数式接口|Lambda表达式)

    首先我们讲几个概念: 环绕执行模式: 简单的讲,就是对于OI,JDBC等类似资源,在用完之后需要关闭的,资源处理时常见的一个模式是打开一个资源,做一些处理,然后关闭资源,这个设置和清理阶段类似,并且会 ...

  6. Shell脚本的编写及测试

                                                      Shell脚本的编写及测试 1.1问题 本例要求两个简单的Shell脚本程序,任务目标如下: 编写一 ...

  7. shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上

    shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上 naonao_127关注2人评论19401人阅读2012-06-08 11:26:16         生产环境下脚本自动备份脚本是 ...

  8. Mongo日期

    当通过mongo shell来插入日期类型数据时,使用new Date()和使用Date()是不一样的: > db.tianyc04.insert({mark:, mark_time:new D ...

  9. android之间的各项信息传输类型

    首先是activity想fragment怎样动态的传输数据: 一:activity与fragment之间进行数据传递是,在Activity中将要传递的数据封装在一Bundle中,使用setArgume ...

  10. docker-compose容器中redis权限问题

    遇到的问题:aof文件不断变大,导致服务器卡崩溃. 1.在服务器上拉取Bitnami/redis的镜像 2.出现aof权限不够问题,所以直接给aof文件加了权限,导致aof不断变大,最终服务器宕机. ...