Spring 源码分析如何解决循环依赖的问题

前言

在关于 Spring 的面试中,我们经常会被问到一个问题:Spring 是如何解决循环依赖的问题的。这是个高频的面试题,本文主要针对这个问题,从以下几个方面进行讲解:

  • 什么是循环依赖?
  • 什么情况下循环依赖可以被处理?
  • Spring是如何解决的循环依赖?

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的对象互相依赖,最终形成一个闭环。比如 A 依赖于 B,B 依赖于 C,C又依赖于 A 。

Spring 源码分析如何解决循环依赖的问题_第1张图片
比较特殊的还有自己依赖自己的。

Spring 源码分析如何解决循环依赖的问题_第2张图片

什么情况下循环依赖可以被处理?

Spring 解决循环依赖是有前置条件的:

  • 出现循环依赖的Bean必须是单例,原型不行
  • 依赖注入的方式不能全是构造器注入的方式(不是只能解决 setter 方法的循环依赖)

关于第一点,对于 原型 (Prototype) 作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存 prototype 作用域的 bean ,因此无法提前暴露一个创建中的 bean 。
原因也挺好理解的,原型模式每次请求都会创建一个实例对象,即使加了缓存,循环引用太多的话,就比较麻烦了,所以 Spring 不支持这种方式,直接抛出 BeanCurrentlyInCreationException 异常:

if (isPrototypeCurrentlyInCreation(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
}

接下来我们根据 4 中测试方式来验证一下第一点。

第一种:A 和 B 都采用 setter 方法注入,最终结果是成功的。

Spring 源码分析如何解决循环依赖的问题_第3张图片
Spring 源码分析如何解决循环依赖的问题_第4张图片

第二种:A 和 B 都采用构造器方法注入,出现循环依赖

Spring 源码分析如何解决循环依赖的问题_第5张图片

在这里插入图片描述

第三种:A 中注入 B 的方式为 setter 方法,B 中注入 A 的方式为构造器,最终结果是成功的。
Spring 源码分析如何解决循环依赖的问题_第6张图片
在这里插入图片描述

第四种:A 中注入 B 的方式为构造器,B 中注入 A 的方式为 setter 方法

Spring 源码分析如何解决循环依赖的问题_第7张图片
Spring 源码分析如何解决循环依赖的问题_第8张图片
从上面的测试结果我们可以看到,不是只有在 setter 方法注入的情况下循环依赖才能被解决,即使存在构造器注入的场景下,循环依赖依然被可以被正常处理。

Spring 如何去解决循环依赖

首先,Spring 内部维护了三个 Map,也就是我们通常说的三级缓存,我们可以注意到三级缓存与前两级缓存不太一样,Map 中维护的值是 ObjectFactory 类型。

//单例缓存池 beanName - instance 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//bean的早期引用, bean name to bean instance 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

//单例工厂 beanName - ObjectFactory  三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • singletonObjects:一级缓存,一个单例bean【实例化+初始化】都完成之后,将会加入一级缓存,也就是我们俗称的单例池
  • earlySingletonObjects:二级缓存,用于存放【实例化完成,还没初始化】的实例,提前暴露,用于解决循环依赖问题
  • singletonFactories:三级缓存,存放单例对象工厂ObjectFactory,与二级缓存不同的是,它可以应对产生代理对象

这 3 个 Map 中的后两个其实是临时使用的,只是创建 Bean 的时候,用来借助了一下,创建完成就清掉了。

这里还要说明一下这个 singletonsCurrentlyInCreation,因为下面讲解变化过程需要使用到,它的意思是,表示当前正在创建的bean(仅仅存放名字 beanName),如果没创建完成,都会保存在这里面

下面我们接着看在创建 A,B 时它是如何从上述的缓存中变化的。

变化过程

1、这里我们以 A 和 B 为例,我们首先创建 A,此时是第一次创建,默认四个容器中都是空的。

Spring 源码分析如何解决循环依赖的问题_第9张图片

2、现在开始创建 A,此时需要往 singletonsCurrentlyInCreation 放入 A,表示 A 正在实例化,此时状态如下:

Spring 源码分析如何解决循环依赖的问题_第10张图片

3、下面正式创建 A 到 A 创建完成,此时将 A 封装成 ObjectFactory 对象,但是属性还没有赋值,我们先定义名称为 aObject ,此时状态如下:

Spring 源码分析如何解决循环依赖的问题_第11张图片

4、那么到这一步就要给 A 的属性赋值,了解循环依赖的小伙伴就知道,这里其实就是给 B 赋值,那么就要去创建 B,创建 B 的过程和创建 A 过程是一样的,依次从一级缓存、二级缓存、三级缓存中获取 B,很明显是获取不到的,那么就要创建 B,在singletonsCurrentlyInCreation 中加入 B,表示当前 B 正在创建,状态如下:

Spring 源码分析如何解决循环依赖的问题_第12张图片

5、接下来的步骤和步骤 3 中差不多,B 创建完成,但是 B 中的属性 b 还没有赋值,此时将 B 封装成 bObject 放到三级缓存,状态如下:

Spring 源码分析如何解决循环依赖的问题_第13张图片

6、此时开始给 B 的属性赋值,也就是给 a 赋值,那么它就要去创建 A,并且把 A 的内存地址给 B 中的 a 属性,那么创建 A 的过程和之前的一样,先依次从一级、二级、三级缓存中获取 A,那么这时可以从三级缓存中获取到 A 的,将获取到的 A 赋值给 B 的 a 属性,此时 B 即将创建完成了,在全部创建完的前一步,将三级缓存中的 B 移到二级缓存,因为实例化 B 的全部步骤全部做完了,此时的状态如下:

Spring 源码分析如何解决循环依赖的问题_第14张图片

7、B 实例化以后,需要从当前正在创建的容器(singletonsCurrentInCreation)中移除 B,这步表示 B 已经完成了,并且同时将 B 移到一级缓存 singletonObjects 中,此时状态如下:

Spring 源码分析如何解决循环依赖的问题_第15张图片

8、前面 B 已经创建完成,所以我们又到了给 A 的属性赋值的时候了,此时我们知道 B 已经创建完成了,所以 b ==B,此时 A 的实例化也快要结束了,A 也要从三级缓存移到二级缓存,过程和步骤 6 类型,此时的状态如下:

Spring 源码分析如何解决循环依赖的问题_第16张图片

9、最后 A 也要做一些类似步骤 7 结束的动作 ,到这里 A 和 B 都已经全部实例化完成了,此时的状态如下:

Spring 源码分析如何解决循环依赖的问题_第17张图片

源码

我们进入 AbstractBeanFactory 的 getBean 跟 doGetBean方法。

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;
                 
           // 方法1、从一级、二级、三级缓存中看看能否获取到
          Object sharedInstance = getSingleton(beanName);
          // 省略无关代码
         }
         else {
             // 如果是多例的循环引用,则直接报错
             if (isPrototypeCurrentlyInCreation(beanName)) {
                 throw new BeanCurrentlyInCreationException(beanName);
             }
             // 省略若干无关代码
             try {
                 // Create bean instance.
                 if (mbd.isSingleton()) {
                     // 方法2、获取单例对象
                     sharedInstance = getSingleton(beanName, () -> {
                         try { //方法3、创建 ObjectFactory 中 getObject 方法的返回值
                             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;
     }

在 doGetBean 方法整个过程当中会调用三次 doGetBean() 方法,我们还是以 A 和 B 进行分析。

第一次调用的时候会尝试获取 A 对象实例,此时走的是第一个 getSingleton() 方法,因为没有已经创建的 A 对象,于是这里获得的是null。

而后就会调用第二个 getSingleton() 方法,建立 A 对象的实例,而后递归的调用 doGetBean() 方法,尝试获取 B 对象的实例以注入到 A 对象中。

此时因为 Spring 容器中也没有 B 对象实例,于是仍是会走到第二个 getSingleton() 方法,在该方法中建立 B 对象的实例,建立完成以后,尝试获取其所依赖的 A 的实例做为其属性,于是仍是会递归的调用 doGetBean() 方法。

此时需要注意的是,在前面因为已经有了一个 A 对象的实例,于是这个时候,再尝试获取 A 对象的实例的时候,会走第一个 getSingleton() 方法,在该方法中会获得一个 A 对象的实例。而后将该实例返回,而且将其注入到 B 对象的属性 a 中,此时 B 对象实例化完成。而后将实例化完成的 B 对象递归的返回,此时就会将该实例注入到 A 对象中,这样就获得了一个 A 对象。

这个方法比较长,对于解决循环引用来说,上面标出来的 3 个方法起到了至关重要的作用,下面我们会挨个讲解到的。

getSingleton() 方法

我们的流程进行到 AbstractBeanFactory 的 doGetBean 方法时,会执行 Object sharedInstance = getSingleton(beanName),接着会执行 getSingleton(beanName,true),一路跟进去,最终会进到DefaultSingletonBeanRegistry 的getSingleton方法,这个方法十分重要,我们具体看一看:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //先从一级缓存中获取已经实例化属性赋值完成的 bean
    Object singletonObject = this.singletonObjects.get(beanName
        // 如果没有从一级缓存中获取到,且对象在正在创建中
        // 判断对象正在创建中的方法是判断 beanName 是不是在 singletonCurrentlyInCreation 这个 Set 里
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
        synchronized (this.singletonObjects) {
            //从二级缓存中获取,获取 bean 的早期引用,实例化完成但是未赋值完成的 bean
            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;
}

从源码可以得知,首先从一级缓存 singletonObjects 获取目标对象,若不存在且目标对象被标记为创建中,从二级缓存 earlySingletonObjects 中获取 bean。如果不存在,继续访问三级缓存 singletonFactories ,得到 bean 工厂对象,通过工厂对象获取目标对象。将目标对象放入二级缓存,删除三级缓存。

getSingleton() 方法重载

另外一个 Singleton 重载的方法:public Object getSingleton(String beanName, ObjectFactory singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
       //调用 getObject 方法创建 bean 实例
        Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
			   //重要步骤
		       //这个方法会将 beanName 添加到 singletonsCurrentlyInCreation 这个 Set 中
			   //这步添加操作会在后面调用 getSingletion(String, boolean )的时候起作用
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                //传入的 lambda 在这里会被执行,调用 createBean 方法创建一个 bean 后返回
                singletonObject = singletonFactory.getObject(); 
                newSingleton = true;
                singletonObject = this.singletonObjects.get(beanName);

            }
            // 创建完成后将对应的 beanName 从 singletonsCurrentlyInCreation 移除
            afterSingletonCreation(beanName);
    	}
    	if (newSingleton) {
            //加入一级缓存
        	addSingleton(beanName, singletonObject); 
    	}
	}
	return singletonObject;
}

addSingleton() 方法

	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
            //添加到一级缓存
			this.singletonObjects.put(beanName, singletonObject);
            //三级缓存移除
			this.singletonFactories.remove(beanName);
            //二级缓存移除
			this.earlySingletonObjects.remove(beanName);
            //beanName 放进单例注册表中  
			this.registeredSingletons.add(beanName);
		}
	}

从上面两个方法中,我们可以看到 bean 实例是由 singletonFactory.getObject() 获取到的,也就是通过 doGetBean() 方法中判断是否单例后的匿名内部类获取到的,从而知道获取到的 bean 是由 createBean() 方法创建的。

creatBean() 方法调用了 doCreatBean() 方法,所以实际的创建逻辑就再 doCreatBean() 方法中。

doCreateBean() 方法

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

    //bean 实例包装类
    BeanWrapper instanceWrapper = null;
     //创建 bean 的时候,这里创建 bean 的实例有三种方法
     //1.工厂方法创建
     //2.构造方法的方式注入
     //3.无参构造方法注入
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    //实例化后的 bean 对象,这里获取到的是一个早期对象,即没有进行属性填充的对象
    Object bean = instanceWrapper.getWrappedInstance();
    
    //解决循环依赖的关键步骤
    //earlySingletonExposure 表示是否提前暴露早期对象的引用
    //因为不论这个 bean 是否完整,他前后的引用都是一样的,所以提前暴露的引用到后来也指向完整的 bean
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName)); 
    //是否允许单例提前暴露
    if (earlySingletonExposure) {
        //如果一级缓存中没有当前 bean,就将当前 bean 放入三级缓存
        //同样也会移除二级缓存中对应的 bean,即便没有
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
	//此时 bean 已经实例化完成, 开始准备初始化
    // bean 为原始对象
    Object exposedObject = bean;
    try {
      //属性装配,属性赋值的时候,如果有发现属性引用了另外一个 bean,则调用 getBean 方法
        populateBean(beanName, mbd, instanceWrapper);
      //调用初始化方法,完成 bean 的初始化操作
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
	//如果 bean 允许被早期暴露,进入代码
    if (earlySingletonExposure) { 
        //第二参数为 false 表示不会从三级缓存中在检查,最多从二级缓存中找,其实二级缓存就够了,其实之前 getSingleton 的时候,已经触发了 A 的 ObjectFactory.getObject(),A 实例已经放入二级缓存中
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            //如果没有代理,进入这个分支
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference; /
            }
            
}

addSingletonFactory() 方法

接着我们看一下 addSingletonFactory方法源码:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        //如果一级缓存中不存在
        if (!this.singletonObjects.containsKey(beanName)) { 
            //添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            //从二级缓存中移除
            this.earlySingletonObjects.remove(beanName);
            //beanName 放进单例注册表中  
            this.registeredSingletons.add(beanName); 
        }
    }
}

从上面源码中,我们知道 bean 在实例化完成之后会直接将未装配的 bean 工厂存放在三级缓存中,并且移除二级缓存。

我们以 A 依赖 B, B 依赖 A,即 A 和 B 相互依赖,我们从头到尾说一下这个流程,如下:

  • 调用 doGetBean() 方法,想要获取 A,于是调用 getSingleton() 方法从缓存中查找 A
  • 在 getSingleton() 方法中,从一级缓存中查找,没有,返回 null
  • doGetBean() 方法中获取到的 A 为 null,于是走对应的处理逻辑,调用 getSingleton() 的重载方法)
  • 在 getSingleton() 方法中,先将 A-beanName 添加到一个集合中,用于标记该 bean 正在创建中。然后回调匿名内部类的creatBean 方法
  • 进入 AbstractAutowireCapableBeanFactory 的 doCreateBean,先反射调用构造器创建出 A 的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中)。判断为true,则将 A 添加到三级缓存中
  • 对 A 进行属性填充,此时检测到 A 依赖于 B,于是开始查找 B
  • 调用 doGetBean() 方法,和上面 A 的过程一样,到缓存中查找 B,没有则创建,然后给 B 填充属性
  • 此时 B 依赖于 A,调用 getSingleton() 获取 A,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到 A 的创建工厂,通过创建工厂获取到 singletonObject,此时这个 singletonObject 指向的就是上面在 doCreateBean() 方法中实例化的 A
  • 这样 B 就获取到了 A 的依赖,于是 B 顺利完成实例化,并将 A 从三级缓存移动到二级缓存中
  • 随后 A 继续他的属性填充工作,此时也获取到了 B,A 也随之完成了创建,回到 getSingleton() 方法中继续向下执行,将 A 从二级缓存移动到一级缓存中

讲解完以上的源码怎么实现的,接下来需要处理以下两个问题:

为什么 Spring 不能解决构造器的循环依赖?

从上面源码中流程可以看出来,在 bean 调用构造器实例化之前,一级、二级、三级缓存并没有 bean 的任何相关信息,在实例化之后才放入三级缓存中,因此当 getBean 的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

最后

由于 Spring 关于循环依赖的源码比较多,这里我只挑选了重点部分进行讲解, 其实主要思想就是利用二级、三级缓存对未初始化完成的 bean 进行提前的引用暴露,也就是将其设置为可引用的,这样当依赖于他的 bean 在进行属性填充时就可以直接拿到引用,解决了死循环的问题。

你可能感兴趣的:(java,spring,java,面试)