spring aop 循环依赖和aop 为什么要三级缓存 尽量大白话的解释

前提:使用setter注入,对docreate流程有基本了解。即实例化(createBeanInstance)注入(populateBean)初始化(initializeBean)

首先第一级缓存,单例对象singletonObjects

一级缓存作用就是存储所有创建好了的单例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);
		}
	}

然后是第三级缓存,工厂对象singletonFactories

这里有两个点,第一个是缓存本身,第二个是为什么使用工厂。假设有AB两个单例循环依赖,先创建A。

第一 为什么使用缓存

首先我们肯定需要某种缓存来临时存放A的早期对象(即还未依赖B未注入属性时的对象),才能解决循环依赖问题,那么很容易想出来如下流程(不是实际流程):

  1. 创建A,实例化A
  2. 将实例化但还没注入属性的早期A放入缓存(addSingletonFactory)
  3. 注入A的属性,发现A依赖B
  4. 创建B,实例化B,注入B的属性,发现B依赖A
  5. 尝试获取A,将缓存中的早期A注入B中,B创建完成返回给A
  6. A注入B,A创建完成。
		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// (bean是单例 && 是否允许循环依赖 && bean是否在正在创建的列表中)
		boolean earlySingletonExposure = (
		   mbd.isSingleton() 
		&& this.allowCircularReferences 
		&& isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

可以看到只需要一个缓存就可以解决循环依赖问题,也即addSingletonFactory方法的一部分。并且在第2步,由于还没有接触注入流程,A自身并不知道是否参与了循环依赖,所以为了解决循环依赖,A一定要把加入缓存的步骤固定到流程里去,也即addSingletonFactory方法是固定在docreate方法中(earlySingletonExposure 正常为true)

第二 为什么使用工厂

此处有一个前提:spring开发人员希望我们遵循一个固定流程,即bean在正常实例化,注入之后,最后一步再通过applyBeanPostProcessorsAfterInitialization实现aop,可能这是某种标准化的体现。
现在如果AB循环依赖,且A实现了AOP代理,那么我们注入B的时候就需要注入A的代理类而非A本身。那么就会产生这种情况:A在实例化时不知道有没有循环依赖,所以为了保证解决问题,就需要在放入缓存(addSingletonFactory)时就生成代理对象,以供可能出现的B使用,那么结合前面提到的前提。这样做不符合开发人员的想法,他们是希望在出现循环依赖的情况下,由于B需要注入A的代理类,所以才不得不将aop的操作提前到addSingletonFactory处,而不是只要aop就提前。为了实现这个目的引入了工厂,也即addSingletonFactory实际上不是放入的早期A的bean实例,而是它对应的工厂!此时B在第5步时,通过工厂来获取A,默认是获取普通的早期A,而如果实现了AOP,则通过相应的类来创建早期A的代理类并获取,即通过工厂延迟了早期A的获取,从而实现了仅在出现循环依赖的情况下,才提前AOP操作。

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		//有aop相关的玩意儿就走aop的,具体在getEarlyBeanReference由相应类实现
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		//默认直接返回
		return exposedObject;
	}

最后是第二级缓存,早期对象earlySingletonObjects

可以看到实际上一三级缓存就已经解决了aop+循环依赖的问题,但是还会带来一些别的问题。
首先前面的流程是简化的,实际上获取bean的方法是AbstractBeanFactory里的doGetBean,其第一步为getSingleton,该方法为检查获取缓存区的bean,一二三级按顺序来,其中boolean参数决定是否检查第三级缓存,如果有就直接返回,没有才走创建流程。部分如下

//先查1级
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
    //查2级
	singletonObject = this.earlySingletonObjects.get(beanName);
	if (singletonObject == null) {
	    //查3级
		ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
			if (singletonFactory != null) {
			    //通过三级工厂的getEarlyBeanReference拿到早期对象
				singletonObject = singletonFactory.getObject();
				//放2级里
				this.earlySingletonObjects.put(beanName, singletonObject);
				//三级工厂删了
				this.singletonFactories.remove(beanName);
		}
	}
}	
第一 性能问题

试想如果不止AB循环依赖,还有CD也依赖A。那么CD创建时同样需要通过工厂走一遍getEarlyBeanReference方法,如果缓存到第二级,那么就可以二级时直接获取到,不用跑一遍方法。

第二 一致性问题

所有对象创建时,都会产生三级缓存,而在bean A创建过程中,如果三级缓存没了变到2级里去了。就可以说明bean A在创建过程中一定被别人调用过了(如上图),起到一个标识的作用。

// Initialize the bean instance.
Object exposedObject = bean;
try {
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
******
if (earlySingletonExposure) {
	//获取早期bean,false代表不看第三级缓存
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		//获取到了,检查跟bean相不相同
		if (exposedObject == bean) {
		//相同就把早期bean当标准输出返回了
			exposedObject = earlySingletonReference;
		}
		//不相同就把不相同的地方检查了组成字符串报出来
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			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) + "字符串"
			}
		}
	}
}

看上述代码,首先第一步把exposedObject设为bean(最初的A),然后initializeBean方法会处理exposedObject,完事后面再重新getSingleton获取自己的早期对象earlySingletonReference(bcd拿到的自己),如果获取到了,一定是二级拿到的,说明自己已经被调用过了,那么检查早期的自己和最终走完流程准备出场的exposedObject是不是相同,不相同就抛出报错。
这里有一个问题,就是为什么非要一致,也即BCD注入的早期A为什么要和被initializeBean过的exposedObject最终A一致,这个我懒得研究了,只能说想想肯定得一致吧?但是实际上可以通过配置关闭这个检查,也即上面代码判断中的allowRawInjectionDespiteWrapping 让它们可以不一致,跳过判断。
还有第二个问题,为什么initializeBean处理后会不一致,这个跟initializeBean中的applyBeanPostProcessorsAfterInitialization方法有关,实际上并不是处理exposedObject类的引用这么简单,有可能返回来的类不再是之前的那个类了,具体需要了解aop相关的源码。

最后总结来说,使用三级缓存有许多目的,三级跟二级最大的区别是一致性问题和性能问题

参考文章:

面试必杀技,讲一讲Spring中的循环依赖
【超级干货】为什么spring一定要弄个三级缓存?
spring 三级缓存的意义是什么,二级缓存也能解决含aop在内的循环依赖问题?

在第三个文章下可以看到有人说,2 3两个文章应该打一架,看懂了会发现这两人说的其实是一码事,第一篇文章的图有助于理解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring 循环依赖通常出现在两个或多个 Bean 之间相互依赖的情况下。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,这就形成了循环依赖Spring 通过三级缓存的方式来解决循环依赖的问题。 三级缓存Spring 中用于存储单例对象的一个数据结构,它包括了三个 Map,分别是 singletonObjects、earlySingletonObjects 和 singletonFactories。其中,singletonObjects 存储已经完全创建好的单例对象,earlySingletonObjects 存储被提前暴露出来但是还没有完全创建好的单例对象,singletonFactories 存储用于创建单例对象的工厂方法。 在创建 Bean 的过程中,Spring 首先通过 singletonObjects 缓存查找已经创建好的单例对象,如果找到了就直接返回,否则继续往下执行。如果 Bean 正在创建中,就从 earlySingletonObjects 缓存中获取未完全创建好的 Bean 实例。如果还没有找到,就从 singletonFactories 缓存中获取用于创建 Bean 实例的工厂方法。 在 AOP 中,Spring 会使用代理对象来包装原始对象,从而实现切面的功能。如果一个 Bean 既被代理了又依赖于其他 Bean,就会出现循环依赖的问题。为了解决这个问题,Spring 引入了 AOP 提前暴露代理对象的功能,即通过将代理对象放入 earlySingletonObjects 缓存中,使得被代理的 Bean 可以提前获取代理对象而不是原始对象。 总之,Spring三级缓存AOP 提前暴露代理对象功能都是为了解决循环依赖问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值