什么是循环依赖?举一个例子,所谓的循环依赖就是现在有一个A类和一个B类,A里面属性注入了B,也就是依赖了B,B里面也依赖了A,那么这就是循环依赖了,而我们要探究的是spring是怎么在实例化A并且初始化的时候注入B,然后实例化B并且初始化的时候又是怎么能够注入到A这个过程。

我们先来看getBean这个方法,因为这个方法里面实现了一个Bean实例化到初始化最终变成一个完整的bean的过程

org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

@Override
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 

Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

其中doGetBean里面一开始有一段这样的代码,逻辑也很简单,就是从getSingleton方法中尝试获取bean,如果获取到了就直接返回这个bean了

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

在这里我们先对这段代码有一点印象,之后再来看这段代码

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

继续回到doGetBean方法,该方法正常流程会走到

protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}
}

在getSingleton方法中首先会从单例对象池中判断是否已经有这个bean了,如果没有就会走下一步,而这个下一步里面会调用到父类的一个beforeSingletonCreation方法,在这个方法中会把当前的beanName放入到singletonsCurrentlyInCreation这个集合中,而这个集合里面存放的是当前正在创建的beanName,这个存放当前正在创建的beanName的集合对于解决循环依赖来说很重要,而为什么在这里才把这个bean放到这个集合中?我猜想是因为当spring实例化一个bean的时候走到这一步,确定了当前容器没有这个bean了,就会从这里开始标识我要开始创建bean了,就会把这个bean存入这个集合当中。在确定了这个bean需要创建的时候,就会调用到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

doGetBean就是包含了spring实例化初始化bean过程的实现方法了,这其中在bean实例化之后,属性注入之前,有一段很重要的代码

//判断这个bean是否符合循环依赖的条件
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

在这一段代码中首先会判断这个bean是否符合循环依赖的条件,条件为这个bean必须是单例的,而且allowCircularReferences属性必须为true(这个属性可以通过外部api进行配置),以及这个bean是否是正在创建的,如果这些条件都符合的话,就会调用到addSingletonFactory方法

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory 

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {//判断一级缓存中是否有当前的beanif (!this.singletonObjects.containsKey(beanName)) {//如果没有,则把创建这个bean的工厂放到二级缓存中this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

这里说一下上面的bean工厂,这个bean工厂spring传入的是一段lambda表达式,具体调用这个bean工厂的getObject方法的时候,会调用到getEarlyBeanReference方法,这个方法中其实是能够得到一个bean的最终状态。好了,说了这么多,我们就需要把上面的东西都串起来,又以A和B为例子,假如上面getBean的流程是对A进行了,也就是getBean(A),那么此时当它走完上面的代码就会来到属性注入的逻辑了,也就是populateBean方法,往A里面注入B,当注入B的时候就会发现B依赖了A,那么如果不解决循坏依赖的问题就会产生一个死循坏了,当前的循坏如下面所示:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)

而spring怎么解决这个死循坏的呢,就是通过上面的东西了,接下来重点看一下B注入A的时候调用getBean(A)的过程。重新来到getBean的代码,上面说过了B的getBean(A)会经过getSingleton的方法尝试去容器里面拿A

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

所以说在B注入A然后调用getBean(A)的时候,在getSingleton就能拿到A了,此时B的属性注入完成,直到B的生命周期结束,然后回到A的属性注入生命周期,最后A的生命周期结束,最终循坏依赖解决,流程如下:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

在这个过程中,可以发现,解决循坏依赖的关键点在于,记录了A是否是正在创建的,并且在A属性注入之前会提前暴露了A的工厂,在A注入B然后B去注入A调用getBean(A)的时候就能够拿到A之前提前暴露的工厂,从这个工厂中拿到A 

二级缓存的意义?

前面说了这个二级缓存就是存放一个bean的工厂的,这个bean工厂的作用就是生产这个bean,那么在A属性注入之前我们直接暴露A不就好了吗,反正此时A已经被实例化了,何必大费周章地去搞一个A的工厂呢?也就是说是否可以直接把实例化好的A放入到三级缓存中就好了,并不需要二级缓存?如果是普通的一个bean的话二级缓存确实是没必要的,但是我们需要考虑的一种场景就是代理

getBean(A)->实例化A->把实例A放到三级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->直接从三级缓存中拿到A->B属性注入A完成并且走完剩余的生命周期->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存

可以看到如果是直接把A放入到三级缓存,然后B从三级缓存中去拿到A并注入,当A完整地走完自己的生命周期的时候你会发现此时的A已经是变成一个代理对象了,而B依赖的那个A却还只是一个普通对象,而引入二级缓存就能够解决这个问题,因为二级缓存中放的是A的工厂,而既然是工厂,那么就能产生出A的代理对象了,也就是上面一直说的A的最终形态,因为在A还没走完自己的生命周期的时候,A这个对象是有可能变的,所以我们不能够直接暴露出这个A实例,而是需要暴露出A的工厂,从这个工厂才能拿到A的最终形态!

getBean(A)->实例化A->把A的工厂放到二级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->从二级缓存中拿到A的工厂,进而从工厂中拿到A的最终形态,比如A的代理对象->B属性注入A完成->B走完剩余的生命周期->把B放到一级缓存->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存

三级缓存的意义?

既然上面说了二级缓存存的是bean的工厂,而且这个工厂这么牛逼能够产生bean的最终形态,那干嘛还用三级缓存?直接从工厂里面拿这个最终形态不就好了吗?我们看下spring是怎么做的

singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}
}

spring每一次都是从一个三级缓存中拿bean,如果为空就从二级缓存中的bean工厂产生bean并且放到三级缓存中,这样做的目的就是减少性能的消耗,因为假如A依赖了100个对象,而这个100个对象又依赖了A,那么这100个对象每一次从工厂中拿一个最终形态的A都是一个极其复杂的过程,所以既然这个A也已经是个最终形态的A了,那么就能够直接缓存起来,下次需要用的需要直接从三级缓存拿就可以了 

Spring是如何解决循坏依赖的?相关推荐

  1. SpringBoot整合emqx(MQTT)解决循坏依赖

    如果问题欢迎大佬指正!!! 导包 <!--MQTT--><dependency><groupId>org.eclipse.paho</groupId>& ...

  2. Spring 是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  3. 互相引用 spring_听说你还不知道Spring是如何解决循环依赖问题的?

    作者:Vt 前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入 ...

  4. Spring是如何解决循环依赖的?

    1.案发情况 @Service public class TestService1 {@Autowiredprivate TestService2 testService2;@Asyncpublic ...

  5. Spring中-IOC-Bean的初始化-循环依赖的解决

    前言 在实际工作中,经常由于设计不佳或者各种因素,导致类之间相互依赖.这些类可能单独使用时不会出问题,但是在使用Spring进行管理的时候可能就会抛出BeanCurrentlyInCreationEx ...

  6. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  7. Spring是如何解决循环依赖的

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够 ...

  8. Spring 通过 @Lazy 注解解决构造方法循环依赖问题

    什么是循环依赖? 先定义两个类 Apple.Orange,如下所示: @Component public class Apple{@Autowiredprivate Orange orange; }@ ...

  9. Spring三级缓存解决循环依赖

    1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 其实可以N=1,也就 ...

最新文章

  1. 专题 15 TCP套接字编程
  2. scapy-yield的含义和使用
  3. GPU 编程入门到精通(四)之 GPU 程序优化
  4. HDU - 4725 The Shortest Path in Nya Graph(最短路+思维)
  5. (转)iOS里面Frameworks介绍
  6. CloudDBA新功能上线--SQL过滤/限制/防火墙
  7. 懂得一些基本常识,就不会被《非酒精類致命飲料》或者叫做《我一辈子都不再喝可口可乐》的这篇文章所蒙蔽...
  8. 潜伏研发群一个月,我发现了程序员不为人知的秘密!这也太可爱了吧
  9. 通过迁移的方式解决Active Directory服务器问题之6
  10. Win10在Dev-C++配置Npcap
  11. BZOJ1597 [Usaco2008 Mar]土地购买
  12. javaSE基础大全--知识点总结
  13. 以某SCI期刊为例说明该期刊投稿须知和流程
  14. linux安装trac+svn+apache+wike,搭建apache+svn+trac平台
  15. python字典增加方法_python增加字典项的方法
  16. e-cology房地产行业解决方案
  17. 如何姿势优美地招不到合适的程序员?——招不聘独孤九式
  18. 微信好友头像无法显示的问题
  19. 常用的文件类型有哪些?有什么类型,属于什么文件?
  20. UE4/UE5 代理使用介绍

热门文章

  1. hive中的distribute by
  2. P3373(线段树2)
  3. 怎样设置图片大小php,php调整图片大小的方法
  4. 群晖安装Calibre(含格式转换豆瓣元数据推送kindle)221211
  5. AKG K66不算评测
  6. 云计算、云服务器、云数据库和云存储基本介绍
  7. JavaScript实现涂鸦笔
  8. 两年数据对比柱形图_办公小技巧:让Excel图表对比更轻松
  9. 【诺奖-1】2018年诺贝尔生理学或医学奖
  10. Markdown实用快捷键