1 什么是bean的循环依赖

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

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

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

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

其中的第 2 、第 3 种情况Spring可以解决,但第 1 情况Spring无法解决。当出现构造器循环依赖的时候,会抛出异常:

  1. 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?

  1. The dependencies of some of the beans in the application context form a cycle:
  2. ┌─────┐
  3. | securityConfig defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\config\SecurityConfig.class]

  4. | userServiceImpl defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\service\impl\UserServiceImpl.class]
  5. └─────┘

2 准备

准备两个POJO

  1. public class Dog {
  2. private String name;
  3. private Cat friend;
  4. public Dog() {
  5. }
  6. public Dog(String name, Cat friend) {
  7. this.name = name;
  8. this.friend = friend;
  9. }
  10. // 省略getter、setter
  11. }
  1. public class Cat {
  2. private String name;
  3. private Dog friend;
  4. public Cat() {
  5. }
  6. public Cat(String name, Dog friend) {
  7. this.name = name;
  8. this.friend = friend;
  9. }
  10. // 省略getter、setter
  11. }

Cat类里有个成员变量的类型是Dog类,同时Dog类里有个成员变量的类型是Cat类,满足了循环依赖的条件。

3 字段循环依赖的源码分析

3.1 field.xml

准备一个field.xml,这个xml配置了两个bean:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="dog" class="Dog">
  7. <property name="name" value="dog"/>
  8. <property name="friend" ref="cat"/>
  9. </bean>
  10. <bean id="cat" class="Cat">
  11. <property name="name" value="cat"/>
  12. <property name="friend" ref="dog"/>
  13. </bean>
  14. </beans>

xml里声明了两个bean,通过属性注入的方式将另一个bean引用到自己的成员变量中。在主类中加载xml:

  1. ClassPathXmlApplicationContext ac
  2. = new ClassPathXmlApplicationContext("field.xml");

3.2 方法的调用链

方法调用过程总结可以直接跳转到3.12。

3.3 AbstractBeanFactory#doGetBean()

  1. protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
  2. @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  3. // 省略部分代码
  4. Object bean;
  5. // 重要步骤
  6. // 检查单例缓存中是否有手动注册单例bean
  7. // 它其实是调了DefaultSingletonBeanRegistry#getSingleton(beanName, true)
  8. Object sharedInstance = getSingleton(beanName);
  9. if (sharedInstance != null && args == null) {
  10. // 省略部分代码
  11. }
  12. else {
  13. // 省略部分代码
  14. try {
  15. // 省略部分代码
  16. if (mbd.isSingleton()) {
  17. // 重要步骤
  18. sharedInstance = getSingleton(beanName, () -> {
  19. try {
  20. // 重要步骤
  21. // singletonFactory.getObject()的具体实现。AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
  22. return createBean(beanName, mbd, args);
  23. }
  24. // 省略部分代码
  25. });
  26. // 省略部分代码
  27. }
  28. // 省略部分代码
  29. }
  30. // 省略部分代码
  31. return (T) bean;
  32. }

这里有两段代码是重点。

  • Object sharedInstance = getSingleton(beanName);

    这个方法调用的实际上是getSingleton(beanName, true);。详见3.4。

    1. sharedInstance = getSingleton(beanName, () -> {
    2. try {
    3. // 重要步骤
    4. // singletonFactory.getObject()的具体实现。AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
    5. return createBean(beanName, mbd, args);
    6. }
    7. // 省略部分代码
    8. });

    详见3.5。

3.4 DefaultSingletonBeanRegistry#getSingleton(String, boolean)

DefaultSingletonBeanRegistry类的三个Map类型的成员变量,这三个成员变量也就是所谓的三级缓存。

  1. // 一级缓存
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. // 二级缓存
  4. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  5. // 三级缓存
  6. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  7. // 除了三级缓存以外,还有一个Set,beanName保存在这个Set里表示这个bean正在创建中
  8. private final Set<String> singletonsCurrentlyInCreation =
  9. Collections.newSetFromMap(new ConcurrentHashMap<>(16));
  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. // 三级缓存其实就是三个Map,键是beanName
  3. // 1 去一级缓存singletonObjects里获取
  4. Object singletonObject = this.singletonObjects.get(beanName);
  5. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  6. // 如果 没有从一级缓存中获取到,且对象在正在创建中
  7. // 判断对象正在创建中的方法是判断beanName是不是在singletonCurrentlyInCreation这个Set里
  8. synchronized (this.singletonObjects) {
  9. // 2 从二级缓存earlySingletonObjects中获取
  10. singletonObject = this.earlySingletonObjects.get(beanName);
  11. if (singletonObject == null && allowEarlyReference) {
  12. // 如果 二级缓存中没有获取到,且allowEarlyReference为true
  13. // 3.1 从三级缓存中获取,这时候获取到的是ObjectFactory,这是个工厂,并不是我们要实例
  14. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  15. if (singletonFactory != null) {
  16. // 3.2 从对象工厂中获取实例
  17. singletonObject = singletonFactory.getObject();
  18. // 3.3 将获取到的实例保存到二级缓存(为什么不用保存到一级缓存)
  19. this.earlySingletonObjects.put(beanName, singletonObject);
  20. // 3.4 将工厂从三级缓存中移除
  21. this.singletonFactories.remove(beanName);
  22. }
  23. }
  24. }
  25. }
  26. return singletonObject;
  27. }

3.5 DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)

3.5其实是3.4的重载。

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  2. // 省略部分代码
  3. synchronized (this.singletonObjects) {
  4. // 去一级缓存中拿bean
  5. Object singletonObject = this.singletonObjects.get(beanName);
  6. if (singletonObject == null) {
  7. // 省略部分代码
  8. // 重要步骤
  9. // 这个方法会将beanName添加到singletonsCurrentlyInCreation这个Set中
  10. // 这步添加操作会在后面调用getSingletion(String, boolean)的时候起作用
  11. beforeSingletonCreation(beanName);
  12. // 省略部分代码
  13. try {
  14. // 重要步骤
  15. // 在这一步创建了对象
  16. // singletonFactory是作为参数传进来的匿名内部类
  17. singletonObject = singletonFactory.getObject();
  18. // 省略部分代码
  19. }
  20. // 省略部分代码
  21. }
  22. return singletonObject;
  23. }
  24. }

这里有两段代码是重点。

  • 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

  1. protected void beforeSingletonCreation(String beanName) {
  2. // inCreationCheckExclusions singletonsCurrentlyInCreation都是Set
  3. // 这里在做判断的同时,调用了Set的add()方法
  4. // 所以beanName被添加到了singletonsCurrentlyInCreation中
  5. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  6. throw new BeanCurrentlyInCreationException(beanName);
  7. }
  8. }

3.7 AbstractAutowireCapableBeanFactory#doCreateBean()

  1. protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  2. throws BeanCreationException {
  3. BeanWrapper instanceWrapper = null;
  4. // 省略部分代码
  5. if (instanceWrapper == null) {
  6. // 重要步骤
  7. // 在这里调用合适的构造方法生成实例,并将实例放在一个包装类中。这里就不详细展开了
  8. instanceWrapper = createBeanInstance(beanName, mbd, args);
  9. }
  10. // 从包装类中将实例取出来
  11. final Object bean = instanceWrapper.getWrappedInstance();
  12. // 省略部分代码
  13. // 这个boolean值的结果取决于->单例、allowCircularReferences为true(默认就是true)、对象正在创建中
  14. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
  15. if (earlySingletonExposure) {
  16. // 省略部分代码
  17. // 重要步骤
  18. // 这个方法里会将bean添加到三级缓存中
  19. addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  20. }
  21. // Initialize the bean instance.
  22. Object exposedObject = bean;
  23. try {
  24. // 重要步骤
  25. // 给属性赋值
  26. populateBean(beanName, mbd, instanceWrapper);
  27. exposedObject = initializeBean(beanName, exposedObject, mbd);
  28. }
  29. // 省略部分代码
  30. }

这里有三段重要代码:

  • 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()

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3. synchronized (this.singletonObjects) {
  4. if (!this.singletonObjects.containsKey(beanName)) {
  5. // 如果 一级缓存中还没有这个beanName
  6. // 在三级缓存中做一下保存
  7. this.singletonFactories.put(beanName, singletonFactory);
  8. // 在二级缓存中移除
  9. this.earlySingletonObjects.remove(beanName);
  10. // 在registeredSingletons里添加
  11. this.registeredSingletons.add(beanName);
  12. }
  13. }
  14. }

在这个方法里将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. [Java网络安全系列面试题] 说一说TCP和UDP的区别与联系?

    TCP TCP是Transfer Control Protocol(传输控制协议)的简称,是一种面向连接的保证可靠传输的协议. 在TCP/IP协议中,IP层主要负责网络主机的定位,数据传输的路由,由I ...

  2. Crash

    一.Crash类型 crash 一般产生自 iOS 的微内核 Mach,然后在 BSD 层转换成 UNIX SIGABRT 信号,以标准 POSIX 信号的形式提供给用户.NSException 是使 ...

  3. python学习第四节 迭代器 生成器

    1:什么是迭代 可以直接作用于for循环的对象统称为可迭代对象(Iterable). 可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator). 所有的Iterable均可以通 ...

  4. 运输问题中产销不平衡问题(表上作业法和LINGO方法)

    对于产销不平衡问题有两种情况: 供大于求(产大于销)→增加虚拟销地 供不应求(产小于销)→增加虚拟产地 例如以下例题: 这个题中,总产量为55,总销量为60,故而我们知道这个问题属于供不应求. 1.这 ...

  5. Java 连接数据库总是报错

    mysql账号密码是正确的,但是一直报账号密码错误. 报错信息: java.sql.SQLException: Access denied for user 'root'@'localhost' (u ...

  6. ASP.NET Core WEB API 使用element-ui文件上传组件el-upload执行手动文件文件,并在文件上传后清空文件

    前言: 从开始学习Vue到使用element-ui-admin已经有将近快两年的时间了,在之前的开发中使用element-ui上传组件el-upload都是直接使用文件选取后立即选择上传,今天刚好做了 ...

  7. mysql物理结构

    MySQL是通过文件系统对数据和索引进行存储的. MySQL从物理结构上可以分为日志文件和数据索引文件. MySQL在Linux中的数据索引文件和日志文件都在/var/lib/mysql目录下. 日志 ...

  8. Linux搜索工具

                                                                Linux搜索工具 Search搜索工具 yum search all vim  ...

  9. 曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  10. 多转一ETH(ERC20代币汇集)

    1.下载表格模板   2.导入小号地址 NO1.地址整理   NO2.地址导入   NO3.导入完成   3.查询地址余额1.下图是汇集ETH的操作图片   2.下图是汇集ERC20代币的操作图片 注 ...