spring之我见- spring循环依赖解决方案

单例在spring里的获取方式

今天讲一下spring中针对单例bean的循环依赖问题,本着追本溯源的学习理念,我们要先知道单例在spring中怎么管理的。spring获取实例都通过beanfactory的getBean方法获取实例,顺着代码而下,在doGetBean方法(AbstractBeanFactory)中,单例总是通过getSingleton()方法获取缓存实例。

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

......
		Object sharedInstance = getSingleton(beanName);
......
}

再来看看getSingleton方法,也非常简单,spring给了三个map,网上说是三级缓存

  • 一级缓存 : singletonObjects 初始化完后的单例都会存在这个map中,一般ioc容器初始完后,你再用getbean取实例都可以从这里获取到。
  • 二级缓存 :earlySingletonObjects 提前曝光的实例,这时候bean还处于创建中(bean的创建分为 实例化 和 初始化),没有完全创建好。
  • 三级缓存 :singletonFactories 跟二级缓存一样,bean还处于萌芽期,甚至取到的只是一个工厂类,需要通过getObject获取实例。

这段代码的含义就是 先从一级缓存中获取实例,取不到,再去取二级缓存,再没有,那就三级中取。 注意: 当从三级中取到实例时,会删除三级缓存中的值,然后放入二级缓存

有人问设计多级缓存的含义在哪? 这其实是为了 本文的中心思想:spring循环依赖解决方案 服务的。后面再来细讲。

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 != NULL_OBJECT ? singletonObject : null);
	}

循环依赖的问题

上一章我们讲了单例如何从三个缓存中去取,可能为什么要设计这个缓存我们听的云里雾里,这里我们就抛出问题:循环依赖。

我们知道,当A 有一个 属性 B , B 有一个属性A时,就形成了循环依赖,按照单例bean的创建逻辑,A没创建完,就去拿B,B还没创建完,又去拿A,请问A在哪?A还没创建完呢 ! 所以解决问题的关键在哪?spring给设计了一个多级缓存,当A完成实例化(createBeanInstance)时,这个实例已经有了自己的内存地址,只是对象不够完善,spring先把这种对象放进一个缓存。然后装配属性的时候需要B时 ,B开始自己的创建之旅,途中B需要A了,它就可以从缓存中顺利拿到实例A,即使这时候的A并不完善。 然后B把自己创建完成后,又轮到A继续完善自己,直到 A和B 都创建完毕,这样就没有了循环依赖的问题。

下面用图反映spring bean的大致创建流程 与 多级缓存之间的配合:

  • 当实例化完一个初始bean时,会先放入三级缓存,记住,这里的A还只是一个 初生的婴儿,没有属性装配,没有经历初始化。
  • 然后经历装配bean属性的过程。期间属性涉及到 循环依赖的时候,就可以通过第三缓存拿到对象。拿到后删除三级缓存,放入二级缓存。
  • 实例创建完毕后会删除二,三级缓存,放入一级缓存。一级缓存就是 spring单例对象的完全体,后面程序可以通过beanfactory 随时取用。
    spring之我见- spring循环依赖解决方案_第1张图片

为什么要设计三层缓存

在了解了循环依赖的基本原理后,我当时第一个疑问就是:为什么要设计三层缓存?而不是两层,理论上,设计两层就能满足需求。

我们来反过来看看最开始根据getSingleton获取单实例的方法,有一个参数allowEarlyReference,这个为true,就说明你可以从 三级缓存 (singletonFactories) 来获取提前曝光的实例,如果为false,你最多只能从二级缓存(earlySingletonObjects)来获取实例。那哪里 会用false来调用getSingleton方法呢?

	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 != NULL_OBJECT ? singletonObject : null);
	}

我们从doCreateBean方法中看到如下代码,这段代码在装配完bean后(populateBean)开始执行,会看到其中调用了getSingleton(beanName, false),这里其实是 解决循环依赖中的二次检查,我们举个例子:A和B相互关联,当A初始化的时候,发现需要B,然后A作为提前曝光的bean放入三级缓存。然后创建B,B发现需要A,从三级缓存(singletonFactories)中顺利取出,放入二级缓存(earlySingletonObjects),然后继续创建B,B创建完毕,继续初始化A,这时, A因为被BeanPostProcessors处理过,导致返回了一个完全不一样的对象,这时进入下面的代码进行二次检查,getSingleton因为allowEarlyReference参数为false 的缘故,只能最多从二级缓存中取数据,发现还真找到了,从二级缓存中取出也说明了 对象A存在被其它对象依赖的情况,当然也包括循环依赖的情形,对于循环依赖来说,对象的创建过程中不允许被二次修改,即使是BeanPostProcessors处理过也不能返回内存地址不一样的对象,想想这样也是对的,这样才能保证全程的对象是一致的。 这也说明了二级缓存的作用--如果你能从二级缓存中拿到对象,就说明它是被其它对象依赖的,在经过BeanPostProcessors 等方法的处理后,也要二次检查来保证对象的一致性。

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<String>(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.");
					}
				}
			}
		}

你可能感兴趣的:(Spring之我见)