所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。那么spring在自动注入的时候是如何解决这个问题的呢?

答案是:三级缓存,就是三个map,通过提早的暴露对象来解决这个问题。

三级缓存描述

DefaultSingletonBeanRegistry中的源码

//一级缓存:单例对象缓存池,beanName->Bean,其中存储的就是实例化,属性赋值成功之后的单例对象private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//三级缓存:单例工厂的缓存,beanName->ObjectFactory,添加进去的时候实例还未具备属性// 用于保存beanName和创建bean的工厂之间的关系map,单例Bean在创建之初过早的暴露出去的Factory,// 为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//二级缓存:早期的单例对象,beanName->Bean,其中存储的是实例化之后,属性未赋值的单例对象// 执行了工厂方法生产出来的Bean,bean被放进去之后,// 那么当bean在创建过程中,就可以通过getBean方法获取到private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//三级缓存是用来解决循环依赖,而这个缓存就是用来检测是否存在循环依赖的private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));

调用链

下面是一个循环依赖的调用链。A->B->A的依赖注入

  • 1.A为正在创建中,反射创建其实例,其对象工厂放入第三层缓存
  • 2.初始化A实例时发现需要依赖注入B,则获取B的实例
  • 3.标记B为正在创建中,反射创建其实例,其对象工厂放入第三层缓存
  • 4.初始化B实例时发现需要依赖注入A,则获取A的实例
  • 5.注意!这时候从缓存中获取时,A为正在创建中且第三层缓存有A的值了,所以调用缓存的对象工厂的getObject方法,把返回的A实例放入第二层缓存,删除第三层缓存
  • 6.B实例初始化完成,放入第一层缓存,移除第二、三层中的缓存
  • 7.回到第2步,A实例初始化完成,放入第一层缓存,移除第二、三层中的缓存

第一步肯定是要去获取Bean执行getBean(A),getBean->doGetBean

doGetBean中有两个getSingleton方法

getSingleton

从doGetBean方法的源码中依次说明

第一个是从缓存中获取,也就是图片最右边的,由于一开始缓存里面肯定没有所以返回的是null

     // 尝试从单例缓存集合里获取bean实例,也就是图片中的getSingleton(2)Object sharedInstance = getSingleton(beanName);

该方法在什么时候会有值呢?如上图最右边依次走到该流程的方法中,这个时候就可以获取到值。首次调用的时候是没有值的,因为还没有放进去。

来看下实现代码

 @Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {//尝试从一级缓存里面获取完备的BeanObject singletonObject = this.singletonObjects.get(beanName);//如果完备的单例还没有创建出来,创建中的Bean的名字会被保存在singletonsCurrentlyInCreation中//因此看看是否正在创建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {//尝试给一级缓存对象加锁,因为接下来就要对缓存对象操作了synchronized (this.singletonObjects) {//尝试从二级缓存earlySingletonObjects这个存储还没进行属性添加操作的Bean实例缓存中获取singletonObject = this.earlySingletonObjects.get(beanName);//如果还没有获取到并且第二个参数为true,为true则表示bean允许被循环引用if (singletonObject == null && allowEarlyReference) {//从三级缓存singletonFactories这个ObjectFactory实例的缓存里尝试获取创建此Bean的单例工厂实例ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//如果获取到工厂实例if (singletonFactory != null) {//调用单例工厂的getObject方法返回对象实例singletonObject = singletonFactory.getObject();//将实例放入二级缓存里this.earlySingletonObjects.put(beanName, singletonObject);//从三级缓存里移除this.singletonFactories.remove(beanName);}}}}return singletonObject;}

第二个getSingleton主要是用来创建Bean的

             //如果BeanDefinition为单例if (mbd.isSingleton()) {//这里使用了一个匿名内部类,创建Bean实例对象,并且注册给所依赖的对象sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.// 显式从单例缓存中删除 bean 实例// 因为单例模式下为了解决循环依赖,可能它已经存在了,所以将其销毁destroySingleton(beanName);throw ex;}});// 如果是普通bean,直接返回,是FactoryBean,返回他的getObjectbean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

匿名工程ObjectFactory来创建对象createBean->doCreateBean

doCreateBean

该方法才是真正做事情的,重要的几个方法

  • createBeanInstance 通过反射完成对象的实例化,得到半成品对象
  • addSingletonFactory 为了防止循环引用,尽早持有对象的引用,把半成品对象放到三级缓存中
  • populateBean 填充半成品的属性
  • initializeBean 初始化半成品对象
createBeanInstance创建Bean实例

这里创建bean的实例有三种方法

  • 工厂方法创建
  • 构造方法的方式注入
  • 无参构造方法注入
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)) {//往三级缓存里添加this.singletonFactories.put(beanName, singletonFactory);//清除此Bean在二级缓存里的缓存信息this.earlySingletonObjects.remove(beanName);//这里为了记录注册单例的顺序this.registeredSingletons.add(beanName);}}}
populateBean填充属性
 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");}else {// Skip property population phase for null instance.return;}}// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.// 给InstantiationAwareBeanPostProcessors最后一次机会在属性注入前修改Bean的属性值,也可以控制是否继续填充Bean// 具体通过调用postProcessAfterInstantiation方法,如果调用返回false,表示不必继续进行依赖注入,直接返回// 主要是让用户可以自定义属性注入。比如用户实现一个 InstantiationAwareBeanPostProcessor 类型的后置处理器,// 并通过 postProcessAfterInstantiation 方法向 bean 的成员变量注入自定义的信息。boolean continueWithPropertyPopulation = true;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {continueWithPropertyPopulation = false;break;}}}}//如果上面设置 continueWithPropertyPopulation = false,表明用户可能已经自己填充了// bean 的属性,不需要 Spring 帮忙填充了。此时直接返回即可if (!continueWithPropertyPopulation) {return;}// pvs是一个MutablePropertyValues实例,里面实现了PropertyValues接口,// 提供属性的读写操作实现,同时可以通过调用构造函数实现深拷贝//获取BeanDefinition里面为Bean设置上的属性值PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);// 根据Bean配置的依赖注入方式完成注入,默认是0,即不走以下逻辑,所有的依赖注入都需要在xml文件中有显式的配置// 如果设置了相关的依赖装配方式,会遍历Bean中的属性,根据类型或名称来完成相应注入,无需额外配置int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.// 根据beanName进行autowiring自动装配处理//     <bean id="boyFriend" class="com.bushro.dao.impl.BoyFriend"  autowire="byName"></bean>if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.//根据Bean的类型进行autowiring自动装配处理//    <bean id="boyFriend" class="com.bushro.dao.impl.BoyFriend"  autowire="byType"></bean>if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}// 容器是否注册了InstantiationAwareBeanPostProcessorboolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();// 是否进行依赖检查,默认为falseboolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;//在这里会对@Autowired标记的属性进行依赖注入PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}// 对解析完但未设置的属性再进行处理pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}}// 依赖检查,对应depend-on属性,3.0已经弃用此属性if (needsDepCheck) {// 过滤出所有需要进行依赖检查的属性编辑器if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}checkDependencies(beanName, mbd, filteredPds, pvs);}if (pvs != null) {//最终将属性注入到Bean的Wrapper实例里,这里的注入主要是供//显式配置了autowiredbyName或者ByType的属性注入,//针对注解来讲,由于在AutowiredAnnotationBeanPostProcessor已经完成了注入,//所以此处不执行applyPropertyValues(beanName, mbd, bw, pvs);}}

在遍历BeanPostProcessor的时候有一个AutowiredAnnotationBeanPostProcessor类,该类的postProcessProperties方法来进行注入

 @Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {//获取指定类中@Autowired相关注解的元信息InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {//对Bean的属性进行自动注入metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;}

在创建A的时候如果发现属性B没有实例化的话,就又会创建A的方法…

循环依赖的支持情况

Spring只能实现单例bean通过set注入或者@Autowired进行循环依赖

  • 构造器循环依赖【singleton(不支持)、prototype(不支持)】
  • Setter注入循环依赖【singleton、prototype(不支持)】

三级缓存除了解决单例循环依赖的问题,还解决了Bean是唯一的问题。而prototype类型的Bean不是唯一的,所以是不能使用这种方式来解决的

不支持prototype构造器循环依赖

例如:

@Component
@Scope(value = "prototype")
public class A {private B b;@Autowiredpublic A(B b) {this.b = b;}
}
@Component
@Scope(value = "prototype")
public class B {private A a;@Autowiredpublic B(A a) {this.a = a;}
}

在AbstractBeanFactory->doGetBean方法中

prototypesCurrentlyInCreation是一个ThreadLocal类型的变量。产生循环依赖的时候该变量就会有值,从而抛出异常

 //如果scope为prototype并且显示还在创建中,则基本是循环依赖的情况//针对prototype的循环依赖,spring无解,直接抛出异常if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}/*** 返回指定的原型bean当前是否正在创建中*/protected boolean isPrototypeCurrentlyInCreation(String beanName) {Object curVal = this.prototypesCurrentlyInCreation.get();return (curVal != null &&(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));}

prototype类型的bean在spirng启动的时候并不会去初始化,只有在使用的时候才会去初始化。执行getBean的时候才会去加载。

AbstractApplicationContext->refresh->finishBeanFactoryInitialization->preInstantiateSingletons

初始话的是单例bean,并且不是懒加载的

不支持单例构造器循环依赖

例如

@Component
public class A {private B b;@Autowiredpublic A(B b) {this.b = b;}
}
@Component
public class B {private A a;@Autowiredpublic B(A a) {this.a = a;}
}

不支持的原因是构造器的实例化是在doCreateBean中的createBeanInstance,此时还没有缓存,在实例化A的时候发现B还没有进行实例化就去执行B的构造器来实例话,这样就形成死循环了。

     // 使用带参的构造函数进行装配Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}

Setter注入循环依赖是在populateBean 填充半成品的属性的时候来注入,这个时候是已经有缓存了。

Spring如何解决单例循环依赖相关推荐

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

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

  2. Spring是如何利用“三级缓存“巧妙解决Bean的循环依赖问题

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

  3. spring源码分析04-spring循环依赖底层源码解析

    1. 什么是循环依赖 很简单,就是A对象依赖了B对象,B对象依赖了A对象. // A依赖了B class A{public B b; }// B依赖了A class B{public A a; } 如 ...

  4. Spring源码 IOC和循环依赖AOP

    Spring源码 IOC和循环依赖AOP IOC篇 1.BeanFactory IOC容器,以BeanFactory为载体. BeanFactory,是Spring容器.这是由Spring管理,产生S ...

  5. Spring源码分析系列-循环依赖和三级缓存

    目录 循环依赖 多级缓存 一级缓存 二级缓存 当循环依赖遇上AOP 三级缓存 Spring三级缓存源码实现 总结 循环依赖   BeanFactory作为bean工厂管理我们的单例bean,那么肯定需 ...

  6. 不懂就问,Spring 是如何判定原型循环依赖和构造方法循环依赖的?

    作者:青石路 cnblogs.com/youzhibing/p/14514823.html 写在前面 Spring 中常见的循环依赖有 3 种:单例 setter 循环依赖.单例构造方法循环依赖.原型 ...

  7. spring自动装配依赖包_解决Spring自动装配中的循环依赖

    spring自动装配依赖包 我认为这篇文章是在企业应用程序开发中使用Spring的最佳实践. 使用Spring编写企业Web应用程序时,服务层中的服务量可能会增加. 服务层中的每个服务可能会消耗其他服 ...

  8. 解决Spring自动装配中的循环依赖

    我认为这篇文章是在企业应用程序开发中使用Spring的最佳实践. 使用Spring编写企业Web应用程序时,服务层中的服务量可能会增加. 服务层中的每个服务可能会消耗其他服务,这些服务将通过@Auto ...

  9. spring中的单例工厂SingletonBeanRegistry设计与实现

    单例工厂接口为SingletonBeanRegistry,主要是单例的注册,其默认实现为DefaultSingletonBeanRegistry 1.类层次图 2.单例工厂在循环依赖时的流程

最新文章

  1. 机器学习丨15个最流行的GitHub机器学习项目
  2. 多径信道理论的直观感受与MATLAB仿真
  3. keras从入门到放弃(十七)使用预训练网络VGG迁移学习
  4. LeetCode刷题指南(一)
  5. spring boot + vue 前后端分离时间戳转换为 yyyy:MM:dd HH:mm:ss格式
  6. zookeeper专题:zookeeper集群搭建和客户端连接
  7. ppt手动放映怎么设置_一键解决PPT的动画播放和动画排序问题!
  8. T-SQL 函数概述
  9. 【github系列】解决Github上README无法显示图片
  10. RFID技术正助力物流行业进入新时代
  11. 2018年终盘点:“年度爆款”的区块链真的结束了吗?
  12. rocketmq生产者发送到哪个队列
  13. Python(二十五):排序、反转
  14. scrapy项目-爬取阳光问政
  15. 1011: 【基础】空心六边形
  16. 软考高级系统架构师是什么来头?考上了就能当架构师了吗
  17. python做工控机_工控机折腾小记
  18. 初学爬虫-笔趣阁爬虫
  19. IIS7+PHP安装教程
  20. Linux安装LUA

热门文章

  1. 微信头像下载并上传到阿里云OSS,PHP文件上传到阿里云OSS简单代码(OSS文件上传,微信头像下载,CURL下载文件,微信头像链接过期)
  2. QuantFabric量化交易系统开源发布
  3. 将Materials Studio导出的pdb文件转换成LAMMPS所用的data坐标文件(含程序)
  4. 【转】不需要 Root,也能用上强大的 Xposed 框架:VirtualXposed
  5. 梦想成真---jdk版本的选择(推荐1.8)
  6. ARM:NVIC VIC GIC SCB
  7. Trustzone 硬件架构
  8. AEM:南林樊奔等-植物根际促生菌控制大豆疫病
  9. crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o
  10. 防治交换机窃听技术_尘毒治理:金属冶炼工程技术措施