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是如何解决循环依赖,如果回答错误,那么还需要继续看看

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 将网址转换为二维码的方法

    将网址转换为二维码的方法通过草料二维码网址是https://cli.im/?fromTopNav=1输入网址后转换为二维码比如用途:可以将自己的作品先放在github上,然后再放入超级简历中个人作品上,能让HR可

  • pagehelper Jar包下载

    pagehelper Jar包下载https://repo1.maven.org/maven2/com/github/pagehelper/pagehelper/5.1.8/pagehelper-5.1.8-javadoc.jarJar包

  • Django(47)drf请求生命周期分析

    Django(47)drf请求生命周期分析前言一般我们写完序列化以后,我们就会开始写视图了,drf中我们一般使用CBV的方式,也就是类视图的方式,最基础的我们会使用fromrest_framework.viewsimportAPIVi

  • harmonyos系统与安卓区别(uAndroid)

    目录一、前言二、HarmonyOS与Android的对比2.1HarmonyOS并不是Android的替代品2.2系统定位2.3内核对比2.4运行速度三、方舟编译器一、前言这段时间我在寻思这学习一下鸿蒙,第一是因为在着手做一个自己的开源项目,技术选型的时候想到了鸿蒙;第二是我个人非常看好鸿蒙系统的未来,清除明白华为和一些民族企业担负的责任和国人的期待,虽然带着一些民族感情;鸿蒙刚发布的时候自己是非常激动的,但是后来项目太忙一直没有认真的去了解过,这次打算花一部

  • 有哪些激光雷达SLAM算法?

    有哪些激光雷达SLAM算法?点击上方“计算机视觉工坊”,选择“星标”干货第一时间送达来源丨https://www.zhihu.com/question/433556301编辑丨计算机视觉工坊方法一作者|牛先卓http…

  • kmp算法 入门题

    kmp算法 入门题

发表回复

您的电子邮箱地址不会被公开。

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