Spring源码之Bean循环依赖问题

本文尝试回答以下几个问题

  1. Spring如何表示在Bean创建过程当中Bean与Bean之间的依赖关系以及这些依赖关系是何时建立的
  2. Spring如何检测循环依赖
  3. 哪些情况下的循环依赖能够解决,哪些不能,Spring是如何解决的

在阅读本文前,熟悉BeanFactory体系,Bean的创建流程和各种回调接口的触发时机,有助于理解本文。

Bean之间的依赖关系

如果某个类A在运行时需要B的协助,则可以在A中声明一个B类型的成员变量,此时就可以看做A依赖B。如果A和B都作为Bean被Spring容器管理,那么在容器启动时需要自动将B注入给A。Spring容器支持的依赖注入方式有:构造器注入、属性注入、工厂方法注入(静态工厂或实例工厂)。
Spring中单例Bean的注册、注销和管理都是DefaultSingletonBeanRegistry实现的。首先我们看一看这个它是如何管理Bean的依赖关系的。

/** /** Map between dependent bean names: bean name to Set of dependent bean names. */
	private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

	/** Map between depending bean names: 
        bean name to Set of bean names for the bean's dependencies. */
	private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

dependentBeanMap代表一个Bean和依赖它的Bean之间的映射关系,表示某个Bean被哪些Bean依赖
dependenciesForBeanMap代表 从一个Bean 到 它依赖的Bean之间的映射关系,表示某个Bean依赖哪些Bean
举个例子:如果A依赖B ,B依赖C和D,C也依赖D,D又依赖A,则两个Map中的元素如下:
dependentBeanMap:
A-->{D}
B-->{A}
C-->{B}
D-->{B,C}
dependenciesForBeanMap:
A-->{B}
B-->{C,D}
C-->{D}
D-->{A}
Bean之间的这种依赖关系何时建立?通过什么方式建立?DefaultSingletonBeanRegistry通过registerDependentBean方法建立Bean之间的依赖关系。

/**
	 * Register a dependent bean for the given bean,
	 * to be destroyed before the given bean is destroyed.
	 * @param beanName the name of the bean
	 * @param dependentBeanName the name of the dependent bean
     * 第一个参数:被依赖的Bean的名字
     * 第二个参数:依赖第一个参数的Bean的名字
	 */
	public void registerDependentBean(String beanName, String dependentBeanName) {
		String canonicalName = canonicalName(beanName);

		synchronized (this.dependentBeanMap) {
			Set<String> dependentBeans =
					this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
			if (!dependentBeans.add(dependentBeanName)) {
				return;
			}
		}

		synchronized (this.dependenciesForBeanMap) {
			Set<String> dependenciesForBean =
					this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
			dependenciesForBean.add(canonicalName);
		}
	}

registerDependentBean方法何时被调用呢?

依赖声明方式 依赖关系注册时机
构造器注入 Spring在创建Bean实例阶段,会对构造器参数进行解析。在解析构造方法参数时,创建和加载依赖bean成功后,注册依赖关系
属性注入 在填充Bean属性阶段,在解析Bean属性值时,获取依赖bean成功后,注册依赖关系
实例工厂方法注入 不注册依赖关系
depend-on 在当前Bean创建之前,加载和创建其依赖的Bean之前,注册依赖关系
autowireByType或autowireByName 在填充Bean属性阶段,在解析Bean属性值时,加载和创建依赖的bean成功后,注册依赖关系

至此,我们已经清楚Bean依赖关系的建立方式和时机。那么Spring是如何检测循环依赖的呢?

循环依赖的检测

循环依赖的几种形式

这里我们不再解释有关循环依赖的概念。但我们要清楚,循环依赖有哪几种表现形式,Spring能够处理的循环依赖需要满足哪些条件:

  1. 所有构造方法造成的循环依赖Spring是不能处理的,但是能检测出来并给出错误提示。
  2. 所有Prototype作用域的Bean之间的循环依赖Spring是不能处理的,同样只能检测并给出错误提示。
  3. Spring只能处理单例Bean的属性注入形式造成的循环依赖,且发生循环依赖时单例Bean不能被代理增强。

示例:

public class Door {

    private Lock lock;

    public Door() {
    }

    public Door(Lock lock) {
        this.lock = lock;
    }

    public Lock getLock() {
        return lock;
    }

    public void setLock(Lock lock) {
        this.lock = lock;
    }
}


public class Lock {

    private Door door;

    public Lock() {
    }

    public Lock(Door door) {
        this.door = door;
    }

    public Door getDoor() {
        return door;
    }

    public void setDoor(Door door) {
        this.door = door;
    }
}

使用构造方法注入方式造成循环依赖,Spring无法处理,只能给出错误提示:

<bean id="lock" class="com.lspring.beans.Lock">
     <constructor-arg ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door">
     <constructor-arg ref="lock"/>
</bean>

Prototype作用域的Bean之间的循环依赖Spring是不能处理的,同样只能检测并给出错误提示:

<bean id="lock" class="com.lspring.beans.Lock" scope="prototype">
      <property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" scope="prototype">
      <property name="lock" ref="lock"/>
</bean>

单例Bean的属性注入方式导致的循环依赖,Spring可以处理,如:

<bean id="lock" class="com.lspring.beans.Lock" >
      <property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" >
      <property name="lock" ref="lock"/>
</bean>

循环依赖的检测方式

如果发生循环依赖且Spring不能自动处理,则会报BeanCurrentlyInCreationException异常:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'lock': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:264)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:303)
	... 16 more

想用弄清楚Spring是如何检测循环依赖的,我们必须清楚Spring加载和创建Bean的整个流程。单例Bean的加载和创建流程如下:
Bean加载流程

转化为逻辑流程图如下:

在整个Bean的加载过程中,Spring是在哪一步对单例的循环依赖做检测的呢?答案其实很简单,就在beforeSingletonCreation方法中:

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

这一步需要将Bean标记为正在创建状态,其实就是将Bean名称加入到singletonsCurrentlyInCreation这个set集合当中。如果加入失败,说明当前即将创建的Bean已经是正在创建状态了,此时一定发生了循环依赖,直接抛异常。这里读者在阅读源码的时候先不用纠结!this.inCreationCheckExclusions.contains(beanName)这个判断,这是对singletonsCurrentlyInCreation的一种屏蔽机制。Spring在创建过程中对于那些已经处于正在创建状态的Bean可以屏蔽其状态。
对于Prototype的Bean的循环依赖,其检测方式与单例Bean类似:

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}

读者可自行查看源码。

解决单例循环依赖

Spring是如何解决单例Bean在属性注入时的循环依赖问题的呢?答案就隐藏在将Bean提早曝光到三级缓存这一步骤中,将Bean提早曝光后,其他依赖这个Bean的Bean在创建时就可以提前从三级缓存中加载到这个Bean了,而当前Bean的创建流程则继续。其实就是把一个半成品的bean给容器,让其他bean先用着。我们首先来看这个三级缓存的结构:

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

第一级缓存singletonObjects:存放已经完全创建好的Bean,这里的完全创建好,指的是走完整个Bean的创建流程,实例化、属性填充和初始化三个阶段。
第二级缓存earlySingletonObjects:存放通过singletonFactories中的ObjectFactory的getObject()方法拿到的提早曝光的Bean.这里面存放的Bean并不是被Spring完全加工好的,只是经历了SpringBean创建流程的第一个阶段,即初始化阶段,并没有经历属性填充和初始化。
第三级缓存singletonFactories:存放能够得到早期曝光Bean的ObjectFactory。通过ObjectFactory的getObject()方法能够拿到早期曝光的Bean实例。
首先看下关于这三个缓存的三个操作方法:

	/**
	 * Add the given singleton object to the singleton cache of this factory.
	 * <p>To be called for eager registration of singletons.
	 * @param beanName the name of the bean
	 * @param singletonObject the singleton object
	 */
	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);
		}
	}

Spring只会在Bean创建完成之后才会调用addSingleton将Bean添加到单例池中,一旦Bean被添加到单例池中,说明Bean已经被完全创建完成,earlySingletonObjects和singletonFactories中的信息立即失效,被移除。

	/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				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;
	}

获取单例Bean时,先尝试从singletonObjects单例池中获取,如果没有,则从earlySingletonObjects获取早期曝光的Bean,如果还是没有,则allowEarlyReference参数判断,从singletonFactories中拿到可以获取早期Bean的ObjectFactory,执行getObject方法。注意,getObject方法一旦执行,获取的早期Bean被放入earlySingletonObjects中,而ObjectFactory立即失效,从singletonFactories中移除。具体getObject执行的是什么逻辑?寻着代码,找到只有addSingletonFactory会向singletonFactories添加ObjectFactory对象

	/**
	 * Add the given singleton factory for building the specified singleton
	 * if necessary.
	 * <p>To be called for eager registration of singletons, e.g. to be able to
	 * resolve circular references.
	 * @param beanName the name of the bean
	 * @param singletonFactory the factory for the singleton object
	 */
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

而调用addSingletonFactory的代码只有一处:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

在doCreateBean方法中,Spring将Bean实例化好之后,立即就通过该方法将Bean暴露出去。请思考:问什么不将Bean直接放到earlySingletonObjects中去,而提供一个ObjectFactory放到singletonFactories呢,在获取Bean的时候再调用getObject方法取呢?这是Spring用三级缓存,而不是用二级缓存解决循环依赖的关键。
分析完以上几个方法的逻辑,再回头看看上文中的Bean加载的逻辑流程图,我们分析下这三个操作缓存的方法的调用时机。

  1. 首先,Spring在获取Bean时,先使用getSingleton(beanName,true)从缓存中加载,true表示三级缓存都会被检查。
  2. 如果没有加载到,则着手创建bean,不过在真正创建bean之前,先将bean标记为正在创建状态。
  3. 开始创建Bean,首先对Bean进行实例化(通过合适的实例化策略),这里简单理解为通过构造方法创建对象,即: A a = new A()。
  4. bean实例化后,通过addSingletonFactory对其进行曝光(注意这时bean创建流程还没有走完,只走了第一步)。
  5. 接下来走完bean的属性填充和初始化这两个阶段,bean的创建工作其实已经完成了。
  6. 走完创建流程后,关键的一步来了,Spring要检查早期曝光的Bean是否被其他Bean使用。通过getSingleton(beanName, false) Spring就知道earlySingletonObjects中有没有bean,如果有,则早期曝光的Bean肯定被使用了,这时候当前bean还没有创建完成,一个没有创建完成的bean被别的bean使用,那就存在循环依赖了(理解这一步,是理解Spring用三级缓存而不是用两级缓存的关键,Spring通过将早期Bean包装成ObjectFactory再暴露,而不是直接将bean直接暴露到earlySingletonObjects,从而能够感知到在当前bean创建过程中,有没有bean依赖当前bean)。这时候Spring就要检查经过属性填充和初始化这两个阶段后的bean,跟早期暴露出去的Bean还是不是同一个对象?要知道初始化阶段的BeanPostProcessor可能会对Bean进行包装,代理增强操作的。我们看看检查逻辑:
if (earlySingletonExposure) {
    /**
    *
        首先调用getSingleton(beanName, false)从缓存中拿bean实例,注意这里的false参数,
        表明只会从singletonObjects和earlySingletonObjects这两级缓存中拿。
        如果没拿到,很好,说明当前Bean的创建过程中没有别的Bean依赖当前Bean,也就不存在循环依赖的可能性。
    */
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
                
                /**
                *	拿到了,说明当前Bean创建的过程中,有别的Bean创建时依赖了当前Bean,并拿到了早期曝光的当前Bean。
                *	这种情况下需要检查当前initializeBean返回的bean和 getEarlyBeanReference返回的是不是同一个对象
                *   这里 bean 是实例化出来的bean
                *	earlySingletonReference 是早期被暴露出去的getEarlyBeanReference返回的bean
                *	exposedObject是initializeBean方法返回的bean
                *	比较exposedObject与bean是不是同一个对象就能知道bean有没有被initializeBean增强
                *	如果没有被增强,则设置exposedObject = earlySingletonReference earlySingletonReference是别的Bean进行属性注入时,拿到的当前bean对象
                *	exposedObject是要被放入Spring容器的,因此这两个对象要一致才行
                *	而earlySingletonReference有可能被getEarlyBeanReference方法增强
                */
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					/**
                    * 这里说明当前bean在初始化阶段被增强了(被initializeBean增强),依赖当前Bean的Bean拿到的对象跟马上要被放入到容器中的对象
                    *	不是同一个对象了
                    *  这里调用getDependentBeans拿到那些依赖当前Bean的Bean
                    *  检查这些Bean是不是 仅仅是为了类型检查而创建的,
                    *    如果全都是为了类型检查而创建的 这些bean不会被放入容器中,也就不造成不一致
                    *    如果有不是为了类型检查而创建的bean,则会造成不一致,要抛异常
                    */
                    String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
  1. 进行将bean的正在创建状态取消,实例处理,将bean加入单例缓冲池等等后续工作。

我们拿个例子来看一下Spring如何解决循环依赖的:

<bean id="lock" class="com.lspring.beans.Lock" >
      <property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" >
      <property name="lock" ref="lock"/>
</bean>
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();      
        String path = System.getProperty("app-bean-path");
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions(path);        
        Object lock = factory.getBean("lock");

创建Lock 创建Door 缓存状态
从缓存中获取lock-->失败getSingleton("lock",true) singletonObjects:无 earlySingletonObjects:无 singletonFactories:无
创建Bean实例createBeanInstance
将lock提早曝光addSingletonFactory singletonObjects:无 earlySingletonObjects:无singletonFactories:lock
开始填充bean属性 populateBean start
从缓存中获取doorgetSingleton("door",true)
创建Bean实例createBeanInstance
将door提早曝光addSingletonFactory singletonObjects:无earlySingletonObjects:无singletonFactories:lock,door
填充door的属性开始populateBean start
从缓存中获取lock-->成功getSingleton("lock",true) singletonObjects:无earlySingletonObjects:lock singletonFactories:door
填充door的属性结束populateBean end
初始化doorinitializeBean
检查早期曝光Bean(door)是否被使用(未使用)check earlySingletonExposure
将Bean加入到单例池中 addSingleton singletonObjects:door earlySingletonObjects:lock singletonFactories:无
填充bean属性 结束populateBean end
初始化Bean initializeBean
检查早期曝光Bean(lock)是否被使用(被使用,检查lock是否被增强(未被增强),检验合格)check earlySingletonExposure
将Bean加入到单例池中addSingleton singletonObjects:door,lockearlySingletonObjects:无singletonFactories:无

读完本文,请尝试回答以下问题:

  1. Spring如何检测循环依赖?
  2. 对于属性注入的循环依赖,明明也是循环依赖的一种,为什么不会抛出BeanCurrentlyInCreationException?
  3. Spring如何解决属性注入方式的循环依赖?bean是在创建的哪个阶段被提早曝光的?
  4. Spring为什么使用三级缓存来解决循环依赖?二级缓存不行吗?你能想到其他访问来解决循环依赖吗?
    这里关键要考虑清楚 earlySingletonObjects和singletonFactories的作用。
    singletonFactories存放的是getEarlyBeanReference方法,如果有别的bean循环依赖了当前bean,该方法才会被执行,getEarlyBeanReference的作用就是将动态代理的创建从initializeBean阶段提前到populateBean之前,
    以保证即使是当前bean进行了增强,那么依赖当前bean的bean也能拿到正确的代理实例。
    earlySingletonObjects存放的是singletonFactories执行的结果。为什么要设置这个?为什么不直接放到singletonObjects?earlySingletonObjects是Spring检测当前bean是否被循环依赖的关键。
    注意Spring在doCreateBean方法的最后阶段,也就是bean的整个创建流程执行完之后,通过getSingleton(beanName, false)又获取一次当前bean,该方法只能从singletonObjects和earlySingletonObjects中拿bean,如果该方法返回null,说明 singletonFactories中的方法没有被执行,也就是说当前bean肯定不存在循环依赖。如果没有返回null,spring即感知到当前bean的创建过程中有循环依赖。
  5. 请自己画出prototype bean的加载流程,并解释为什么prototype bean的循环依赖不能解决。

以下文章对本文帮助很大:

1. Spring Bean的依赖关系
2. Spring循环引用的处理

posted @ 2023-01-31 15:34  小张同学哈  阅读(101)  评论(0)    收藏  举报