spring源码分析之如何解决循环依赖

spring源码分析之如何解决循环依赖

spring-ioc中循环依赖的问题,也算是高频的面试问题了,今天跟大家一起来总结一下spring-ioc中是如何解决循环依赖的,相信大家是可以从这篇文章中彻底理解spring容器如何帮我们解决循环依赖,为了更好的理解spring-ioc如何解决循环依赖,大家可以先简单的了解spring-ioc中bean实例化的整个时序图。

一、spring-ioc解决循环依赖的位置

在这里插入图片描述

红色的标注框的地方,表示解决循环依赖的重点逻辑,后面会跟大家一起详细阅读,这里大家可以先有个印象

二、spring-ioc解决循环依赖的动态示意图

首先spring-ioc是无法解决构造函数中循环依赖的问题,这个后面会一起解释。咱们先用一个简单的例子和示意图来描述spring-ioc解决循环依赖的思想,最后再去阅读源码,我相信大家能更容易理解

@Service
public class ACircleService {
   

    @Autowired
    private BCircleService bCircleService;
}
@Service
public class BCircleService {
   

    @Autowired
    private ACircleService aCircleService;
}

相信大家经常这么使用,此时ACircleService(后面简称A)引用BCircleService(后面简称B)B中引用A就构成了一个循环依赖,下面我们用示意图来描述它们的创建过程

1、首先大家理解下面几个容器

(1)singletonsCurrentlyInCreation:表示当前正在创建的bean(仅仅存放名字beanName),如果没创建完成,都会保存在这里面

(2)singletonObjects:一级缓存,可以理解为bean最终都是放在这里面的,一个bean真正完成了就放到这里面

(3)earlySingletonObjects:二级缓存,过渡使用

(4)singletonFactories:三级缓存,也是过渡使用(提前曝光的bean存放),它与二级缓存不同的是它放的是ObjectFactory,而不是最终的Bean,二级缓存中是放的三级缓存getObject的结果

介绍完上面的容器,我们接着看在创建A,B时它是如何从上述的容器中变化

2、变化过程图

假如先创建A,此时四个容器中都是空的

(1)先依次从一级、二级、三级缓存中判断是否有能拿到A,结果显然是拿不到,四个容器都是空的,我就不画了

(2)要开始创建A,此时需要往singletonsCurrentlyInCreation放入A,表示A正在实例化,此时四个容器的状态如下

在这里插入图片描述
(3)接下来正式开始创建A到A创建完成(堆上面已经分配了空间,但是属性还没赋值),此时将A封装成ObjectFactory对象(为什么要封装,后面会讲一下),大家可以认为此时的A对象已经创建,但是属性未赋值,我们暂时用下面命名AObjectFactory,但是AobjectFactory.getObject() == A(A的地址假设A@9527xxx),此时A是在堆上已经创建好了,但是它的属性是null(bCircleService==null),我不知道这里有没有描述清楚,容器状态如下:

在这里插入图片描述

(4)此时要给A的属性赋值,这里就是给bCircleService赋值,那么就去创建B,创建B的过程和创建A的过程一样的,先依次从一级缓存、二级缓存、三级缓存中获取B,显然获取不到,那么正式开始创建B,singletonsCurrentlyInCreation中加入B,表示当前也正在创建B,容器状态如下:

在这里插入图片描述

(5)接下来同(3)类似,B创建完成,此时也只是在堆上创建好了对象,但是B中的属性aCircleService还没有赋值(aCircleService==null),此时将B封装成BObjectFactory放到三级缓存,容器状态如下:

在这里插入图片描述

(6)接下来是重点了,此时开始给B的属性赋值了,这里即给aCircleService 赋值,那么它就要去创建A,并且把A的内存地址付给B中的aCircleService属性,那么创建A的过程和之前的一样,先依次从一级、二级、三级缓存中拿A,此时是可以从三级缓存中拿到A的,那么将拿到的A赋值给B的aCircleService属性,此时aCircleService==A@9527xxx,此时B即将创建完成了,在全部创建完的前一步,将三级缓存中的B移到二级缓存(存放的是BObjectFactory.getObject()),因为实例化B的全部步骤全部做完了,此时容器的状态如下:

在这里插入图片描述

(7)此时B(假设地址是B@9527)已经全部实例化完成了,但是还有一些收尾的工作呀,就是需要从当前正在创建的容器(singletonsCurrentInCreation)中移除(表示B创建完成),并且同时将B移动到一级缓存singletonObjects中,此时的容器状态

在这里插入图片描述

(8)上述B已经创建完成了,我们要记得创建B的时机是在A给bCircleService赋值的时候,所以我们的逻辑又到了给A的属性赋值的时候了,此时我们知道B已经创建完成了,所以bCircleService==B@9527,此时A的实例化也快要结束了,A也要将三级缓存的内容移到二级缓存,类似过程(6),容器状态如下:

在这里插入图片描述

(9)最后A也要做一些结束的动作,类似过程(7),到这里A和B都已经全部实例化完成了

在这里插入图片描述

总结:上述看上去流程挺多的,其实主要的核心就是在A创建完成(对象已经在堆中分配),还没有给属性赋值(bCircleService==null)的过程中,将A封装成ObjectFactory,放到三级缓存。然后在实例化B的过程中,给B的属性aCircleService赋值时,依次 从容器中拿A,此时是可以从三级缓存中拿到,所以不会再去走创建A的过程了,相当于提前曝光了A

上面还留了两个问题,会在下面的源码分析中解释

(1)为什么spring-ioc中无法解决构造函数中的循环依赖

(2)为什么需要使用三级缓存,而且里面装的是ObjectFactory

三、源码分析

1、AbstractBeanFactory#doGetBean()

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   

   final String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   //(1).先依次从一级、二级、三级缓存中看看能否取到
   Object sharedInstance = getSingleton(beanName);
    //(2).如果缓存中能取到,则不会走下面的创建
   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);
   }
//(3).如果缓存中取不到,则走创建的逻辑
   else {
   
      // Fail if we're already creating this bean instance:
      // We're assumably within a circular reference.
		//省略代码...
         // Create bean instance.
         if (mbd.isSingleton()) {
   
             //(4).主要的创建逻辑
            sharedInstance = getSingleton(beanName, () -> {
   
               try {
   
                   //(5).java8函数式编程
                  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.
                  destroySingleton(beanName);
                  throw ex;
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }
		//省略代码...
   }

   //省略代码...
   return (T) bean;
}

上述代码我省略了很多,主要保留了需要分析循环依赖的逻辑,上面已经加了注释

首先从一级、二级、三级缓存中取

下面的if else针对是否能从缓存中取出结果

结合上述的图,我们发现第一次创建A,显然是走else的创建逻辑getSingleton

2、DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
   
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
   
         if (this.singletonsCurrentlyInDestruction) {
   
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
   
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
         //(1).开始创建之前调用方法
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
   
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
   
             //(2).真正的调用方法
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         catch (IllegalStateException ex) {
   
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
   
               throw ex;
            }
         }
         catch (BeanCreationException ex) {
   
            if (recordSuppressedExceptions) {
   
               for (Exception suppressedException : this.suppressedExceptions) {
   
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         }
         finally {
   
            if (recordSuppressedExceptions) {
   
               this.suppressedExceptions = null;
            }
            //(3).开始创建之后调用方法
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
   
             //(4).创建成功之后调用的方法
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

我在四个核心的方法上加了注释

beforeSingletonCreation(beanName);

singletonFactory.getObject();

afterSingletonCreation(beanName);

addSingleton(beanName, singletonObject);

3、DefaultSingletonBeanRegistry#beforeSingletonCreation(beanName)

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

比较简单,其实就是判断当前正在创建的容器singletonsCurrentlyInCreation是否已经包含正在创建的类,如果包含,则抛异常,我们在构造做循环依赖就会在这里抛异常,后面会具体分析为什么会在这里抛异常

4、DefaultSingletonBeanRegistry#afterSingletonCreation(beanName);

这里我们先没有分析singletonFactory.getObject()创建的核心逻辑,因为比较长一时半会儿分析不了,我们分析afterSingletonCreation是因为它和上面的beforeSingletonCreation有关联。这里我们先假设singletonFactory.getObject()已经成功执行完了,我们看afterSingletonCreation()

protected void afterSingletonCreation(String beanName) {
   
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
   
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
   }
}

很简单,就是将singletonsCurrentlyInCreation清除当前正在创建的bean,因为此时我们已经创建完了,接下来再接着看addSingleton

5、DefaultSingletonBeanRegistry#addSingleton

protected void addSingleton(String beanName, Object singletonObject) {
   
   synchronized (this.singletonObjects) {
   
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}

很简单,4、5的步骤对应我们的上述第二节示意图(6)-(7)的过程。上面我们还没有分析singletonFactory.getObject()创建bean的核心逻辑,只是假设它成功调用完成了,我们现在回过头来分析

6、ObjectFactory#getObject()

这里其实是调用的AbstractAutowireCapableBeanFactory#createBean(),因为这里是使用java8的lambda表达式,传的是一个函数(参考1中代码注释(5)的那一段代码)

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
   
//省略代码....
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    //省略代码...

}

省略了代码,我们重点看doCreateBean,这个是核心

7、AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
   

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
   
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
   
       //(1).创建bean包装类BealWrapper,这段代码执行结束,说明bean已经构建完成,在堆上创建了实例
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
    //(2).拿到bean
   final Object bean = instanceWrapper.getWrappedInstance();

   //省略部分代码...

   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
    //(3).这里非常重要,没有删除原来的英文注释,这里就是提前曝光
   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");
      }
       //(3-1)
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
   
       //(4).给属性赋值
      populateBean(beanName, mbd, instanceWrapper);
       //(5).调用初始化方法
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   if (earlySingletonExposure) {
   
       //(6).bean在容器中移动
      Object earlySingletonReference = getSingleton(beanName, false);

  //省略部分代码...

   return exposedObject;
}

这里是解决循环依赖的核心—即提前曝光一个实例(该实例已经创建好,但是里面的属性还没赋值,因为赋值的逻辑要到代码(4),而提前曝光的逻辑在(3))。拿上面的循环依赖A,B来说,代码执行完(3)时,A对象已经在堆上分配,只是bCircleService == null而已。此时将A提前曝光,插入到三级缓存中;而实例化B的入口则在(4)中,给bCircleservice赋值,此时如果B没有创建,就开始创建B。等B同样执行完上述(3),则B也在堆上分配了,只是暂时B中的aCircleService==null,所以B执行(4)时,去创建A,此时创建A先依次从一级、二级、三级缓存中取A时是可以在三级缓存中取到,因此代码不会执行1中的else逻辑,而是执行if。

我们现在来处理上述的第一个问题:为什么构造函数中的循环依赖不能解决?

我们还是拿A,B来举例(假如A的构造函数中依赖B),如果在构造函数中循环依赖,则A不会上述代码7中的(3)而是在(1)中就去获取B(此时注意A没有提前曝光,即一级、二级、三级缓存中都不存在),假如此时B没有创建,则开始创建B。等B执行到(4)时,给B中的aCircleService赋值时,需要去创建A,先从一级、二级、三级缓存中去取A,取不到,则走代码1中的else逻辑,else的逻辑最终会调用beforeSingletonCreation(beanName),因此就抛异常了。

我们处理第二个问题,为什么需要使用三级缓存singletonFactories,里面装的是ObjectFactory,而不是直接将ObjectFactory.getObject()获取的结果放到二级缓存earlySingletonObjects呢?

这里我们就需要看上述代码7中的(3-1)addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));这里是使用java8的函数式编程,如果不明白的,我用下面的匿名内部类来替换把

addSingletonFactory(beanName, new ObjectFactory<Object>(){
   

    @Override
    public Object getObject() throws BeansException {
   
        return getEarlyBeanReference();
    }
});

我们再看看getEarlyBeanReference()方法

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
   
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
   
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

这里无非就是对这个bean进行拦截,做一些处理,最终这个方法的调用时机,是在代码2中的(4),此时bean的实例化的基本全部完成,所以这里起到了延迟调用的作用。如果不延迟调用存在两种情况

(1)不调用getEarlyBeanReference(),则有些实例创建没有起到必要的拦截

(2)不延迟,那么可能该实例仅仅是在堆上分配了,里面属性什么都没赋值,初始化方法也没调用,可能我们用beanpostprocessor拦截也没任何意义,达不到效果。

这里大家可以思考一下这两个问题,
1.当A构造函数中依赖B,而在B中依赖A不是构造函数依赖,会不会报错
2.当A依赖B时不是构造函数依赖,而B依赖A时是构造函数依赖,会不会报错
如果大家对这两个问题都能回答正确,我相信是彻底理解了spring是如何解决循环依赖,如果回答错误,那么还需要继续看看

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111202.html原文链接:https://javaforall.net

(0)
上一篇 2021年8月4日 上午8:00
下一篇 2021年8月4日 上午9:00


相关推荐

  • 【差异分析】蓝牙4.0 vs 蓝牙4.1 vs 蓝牙4.2 vs 蓝牙5.0

    【差异分析】蓝牙4.0 vs 蓝牙4.1 vs 蓝牙4.2 vs 蓝牙5.0目前市场上依然有大量蓝牙 4 0 3 0 2 1 2 1 EDR 产品存在 从自拍器 遥控器到各种智能设备 因其功能够用 价格低廉 受到快消类产品客户的亲昵 而工业类 汽车类应用 BT4 0 的产品依然当道 究其原因 稳定 够用 供货好 当然价格不贵 但如果说蓝牙 5 之前蓝牙解决的是单点连接的可穿戴式设备与手机互联的问题 那么蓝牙 5 就是解决多点互联 IoT 物联网的问题

    2026年3月19日
    2
  • golang环境搭建_linux开发环境的搭建

    golang环境搭建_linux开发环境的搭建『Golang』(Go语言,以下简称Go)是Google开发的一种编译型、可并行化、并具有垃圾回收功能的编程语言。罗布·派克(RobPike)、罗伯特·格瑞史莫(RobertGriesemer)、及肯·汤普逊于2007年9月开始设计Go,随后IanLanceTaylor、RussCox加入项目中。Go是基于Inferno操作系统所开发的。Go于2009

    2022年10月11日
    2
  • win10系统中pdf不显示缩略图及预览图

    win10系统中pdf不显示缩略图及预览图试试这个,我的取消了复选框反而出现了预览图:https://helpx.adobe.com/cn/acrobat/using/enable-pdf-thumbnail-preview-windows-explorer.html

    2022年5月6日
    401
  • 遍历hashMap的5种方法

    遍历hashMap的5种方法1 使用 Iterator 遍历 HashMapEntry 使用 Iterator 遍历 HashMapKeySe 使用 For each 循环迭代 HashMap4 使用 Lambda 表达式遍历 HashMap5 使用 StreamAPI 遍历 HashMap1 使用 Iterator 遍历 HashMapEntry java tutorials iterations importjava util HashMap import

    2026年3月18日
    2
  • pycharm配置环境及安装第三方库_pycharm关联python

    pycharm配置环境及安装第三方库_pycharm关联pythonpycharm配置python环境的方法是:1、依次点击【File】、【ProjectInterpreter】;2、点击【ShowAll】,选择【ExistingEnvironment】;3、选择python的安装路径,点击OK即可。配置方法:1、打开软件,依次点击【File】→【Settings】→【Project】→【ProjectInterpreter】,这样我们就进入了配置Pyth…

    2022年8月29日
    10
  • 如何用Cursor打造专属Go语言开发神器?超详细图文教程来了

    如何用Cursor打造专属Go语言开发神器?超详细图文教程来了

    2026年3月15日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号