Spring循环依赖

一、什么是循环依赖

循环依赖就是循环引用,即两个或者多个bean相互之前的持有对方,如下图:

A引用B,B引用C,C又引用A,最终反应为一个环。

注意:此处不是循环调用,循环调用是方法之间的环调用,而且循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

二、Spring解决循环依赖的方式

Spring容器循环依赖的场景:

  • 构造器循环依赖
  • setter循环依赖

        Spring的循环依赖的理论依据其实是基于Java的引用传递,当获取到对象的引用时,对象的属性是可以延后设置的(但是构造器必须是在获取引用之前)。Spring的单例对象的初始化主要分为三步: 

  • createBeanInstance:实例化,可以说就是调用对象的构造方法实例化对象
  • populateBean:属性填充,这一步主要是多bean的依赖属性进行填充
  • initializeBean:调用spring xml中的init 方法。

根据上图的单例bean初始化步骤可以知道,循环依赖主要发生在第一、第二步。即构造器循环依赖和setter循环依赖,下面我们分别对其进行说明:

1、构造器循环依赖

      表示通过构造器注入构成的循环依赖,此依赖是无法解决的。
      Spring容器将每一个正在创建的bean标识符放在一个 “当前创建bean池” 中,bean标识符在创建的过程中将一直保持在这个池中,因此如果在创建bean的过程中发现已经在 “当前创建bean池” 时,将抛出BeanCurrentlyCreationException异常标示循环依赖;尔对于创建完毕的bean将从 “当前创建bean池” 中清除掉。

2、setter循环依赖

       表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴漏刚完成构造器注入但是未完成其他步骤(例如setter注入)的bean来完成的,并且只能解决单例作用域的bean的循环依赖。

       对于 “prototype” 作用域的bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存 “prototype” 作用域的bean,因此无法提前暴漏一个创建中的bean。

通过提前暴漏一个单例工厂方法,从而使其他bean能够引用到该bean,如下代码:

AbstractAutowireCapableBeanFactory.java

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

		........

		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		........

	}

Spring为了解决单例的循环依赖问题,使用了三级缓存

singletonFactories : 单例对象工厂的cache 
earlySingletonObjects :提前暴光的单例对象的Cache 
singletonObjects:单例对象的cache

	/** 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);

在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

/**
	 * 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
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//从单例缓存中家在bean
		Object singletonObject = this.singletonObjects.get(beanName);
		//单例缓存中bean为空且当前bean正在创建中
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			//加锁
			synchronized (this.singletonObjects) {
				//从earlySingletonObjects获取
				singletonObject = this.earlySingletonObjects.get(beanName);
				//earlySingletonObjects中没有提前创建,且允许提前创建
				if (singletonObject == null && allowEarlyReference) {
					//从singletonFactories中获取对应的ObjectFactory
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						//获取bean
						singletonObject = singletonFactory.getObject();
						//添加到earlySingletonObjects中
						this.earlySingletonObjects.put(beanName, singletonObject);
						//从singletonFactories中移除
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
//添加到earlySingletonObjects中
this.earlySingletonObjects.put(beanName, singletonObject);
//从singletonFactories中移除
this.singletonFactories.remove(beanName);

上面的这个操作相当于将bean从三级缓存移入到二级缓存,并删除三级缓存

从上面三级缓存的分析,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。并且cache的类型是ObjectFactory,定义如下:

@FunctionalInterface
public interface ObjectFactory<T> {

	/**
	 * Return an instance (possibly shared or independent)
	 * of the object managed by this factory.
	 * @return the resulting instance
	 * @throws BeansException in case of creation errors
	 */
	T getObject() throws BeansException;

}

这个在下面被调用:

	/**
	 * 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);
			}
		}
	}

       上面就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,即单例对象此时已经被创建出来(调用了空参构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二、三步),但是已经能根据对象引用能定位到堆中的对象,所以Spring此时将这个对象提前曝光出来让其他bean使用。

    通过上面的这个原理分析,我们就可以知道Spring不能解决构造器循环依赖问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

知道了原理,我们在这里,在来说一下解决上面A、B、C三个bean的setter循环依赖的步骤:

  1. Spring容器创建单例 “A” bean,首先会通过A的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “A” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “B”。
  2. Spring容器创建单例 “B” bean,首先会通过B的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “B” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “C”。
  3. Spring容器创建单例 “C” bean,首先会通过C的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “C” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “A”。进行注入 “A” 的时候由于 “A” 提前暴露了 “ObjectFactory” 工厂,从而使它返回提前暴露一个创建中的bean。
  4. 最后在依赖注入“B” 和 “C” ,完成setter的注入。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值