Spring 源码解析——IOC 源码解析(单例 Bean 的循环依赖解决方案)(三)

写文章不易,转载请标明出处。

同时,如果你喜欢我的文章,请关注我,让我们一起进步。

一、概述

在上一篇博文中我们已经重点分析了 Bean 的实例化和初始化的主体流程,在这篇博文中我们会在其基础上进一步探究 Bean 的初始化过程,并通过源码来分析在 Spring 中是怎样解决 Bean 循环依赖问题的。

但是在这里我们需要明确的一点是我们在这里所分析的是 Bean 的作用范围为单例(Singleton)时的情况,当 Bean 的作用范围为原型(Protype)时是采用其他的解决方案的,这点是需要注意的(网上有很多文章连前提都不提就开始讲所谓的 Bean 循环依赖的解决方案)。

二、问题描述

我们都知道 Spring 的最大的特色就是它的 IOC 机制(即控制反转),通过 IOC 机制开发者将 Java Bean 的控制权交给 Spring 容器,由 Spring 容器来完成 Bean 的实例化和初始化工作。但是在这个过程中可能遇到的一种问题就是 Bean 的循环依赖问题,何为循环依赖,顾名思义就是 Bean 中存在着相互依赖的关系,这种问题主要出现在当一个 Bean 中存在着另一个 Bean 作为属性,而另一个 Bean 中又使用了该 Bean 作为属性(听不懂?听不懂没关系,继续往下看)。

通过前面博文的分析,我们知道 Bean 的实例化过程其实总体上可以大致分为两个部分,第一个部分就是创建 Bean 实例(实例化),而第二个部分就是初始化 Bean(简单理解就是为 Bean 的属性赋值),并且我们知道 Bean 其实就是一个 Java 对象实例,它也包含了属性值和方法,而这种循环依赖的问题准确来说就出现在初始化过程中为属性赋值时,简单的可以用下图来理解。

    // 示例代码
    public class BeanA {
    	@Autowired
    	private BeanB bean;
    }
    
    public class BeanB {
	@Autowired
	private BeanA bean;
    }

通过示意图和代码我们可以大致了解到循环依赖的问题产生的原因,表面上来看是因为我们在一个 BeanA 中注入了一个 BeanB 类型的 Bean 作为属性值,而在 BeanB 中又注入了一个 BeanA 类型的 Bean 作为属性值,这看上去是一个循环(其实这里还可以有更多的对象来产生这种依赖关系),但是当你不理解 Bean 的生命周期时,你会觉得这好像没有什么大的问题啊,Spring 完全可以先实例化一个 BeanA 然后再实例化一个 BeanB(实例化过程中都不对其属性进行赋值),然后都实例化完成后再对它们互相赋值就可以了啊,这好像没问题啊,但是如果你了解了 Bean 的实例化过程你可能就不会这么想了(先祭出 Bean 实例化简化版流程图,未包含解决循环依赖方案)。

 之前我们分析过,在 Bean 的实例化(创建实例)完成后,Spring 会立即对它的属性进行赋值操作(属性注入),这时它首先会尝试从缓存池中获取 Bean 实例,如果获取失败它就会尝试首先去实例那个依赖的 Bean 实例。举个例子,还是上面的 BeanA 和 BeanB,当 Spring 开始实例化时,首先它会将 BeanA 和 BeanB 的 BeanDefinition 注册到 BeanFactory 的 BeanDefinitionMap 中,然后假设这里先开始实例化 BeanA,当 Spring 创建完 BeanA 后,它发现 BeanA 存在属性 bean,因此它开始尝试为 BeanA 的属性 bean 赋值,这时它发现 bean 的类型为 BeanB,所以它会先去到缓存池中获取 BeanB 实例,但是因为此时 BeanB 还没有被实例化所以获取失败,然后它就会去尝试先实例化 BeanB,但是当它完成 BeanB 的创建,开始对 BeanB 进行赋值的时候,又发现 BeanB 存在一个类型为 BeanA 的属性 bean,所以按照刚刚的逻辑,它又会去缓存池中尝试获取 BeanA 实例,但是因为此时 BeanA 仅仅被创建成功,还没有完成实例化(没有完成属性注入),所以它还没有被放入到缓存池中,那么 Spring 就又开始尝试实例化 BeanA,然后当它创建完 BeanA 将要对其进行赋值的时候,又发现它的一个属性类型为 BeanB,所以他又去实例化 BeanB,最后陷入了无解的死循环(大致流程如下图)。

三、解决思路分析

我们可以先尝试着自己去思考一下,如果我们开发的框架中遇到了这种问题,我们会怎么去解决它。首先,分析出现问题的根本原因就是当 BeanA 要去注入 BeanB 时,在实例化 BeanB 的过程中又要去注入 BeanA,但此时存在的问题就是因为我们的 BeanA 还没有实例化完成,所以没有被添加到单例缓存池中,因此也就没有办法在实例化 BeanB 的过程中被获取到,因而导致最后在为 BeanB 进行属性注入时又不得不去尝试先去实例化 BeanA。

但是我们仔细思考一下,当我们在为 BeanB 进行属性注入的时候,这个时候 BeanA 真的是完全不可用的么?我们知道 Bean 的实例化分为两个阶段,即创建 Bean 对象,然后为 Bean 对象赋值,这也就是说其实当我们在为 BeanB 进行属性注入的时候 BeanA 已经被创建出来了,只是还没有进行属性的赋值操作,换句话说这个时候的 BeanA 其实已经是一个可用的实例对象了,只不过属性值还为空而已,但这又有什么关系呢,我们在为 BeanB 进行属性注入的时候,需要的只是 BeanA 实例的一个引用而已,我们又不需要它已经被完全实例化好了(属性已经完成赋值操作)。

因此,我们其实可以有这样的一个解决思路,就是创建一个集合来单独保存位于中间状态(创建完成但未被赋值)的 Bean,我们这里姑且称他为中间态缓存池(Spring 中称为 earlySingletonObjects 和 singletonFactories)。然后当我们已经创建好一个 Bean 实例后就将它放到这个缓存池中,这样当我们后续再为 BeanB 进行属性注入时先从单例缓存池中获取,获取失败后再尝试从中间缓存池中获取,而因为此时 BeanA 已经创建完成了,所以这里就一定可以获取到 BeanA 的实例对象(虽然此时 BeanA 还未完成属性赋值,但这并不重要),从而就可以正常的完成 BeanB 的属性赋值操作,也就可以完成 BeanA 的属性赋值操作,最终也就打破了这个循环引用的问题。

整体思路的流程图放在下面了,然后我们接着来通过 Spring 的源码来分析一下 Spring 中的具体代码实现。

四、源码解析

在开始进行源码解析之前先声明一下,因为对于 Bean 的实例化和初始化过程在前面的博文中已经详细的分析过了,因此在这篇博文中不再赘述,并且为了使分析的思路更加清晰,我会将无关的代码都删除掉,只留下与 Spring 解决单例 Bean 循环依赖相关的代码。对于源码的解析我们直接从实例化 BeanA 的 getBean 方法开始,示例的代码如上。

	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false); // name: "beanA"
	}

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

		// 1.调用 getSingleton 重载版本一获取 BeanA 实例
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
				// 2.上面获取失败因此尝试使用 getSingleton 重载版本二获取 BeanA 实例
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
			}
		}

		return (T) bean;
	}

首先 Spring 会通过调用 getBean 方法来获取 Bean 实例,这个方法实际会调用到 doGetBean 方法,而在这个方法中首先会尝试使用 getSingleton 重载版本一方法来从单例缓存池中获取 Bean 实例,当获取失败后又经过一系列的逻辑后最终又会调用到 getSingleton 重载版本二方法来创建 Bean 实例。

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
                        // 3.尝试从单例缓存池中获取 BeanA 实例
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
                                // 4.调用 beforeSingletonCreation 方法
				beforeSingletonCreation(beanName);

				try {
                                        // 6.因为外部入参的 singletonFactory 是一个 Lambda 表达式
                                        // 所以调用其 createBean 方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				finally {
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

	protected void beforeSingletonCreation(String beanName) {
                // 5.将当前 BeanA 的 BeanName(beanA) 添加到 singletonsCurrentlyInCreation 集合中
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

	protected void afterSingletonCreation(String beanName) {
                // 将当前 BeanA 的 BeanName(beanA) 从 singletonsCurrentlyInCreation 集合移除
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

 进入到 getSingleton 重载版本二方法后,它首先会再次尝试从单例缓存池中获取 Bean 实例,当获取失败后会进入到正式的实例化流程,首先会调用 beforeSingletonCreation 方法,这个方法的代码我也贴在了下面,这一步就是解决循环依赖的第一个关键点,即将当前 Bean 的 BeanName 添加到 singletonsCurrentlyInCreation 集合中,这也就意味着当前 Bean 已经进入到了实例化流程(这一步对应着当在实例化 BeanB 过程中尝试获取 BeanA 实例来进行属性注入时调用 getSingleton 重载版本一方法时,getSingleton 方法里面的一个判断方法即 isSingletonsCurrentlyInCreation,如果确认 BeanA 已经在创建过程中了那么就表示此时已经进入了循环依赖状态,所以开始尝试从中间态缓存池中获取实例化完成但初始化未完成的 BeanA 实例来打破循环依赖)。

之后会调用入参 singletonFactory 的 getObject 方法来获取 Bean 实例,而这个 singletonFactory 就对应着外部传进来的 Lambda 表达式中的 createBean 方法,因此首先会调用 createBean 方法来创建一个 Bean 对应的 FactoryBean 对象。

这里可以稍微提一下的是下面的 afterSingletonCreation 方法对应着上面的 beforeSingletonCreation 方法,这个方法的主要作用就是将 BeanName 从 singletonsCurrentlyInCreation 集合中移除,标志着当前 Bean 已经完成了实例化。

 

        protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
            try {
                // 7.调用 doCreateBean 方法创建 Bean 实例 
                Object beanInstance = doCreateBean(beanName, mbdToUse, args);
                return beanInstance;
            }
        }

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

		if (instanceWrapper == null) {
		        // 8.创建 BeanA 实例对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}

		// 9.判断 BeanA 时候为单例、当前是否允许循环引用
                // 以及当前 BeanA 是否存在于 singletonCurrentlyInCreation 集合中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
                        // 10.因为 BeanA 符合上述判断条件因此调用 addSingletonFactory 方法
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		try {
                        // 13.对 BeanA 实例进行属性注入
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

	}

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
                                // 11.将 BeanA 的 FactoryBean 添加到集合 singletonFactories 中
				this.singletonFactories.put(beanName, singletonFactory);
                                // 12.移除 earlySingletonObjects 中的 BeanA 实例缓存
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

 这里面的 createBean 方法是一个空壳方法,其直接调用了 doCreateBean 方法,在 doCreateBean 方法中首先调用了 createBeanInstance 方法来创建 Bean 实例对象,然后计算了一个判断表达式赋值给 earlySingletonExposure,对于这个判断表达式一共三个条件,首先当前被实例化的 Bean 一定是一个单例 Bean,并且当前是允许循环引用的,最后判断当前 Bean 是在 singletonsCurrentlyInCreation 集合中的,因为我们上面已经将它的 BeanName 添加到集合中了,所以这三条都是满足的,因此会调用 addSingletonFactory 方法,这个方法就是解决循环依赖问题的第二个关键点,在这方法里面它将当前 Bean 对应的 FactoryBean 添加到了singletonFactories 集合中,然后移除了 earlySingtonObjects 中缓存的该 Bean 实例(此时其实集合中并不存在该实例)。

至于为什么在这里不直接将已经创建的 Bean 实例放到 earlySingtonObjects 集合中,而是先把它的 FactoryBean 放到 singletonFactories 集合中,后面再从这个集合中去获取 BeanFactory,再通过 getObject 方法来获取 Bean 实例,我认为主要的原因可能是因为在将 Bean 放入到中间态缓存池的过程中其实 Spring 还做了一些其它的工作,调用了一些 BeanPostProcessor,具体代码我们可以稍微看一下 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 实例对象已经被创建成功,但是它得属性值仍为空(如下图),因此就接下来会调用 populateBean 方法来对 Bean 实例进行属性注入(之后又会调用 initializeBean 方法来调用一些相关的初始化方法,跟本文无关,所以暂时不分析)。 

	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		if (hasInstAwareBpps) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                                        // 14.当 bp 为 AutowiredAnnotationBeanPostProcessor 时
                                        // 调用它的 postProcessProperties 方法进行 BeanA 属性自动注入
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				}
			}
		}
	}

	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
                // 15.获取注入元数据
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
                        // 16. 对 BeanA 进行属性注入
			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;
	}

跟进 populateBean 方法,在这里我直接省略了所有的无关代码,直接看为属性进行赋值的核心代码,因为在示例代码中我们是采用的 @Autowired 注解来进行自动注入的,在 Spring 中对于 @Autowired 自动注入采用的是 BeanPostProcessor 来实现的,也就是我们常说的后置处理器,因为如果详细分析 @Autowired 的自动注入过程就该偏离了本文的核心,所以我们这里不进行分析(后面会有博文专门分析),只需要知道属性自动注入使用的 BeanPostProcessor 是 AutowiredAnnotationBeanPostProcessor 即可。在这里调用了它的 postProcessProperties 方法,而在 postProcessProperties 方法中首先获取了 Bean 的元数据信息(具体的 metadata 信息如下截图),然后开始对 Bean 实例进行属性注入。

	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
                                // 17.遍历获取所有属性调用 InjectedElement.inject 进行属性注入
				element.inject(target, beanName, pvs);
			}
		}
	}

 接下来在 InjectionMetadata 的 inject 方法中又获取了所有的属性,然后通过遍历对每一个属性调用 InjectedElement 的 inject 方法进行处理。

	protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			try {
                                // 18.属性解析判断等操作方法调用链
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
                                // 成功获取到属性实例对象后通过该方法赋值
				field.set(bean, value);
			}
		}
	}


	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
			if (result == null) {
                                // 18.属性解析判断等操作方法调用链
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);    
			}
			return result;
	}

	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		try {
			if (instanceCandidate instanceof Class) {
                                // 18.属性解析判断等操作方法调用链
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
		}
	}

	public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException {
                // 19.调用 getBean 方法获取 BeanB 实例
		return beanFactory.getBean(beanName); // beanName: "beanB"
	}

 在进入到 InjectedElement 的 inject 方法后又进行了一系列验证和方法调用,我们直接略过这些调用(具体的方法调用链见下图),直接来到核心的 resolveCandidate 方法,在这个方法中调用了 getBean 方法来尝试获取 BeanB 实例(注意此时我们还在初始化 BeanA 实例的流程中)

 

 通过调用 getBean 方法其实来获取 BeanB 实例其实就已经回到了最开始的代码逻辑中(博文开始处也是从 getBean 方法开始分析),因为我们上面已经完整的分析过 BeanA 的 getBean 流程了,BeanB 的与它的相同,因此我们这里就不再重述一遍了,直接跳过 BeanB 执行上面的流程,走到这一步骤(此时位于 BeanB 的初始化流程中,它所执行的 resolveCandidate 是为了获取 BeanA 实例来注入其属性。具体可见下面两张图,第一张是当 BeanA 执行到这步时尝试获取 BeanB 实例来注入属性,第二张是当 BeanB 执行到这步时尝试获取 BeanA 实例来注入属性,图三为)

 接下来就是解决方案中最关键的地方了,也就是当 BeanB 实例创建完成后,在为 BeanB 进行属性注入 BeanA 实例时,会通过调用 getBean 方法来获取 BeanA 的实例,但 getBean 方法是一个空壳方法,因此最终会调用到 doGetBean 方法,此时通过下图我们可以看到 doGetBean 方法中会通过 getSingleton 重载版本一方法来获取 BeanA 实例。

 进入到 getSingleton 重载版本一方法中,这次它也会先尝试从单例缓存池中获取 BeanA 实例,但因为此时 BeanA 的实例化过程还没有完成(实例创建完成但未完成赋值),因此这时仍然获取不到 BeanA 实例,然后它就会去判断此时我们要获取的 Bean 是否位于 singletonsCurrentlyInCreation 集合中(换句话说就是判断此时我们要获取的 Bean 是不是正在实例化过程中,此时是不是正处于循环引用状态),因为我们前面在 BeanA 执行 getSingleton 的 beforeSingletonCreation 方法时已经将 BeanA 对应的 BeanName 添加到 singletonsCurrentlyInCreation 中了,所以这里的判断一定是成立的,然后它首先会尝试从 earlySingletonObjects 集合中获取中间态 BeanA 实例,但因为此时中间态实例还未被加入到 earlySingletonObjects 中,因此接着它就会尝试去从 singletonFactories 集合中去获取 BeanA 所对应的 FactoryBean,当其成功获取到 FactoryBean 之后,就可以通过 FactoryBean 的 getObject 方法来获取到中间态的 BeanA 实例(创建完成但属性未被赋值),接着会将 FactoryBean 从集合中移除并将刚刚获取到的中间态 BeanA 实例添加到 earlySingletonObjects 集合中(这样如果还存在更多的循环依赖,下次再获取中间态 BeanA 实例就不需要通过 FactoryBean 了,而是可以直接从 earlySingletonObjects 集合中获取),最后再将中间态 BeanA 实例返回。这样就完美的解决了循环依赖的问题。

 当重载版本一的 getSingleton 方法成功获取到中间态 BeanA 实例后就不会再去执行重载版本二的 getSingleton 方法,而是会直接将获取到的中间态 BeanA 实例返回,因此之前方法调用会一直将这个中间态 BeanA 实例返回,最终返回到 BeanB 的 InjectedElement 的 inject 方法,通过 field.set 方法完成了 BeanB 中 bean 属性(类型为 BeanA)的赋值操作 ,最后将已经初始化完成的 BeanB 实例保存到单例缓存池中并同时将其返回给 BeanA 的 InjectedElement 的 inject 方法,再通过 field.set 方法完成了 BeanA 中 bean 属性 (类型为 BeanB)的赋值操作,最终将实例化完成的 BeanA 实例添加到单例缓存池中并返回,这样就完成了 BeanA 和 BeanB 的实例化工作。

	protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			try {
                                // 属性解析判断等操作方法调用链
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
                                // 成功获取到属性实例对象后通过该方法赋值
				field.set(bean, value);
			}
		}
	}

五、内容总结

 这篇博文到这里就结束了,如果你还有什么疑问可以评论在下面,我们一起来讨论。最后的内容总结我就把整体的一个方法调用流程图放在这里了,这个流程图可能有的地方不是特别严谨,主要是没有详细区分是层级调用还是平级调用,但毕竟我们主要在意的是方法的整个调用流程,所以总的来说应该也还是比较好理解的,等以后有时间可能会补一些新的更加详细的流程图。

生活是公平的,哪怕吃了很多苦,只要你坚持下去,一定会有收获,即使最后失败了,你也获得了别人不具备的经历。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章