从源码分析Spring是如何解决循环依赖的

循环依赖问题

什么是循环依赖

首先看一下下面的Spring配置文件java

<!-- beanA依赖于beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
    <property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依赖于beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
    <property name="beanA" ref="beanA"/>
</bean>
复制代码

当IOC容器读取上面的配置时,就会先对beanA进行加载;在对beanA进行属性填充时,会发现beanA依赖于beanB,而后就会对beanB进行加载;当对beanB进行属性填充时,又会发现beanB依赖于beanA,因而就加载beanA... 能够想到,若是Spring的容器对于这种循环依赖问题不做出响应的处理,那么就会无限执行上面的过程。最终的结果就可能形成OOM从而致使程序崩溃 spring

WX20200213-173232@2x.png

Spring中bean注入的方式

咱们知道在Spring中,注入bean的方式有【构造器注入】和【setter注入】两种方式。但在咱们使用Spring管理bean时,可能会遇到一种特殊的状况,那么就是上面所说的循环依赖问题 咱们再看一下Spring建立bean的过程缓存

Spring建立bean的过程

若是阅读过IOC相关的源码就会知道,建立bean的过程大致能够分为初始化bean对bean的属性进行填充对bean进行初始化三个步骤app

  • 初始化bean:即new一个bean实例,是经过反射调用构造器实现的
  • 对bean的属性进行填充:能够理解为对标签相应的属性进行赋值
  • 对bean进行初始化:即调用事先配置好的init-method方法,因此能够将一些初始化的行为写到这个方法中

而后就来分析一下两种注入方式ui

构造器注入

在普通的java程序中,若是已经new出了一个对象,咱们就知道这个对象已是可用的了,不论它的属性是否完整。 但在Spring中,建立出来的bean必需要完成三个步骤才能被认为是可用的,才会将这个“完整”的bean放入到IOC容器中。 由于构造器注入是在实例化对象时反射调用构造器去注入参数,因此既然beanA、beanB的都拿不到完整的依赖,就会进行无限的循环调用,从而没法解决【循环依赖问题】。解决办法就只有是修改依赖关系了this

setter注入

再看一下setter注入方式 setter注入方式就是new出一个对象后,调用该对象的set方法对属性进行赋值。此时对象已经被new出来了,只不过是不完整而已。 若是出现了循环依赖的问题,这就要比构造器注入的方式好的多 因此Spring对于循环依赖问题的解决就是针对于setter方法的spa

接下来就开始分析Spring是如何解决循环依赖问题的3d

Spring对于循环依赖的解决

先提早知道一下问题大概是怎样解决的

首先咱们要知道,Spring对于循环依赖的问题是采用【缓存】的方式解决的 看一下Spring源码中的DefaultSingletonBeanRegistry类(注:SingletonBeanRegistry接口提供了关于访问单例bean的功能,DefaultSingletonBeanRegistry就是该接口的默认实现)代理

/** Cache of singleton objects: bean name to bean instance. */
    // 用于存储完整的bean,接下来称之为【一级缓存】
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of early singleton objects: bean name to bean instance. */
    // 用于存储不完整的bean,即只是new出来,并无属性值的bean,接下来称之为【二级缓存】
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    //用于存储bean工厂对象,接下来称之为【三级缓存】
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

复制代码

大概捋一遍bean的获取、建立过程

由于循环依赖都是产生在获取bean时,因此咱们直接从AbstractBeanFactory的getBean()方法开始code

  1. AbstractBeanFactory#getBean()没什么自身的实现,只调用了doGetBean()
  2. AbstractBeanFactory#doGetBean(),在这个方法中调用了getSingleton(beanName)获取实例:Object sharedInstance = getSingleton(beanName);
  3. 判断sharedInstance是否为null,若是不为null则调用getObjectForBeanInstance处理,而后返回。也就是IOC容器获取bean成功,能够拿去使用了。若是sharedInstance为null,则调用getSingleton(beanName,Object{...})方法
  4. DefaultSingletonBeanRegistry#getSingleton中,首先会从【一级缓存】中get一下bean,若是获取不到,则会进入建立bean的流程
  5. 建立bean的主要逻辑就是走AbstractAutowireCapableBeanFactory#doCreateBean,先是使用createBeanInstance方法建立bean的实例,而后对bean进行初始化,再进行属性填充....而后返回bean
  6. 获取到bean,完成

上面并无涉及到循环依赖和二级、三级缓存的问题,由于对于循环依赖的处理,都表如今代码中的细节之处

对应上面的过程,从源码上开始分析

首先看doGetBean方法

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
		// 从缓存中获取单例bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) { //若是获取到单例bean,则走下面代码
            //......
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}else {//若是没有获取到单例bean,则走下面代码 
                //...... 
				// 若是是单例的Bean,请下面的代码
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							// 建立单例Bean的主要方法,返回的bean是完整的
							return createBean(beanName, mbd, args);
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
                //......
        }
		return (T) bean;
	}
}
复制代码

上面的代码中,sharedInstance是经过getSingleton()方法得到的,实际上getSingleton(beanName)方法没什么逻辑,内部调用了getSingleton(beanName, boolean)这个方法,因此接下来就进入到这个方法中

getSingleton(beanName, boolean)的实现

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 从一级缓存中获取单例对象
		Object singletonObject = this.singletonObjects.get(beanName);
		// isSingletonCurrentlyInCreation : 判断当前单例bean是否正在建立中,也就是没有初始化完成。好比beanA的构造器依赖了beanB对象因此得先去建立B对象,或者在A的populateBean过程当中依赖了B对象,得先去建立B对象,这时的beanA就是处于建立中的状态
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				// 从二级缓存中获取单例bean
				singletonObject = this.earlySingletonObjects.get(beanName);
				// allowEarlyReference :是否容许从singletonFactories中经过getObject拿到对象
				if (singletonObject == null && allowEarlyReference) {
					// 从三级缓存中获取单例bean
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						// 经过单例工厂获取单例bean
						singletonObject = singletonFactory.getObject();
						// 从三级缓存移动到了二级缓存,并移除singletonFactory
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
复制代码

从上面的代码中能够总结出如下几点:

  1. 先从【一级缓存】中查找,有则直接返回
  2. 若是在【一级缓存】中获取不到,而且对象正在建立中(beanName包含在singletonsCurrentlyInCreation),那么就再从【二级缓存】中查找,有则直接返回
  3. 若是仍是获取不到,且容许singletonFactories(allowEarlyReference=true)经过getObject()获取,就从【三级缓存】中获取(singletonFactory.getObject())。经过ObjectFactory获取到的对象,是进行代理后的对象(假设有AOP)。将从【三级缓存】中获取到的对象放到【二级缓存】中,同时删除此beanName对应的【三级缓存数据】

再看一下doGetBean()方法中刚刚没有讲到的“if-else”部分

若是getSingleton()方法获取到了bean,即sharedInstance不为null,则对其进行处理而后返回 若是sharedInstance为null,就要走else中的代码了 首先判断一下是否为单例,(mbd是经过读取配置文件中bean标签生成的bean的定义信息,具体得到的方法这里不详细说了)。由于多例的bean是不须要放入到IOC容器中的,因此这里只处理单例bean 若是为单例,则调用getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        // ......
        // 建立 bean 实例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;
        if (newSingleton) {
            // 添加新建立的bean添加到【一级缓存】中,并删除其余缓存中对应的bean
            addSingleton(beanName, singletonObject);
        }
        // ......
        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 将新建立的bean添加到【一级缓存】中
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        // 从其余缓存中移除相关的bean
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
复制代码

上面的代码主要包含了两个功能

  1. 获取完整的bean实例
  2. 将新的bean添加到【一级缓存】中,之后getBean的时候就能够直接获取了

能够看到bean实例是由singletonFactory.getObject()拿到的,也就是经过doGetBean()方法中判断是否单例后的匿名内部类获取到的,从而知道获取到的bean是由createBean()方法建立的

creatBean()方法调用了doCreatBean()方法,因此实际的建立逻辑就再doCreatBean()中

doCreatBean()

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

        // 默认调用无参构造实例化Bean
		// 构造方法的依赖注入,就是发生在这一步
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// 实例化后的Bean对象,这里获取到的是一个原始对象,即没有进行属性填充的对象
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		
        //......

		// 解决循环依赖的关键步骤
        // earlySingletonExposure:是否”提早暴露“原始对象的引用
        // 由于不论这个bean是否完整,他先后的引用都是同样的,因此提早暴露的引用到后来也指向完整的bean
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		// 若是须要提早暴露单例bean,则将该bean工厂放入【三级缓存】中
		if (earlySingletonExposure) {
			// 将刚建立的bean工厂放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
            // 一样也会移除【二级缓存】中对应的bean,即使没有
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
            //填充属性(依赖注入)
			populateBean(beanName, mbd, instanceWrapper);
			//调用初始化方法,完成bean的初始化操做
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		//......

		return exposedObject;
	}
复制代码

ok,看到这里,整个在有循环依赖问题下建立、获取bean的流程就结束了 举个例子,从头串一下流程。假设beanA->beanB, beanB->beanA,即A、B相互依赖

  1. 调用doGetBean()方法,想要获取beanA,因而调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到的beanA为null,因而走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的) (ps:如今是2020.2.14 凌晨1点07分,情人节了,由于疫情不能和小杨一块儿,在个人第一篇博客中记念一下这个节日😂,祝全部人情人节快乐)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在建立中。而后回调匿名内部类的creatBean方法
  5. 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器建立出beanA的实例,而后判断:是否为单例、是否容许提早暴露引用(对于单例通常为true)、是否正在建立中(便是否在第四步的集合中)。判断为true,则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB,因而开始查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程同样,到缓存中查找beanB,没有则建立,而后给beanB填充属性
  8. 此时beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的建立工厂,经过建立工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
  9. 这样beanB就获取到了beanA的依赖,因而beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
  10. 随后beanA继续他的属性填充工做,此时也获取到了beanB,beanA也随之完成了建立,回到getSingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

最后

整个过程大概就是这样了,因为spring的源码比较多,就只挑选了重点部分进行注释 其实主要思想就是利用二级、三级缓存对未初始化完成的bean进行提早的引用暴露,也就是将其设置为可引用的,这样当依赖于他的bean在进行属性填充时就能够直接拿到引用,解决了死循环的问题

还有几个比较重要的点,在这里指出位置,能够根据这些去查找看

  • 这三个级别的缓存,在同一时间,同一beanName对应的bean只会存在于一个缓存中
  • 若是没有循环依赖的问题,二级、三级缓存是没有用处的,体如今AbstractAutowireCapableBeanFactory#doCreateBean的判断earlySingletonExposure这个地方
  • 判断循环依赖,是用一个Set集合实现的,正在建立中的beanName会加到这个集合中
  • 三级缓存其实还有建立AOP代理的功能,在AbstractAutowireCapableBeanFactory#createBean调用resolveBeforeInstantiation的位置。而若是没有循环依赖问题,那么代理就是在调用init-method过程当中建立的
  • bean实例化以后,属性填充以前,若是有循环依赖,就将这个bean封装到一个ObjectFactory而后放到三级缓存中(为了提早暴露引用)
  • 三级缓存中的ObjectFactory第一次拿出被他保存bean后,这个bean就会进入二级缓存
  • bean被建立完整后,进入一级缓存

》》》》》》》》》》》》》》》》》》》》》

有些东西不知道怎么转述成语言表达出来,还有若是有很差的或者说错的地方但愿看过的大佬能帮忙指正,谢谢~~

相关文章
相关标签/搜索