Spring 为什么要用三级缓存来解决循环依赖(AOP),二级缓存不行吗

这篇具有很好参考价值的文章主要介绍了Spring 为什么要用三级缓存来解决循环依赖(AOP),二级缓存不行吗。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

结论

解决有代理对象的循环依赖不一定要三级缓存,用二级甚至一级也能解决,下面讨论下Spring为什么选择三级缓存这个方案。

Spring最开始是没有三级缓存的,后面版本因为引入了AOP,有了代理对象,又因为存在循环依赖,为了保证依赖注入过程注入的是代理对象,且不完全打破Spring的设计原则(代理等这些后置处理器应当在初始化阶段完成),Spring选择稍微打破限制,引入三级缓存,提前对循环依赖的bean在依赖注入的时候就生成代理对象。

解释

Spring 现在的三级缓存如下:

	/** 第一级缓存,存放可用的成品Bean。 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** 第三级缓存,存的是Bean工厂对象 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** 第二级缓存,存放半成品的Bean,半成品的Bean是已创建了对象,但是未注入属性和进行初始化*/
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Spring目前是通过二三级缓存配合,进行循环依赖的解决。

首先,我们要明确Spring遵守的单例bean的创建流程,bean 先实例化,再属性赋值,依赖注入,再初始化。。。。这个基本过程。我们所说的代理对象其实就是初始化阶段,BeanPostProcessor后置处理器完成代理对象的。

网上有很多地方说三级缓存是为了解决代理对象,这个说法并没有说到根本。

如果我们单纯为了解决有代理的循环依赖,其实解决循环依赖用二级缓存甚至一级缓存就可以了,之所以用了三级缓存是Spring开发者的一种取舍造成的。
对于如何解决带有AOP的循环依赖,有如下两种解决方案:

1、无论这个bean有没有循环依赖,在依赖注入之前,就创建好这个bean的代理对象放入缓存,出现依赖注入的时候,直接从这个缓存拿取代理对象即可。

2、不提前创建代理对象,当只有出现循环依赖的时候,才实时地创建代理对象。

Spring 因为为了不完全违背bean的创建流程的定义(代理应当在属性赋值后的初始化过程中生成代理对象),只能勉为其难的提前进行。所以选择了上述的第二种方案。

接下来,说一下为啥选择第二种方案要用三级缓存。
spring 为了优雅,缓存尽量存储的是单一性质的元素,所以必须有第一级缓存,用来存放可用的成品Bean。

 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

接下来,假如A 和 B 循环依赖,A 和 C也循环依赖,所以当创建A的bean的时候,避免B和C 拿到不同的代理对象,因此我们需要第二个缓存来存储B拿到的A的代理对象,当C去A代理对象的时候,就可以直接从第二个缓存中拿取了。

为了实现只有出现循环依赖的时候才实时地创建代理对象这个过程,Spring 又引入了第三个缓存,第三个缓存的作用是当A在实例化的时候,就把自己放入第三个缓存,代码如下:

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

,表示A正在创建当中,其中() -> getEarlyBeanReference(beanName, mbd, bean))的函数式接口就是用来实现创建代理对象的,

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

当B需要注入A的时候就会执行如下步骤:

@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

先去第一级缓存拿,没有就去第二级缓存拿,二级没有的话,就去第三级缓存看看,当在第三级缓存发现有A的时候,说明此时A正在创建中,且未被其他bean引用,此时就会从三级缓存中取出beanFactory,beanFactory再执行getObject方法,getObjetc方法就前面的

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

其中SmartInstantiationAwareBeanPostProcessor 是spring内部对于BeanPostProcessor的实现,大家可以自己点去看看,SmartInstantiationAwareBeanPostProcessor 的getEarlyBeanReference就是创建代理对象,并标记自己已经创建了代理对象(earlyProxyReferences)。

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

此时B相当于从三级缓存中拿到了A的代理对象,B为了后面的C和自己拿到的是同一个A的代理对象,他就需要把这个A代理对象放入第二级缓存。同时移除第三级缓存的A,表示A已经提前创建好了代理对象,不需要再从三级缓存里面获取新代理对象了。

接下来,B的创建好后,A继续注入C,C直接从第二级拿到已经创建好了的A的代理对象。A在后面的初始化阶段执行applyBeanPostProcessorsAfterInitialization

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
                //判断是否提前创建了代理对象
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

的时候,会判断之前是否提前创建了代理对象,这样就解决了带有AOP的循环依赖。

当然,如果A没有循环依赖,那么就不会被其他bean从第三级缓存中取出来执行getEarlyBeanReference方法,这样A的AOP自然就留在了初始化阶段完成了,这样也就遵守了Spring定义的bean的创建过程。

继续解释

继续解释下为什么我说只用二级或者一级缓存也能解决带有AOP的循环依赖问题。

假如不遵守Spring强烈要求bean的创建过程,我们可以直接在依赖注入前,就往第二级缓存存入A的代理对象(如果没有代理就直接存原始对象),这样B和C直接就可以从第二级缓存拿到A的代理对象,这样两个缓存就能解决了,但是这样做就是提前把代理对象都创建好了。

如果我们更过分点,不遵守每一级缓存存入的是同一过程性质的bean,那么我们只需一级缓存,每个bean提前创建好代理对象就放入一级缓存,(此时一级缓存的bean还是未初始化的bean),接下来B直接从一级拿到A的代理对象,完成创建,B把自己完整的Bean也放入一级缓存,此时一级缓存的bean 就有中间态和完成态两种形态的bean, 最终A完成创建,一级缓存全是完成态Bean。这样做,只用一级缓存就能完成所有的过程,只是不优雅~~。文章来源地址https://www.toymoban.com/news/detail-858957.html

到了这里,关于Spring 为什么要用三级缓存来解决循环依赖(AOP),二级缓存不行吗的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [Spring] 三级缓存解决循环依赖详解

    注册一个bean对象的过程: Spring扫描class得到BeanDefinition – 根据得到的BeanDefinition去生成bean – 现根据class推断构造方法 – 根据推断出来的构造方法,反射,得到一个对象 – 填充初始对象中的属性(依赖注入) – 如果原始对象种的某个方法被AOP了,那么要根据原始对象生成一

    2024年02月15日
    浏览(40)
  • spring解决循环依赖的三级缓存

    实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法,简单理解就是new了一个对象。 属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法,为实例化中new出来的对象填充属性和注入依赖。 初始化,对应方法:AbstractAutowireCapableBeanFactory的initialize

    2024年02月03日
    浏览(40)
  • Spring使用三级缓存解决循环依赖?终于完全弄明白了

    文章阅读前推荐 推荐先去看看源码,源码很短,但是对于我们在脑子里构建一个完整思路很重要。看起来非常简单,只需要双击shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到550行左右的doCreateBean方法,重点看一下580行到600行这20行代码就行,包含了三级缓存、属性注

    2024年03月25日
    浏览(58)
  • 为什么要用redis

    就是把你一些复杂操作耗时查出来的结果(用了600ms),如果确定后面不咋变了,然后但是马上还有很多读请求,那么直接结果放缓存(6ms),后面直接读缓存就好了。 这样,性能就提升了100倍 说白了就是,用redis挡访问,高并发的访问,不让mysql挂了。 mysql这么重的数据库,压根

    2024年02月12日
    浏览(37)
  • 为什么要用开源容器?

    说到开源容器,大家首先想起来的应该是Docker吧,那么我们就以Docker来从个人角度理解一下为什么要用开源容器。 通常都会说Docker开源容器,但是Docker 实际上是一个开源的应用容器引擎。Docker是一个基于轻量级虚拟化技术的容器,整个项目基于Go语言开发,并采用了Apache 2

    2024年04月16日
    浏览(58)
  • 为什么要用B+树

    B+树的优势 支持范围查询:B+树在进行范围查询时,只需要从根节点一直遍历到叶子节点,因为数据都存储在叶子节点上,而且叶子节点之间有指针连接,可以很方便的进行范围查询 支持排序:B+树的叶子节点按照顺序存储,可以快速支持排序操作,提供排序效率 存储

    2024年01月20日
    浏览(47)
  • 为什么要用线程池?

    线程池是一种管理和复用线程资源的机制,它由一个线程池管理器和一组工作线程组成。线程池管理器负责创建和销毁线程池,以及管理线程池中的工作线程。工作线程则负责执行具体的任务。 线程池的主要作用是管理和复用线程资源,避免了线程的频繁创建和销毁所带来的

    2024年02月06日
    浏览(63)
  • Spring FrameWork从入门到NB -三级缓存解决循环依赖内幕 (一)

    循环依赖就是我依赖你、你依赖我,或者A依赖B、B依赖C、C依赖A…组成的错综复杂的依赖关系。 其实各种不同的依赖关系最终从逻辑上都可以演变为:我依赖你、你依赖我。 循环依赖大致可以分为两种情况: 属性依赖:比如A对象有一个属性B,B对象有属性A。 构造器依赖:

    2024年02月11日
    浏览(46)
  • 为什么要用虚拟 DOM?

    虚拟DOM(Virtual DOM)是一种将应用程序的状态(state)与DOM分离的技术。它是一个JavaScript对象,它的结构类似于实际DOM元素的结构。使用虚拟DOM的目的是在减少DOM操作的数量的同时,提高应用程序的性能和响应速度。 当应用程序的状态发生变化时,使用虚拟DOM可以计算出需要

    2024年02月01日
    浏览(44)
  • 低代码是什么意思?企业为什么要用低代码平台?

    低代码是什么意思?企业为什么要用低代码平台? 这两个问题似乎困扰了很多人,总有粉丝跟小简抱怨, 一天到晚念叨低代码,倒是来个人解释清楚啊! 来了,这次一文让你全明白。 在此之前,先了解什么是云计算。 “云” :指的就是互联网,因为之前互联网(Internet)

    2024年02月07日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包