再谈Spring的循环引用

1. 前言

我之前有一篇文章,也是谈的Spring的循环引用(文章链接)。文章中有我翻看源码的见解,其实那篇文章只是说了循环引用的一部分,并没有阐述“为什么需要三个级别的缓存才可以”,如果按照我上一个文章来看,貌似用两级缓存就足以解决了循环引用的问题。但是我上一个文章,只是说了普通的情况,也就是说,普通的循环引用的情况。普通的循环引用的话,直接使用两个级别的缓存的确可以解决。那么什么情况下,两个的缓存不能解决呢?

其实这个文章好久之前都写好了。只是时间问题,没有分享出来。
这里分享出来我的见解,欢迎指正。

2. 两个级别的缓存带来的问题

上面说了,两个级别的缓存,的确可以解决大部分的循环引用的问题。那么下面来阐述一下两个级别的缓存不能解决的问题。那就是经过代理之后的bean的问题。那么这个问题的出现,就导致两个级别的缓存无法解决此问题了。我们都知道,Spring Aop 使用的就是动态代理,那么生成的代理对象,必定是一个新的对象。那么只是文字论述是比较难理解的,下面使用图的方式,解释一下的观点。

注意,下面的图,只是简略的、可以说明情况的简略图。


说明的情况如图:

下图的情况需要说明,并不是直接去二级缓存找,而是先从一级缓存找的,为了简洁,我并没有表达出来

在这里插入图片描述

那么我们发现,使用两个级别的缓存的话,是无法解决 具有动态代理的情况下 bean的循环引用的问题的。

3. 三个级别的缓存是如何将问题解决的?

上面也阐述了使用二级缓存有问题的原因。下面就来看看Spring是如何使用三级缓存来解决这个问题的。当然,使用硬生生的文字,我也是无法阐述这种情况,下面依旧是使用图的方式来阐述我的观点。

在这里插入图片描述

上面的图片,已经阐述了我的观点,由于我不细心,可能图中出现的纰漏,请指出。

那么从上面的图解可以看出,的确,使用三级缓存就可以解决了。

4. 源码证实

从上面的图也阐述了我的观点,因为你也不知道我是不是乱说的?

所以下面就查看spring源码来证实我的观点是否正确。

首先从缓存中获取BeanA,肯定没有获取到

在这里插入图片描述

省略中间步骤。。。。。。

下面来到了创建beanA的过程,因为我们并没有获取到

在这里插入图片描述

进入doGetBean()方法之后。

在这里插入图片描述

放入三级缓存

在这里插入图片描述

属性填充

在这里插入图片描述


转到BeanB的创建过程

在这里插入图片描述

创建BeanB
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现啥都没干,返回了,也就是说,这个对象并没有实现这个方法。那么到底哪个bp实现了这个方法,用这个方法干了一些事情了呢?

我们一探究竟

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们接下来回来。

在这里插入图片描述

接下来在返回。

在这里插入图片描述

接下来,beanA返回,BeanB成功初始化,成功放入一级缓存,

那么BeanB初始化成功,那么就又回到了BeanA的创建过程。

下面我们再次执行到BeanA填充完成属性后面的过程。

下面到了beanA的初始化,进入此方法

在这里插入图片描述

下面执行到初始化bean的时候,执行BeanPostProcessor的after方法

在这里插入图片描述

选则自动代理的bp进入

在这里插入图片描述

下面可以看到,并不执行,直接返回,因为我们在从三级缓存中放入二级缓存的时候,已经执行过了(当然,只有配置了,aop,从三级缓存中获取的时候,这里才可能执行过,刚才源码已经展示了)。

在这里插入图片描述

在这里插入图片描述

5. 从源码中得出的问题(个人突发奇想)

那既然Spring针对aop相关的beanPostProcessor 他做了特殊处理,解决了循环依赖,中bean的依赖不一致的问题。

那么我们也定义一个BeanPostProcessor,我也针对BeanA做动态代理,我也返回代理对象,这样Spring肯定没做特殊处理,那么Spring将会怎么做?

这是一个非常有意思的问题。


环境准备:

依旧是老环境。BeanA && BeanB 两个bean,都是互相使用 注解注入对方。这个不在截图

那么我准备了一个BeanPostProcessor,只针对BeanA 做代理,我的BeanPostProcessor 如下:
在这里插入图片描述

我们运行环境,看看Spring会做出何种反应!!

在这里插入图片描述

没毛病!

那么我们来分析一下报错信息:

Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans [beanB] 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.

啥意思?

翻译一下:

创建 一个 名字为:“beanA”的bean的时候,出错了!为啥呢?

因为beanA 在还是原始对象的时候啊被beanB引用了。

但是呢,后来beanA又被包装了。

这就意味着,我beanB中引用的beanA 并不是最终的版本

最终会导致,beanB依赖的BeanA

与 一级缓存中的beanA 不是一个Bean 了, 完蛋了。

5.2 出错源码分析

现在我们定位到源码,源码的位置如图:

在这里插入图片描述

从上图往下,我把我代码贴出来,不在使用截图:大家看代码即可明白

	
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {
    
	// 上面的方法省略...............................................
    
    
    
	/**
	 * Actually create the specified bean. Pre-creation processing has already happened
	 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
	 * <p>Differentiates between default bean instantiation, use of a
	 * factory method, and autowiring a constructor.
	 * @param beanName the name of the bean
	 * @param mbd the merged bean definition for the bean
	 * @param args explicit arguments to use for constructor or factory method invocation
	 * @return a new instance of the bean
	 * @throws BeanCreationException if the bean could not be created
	 * @see #instantiateBean
	 * @see #instantiateUsingFactoryMethod
	 * @see #autowireConstructor
	 */
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
        
        // 上面的代码省略..........................
        
        
        
        
		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			// bean 的依赖填充
			populateBean(beanName, mbd, instanceWrapper);
			// 初始化bean,里面会执行各种aware, beanPostProcessor的前置方法,initMethod方法,beanPostProcessor的后置方法等。
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		// 判断是否早期引用
		if (earlySingletonExposure) {
			// 可以看到,调用了又调用了getSingleton,但是allowEarlyReference 参数是false,
			// 说明他不会去三级缓存中寻找
			// 就是去一级二级中找
			Object earlySingletonReference = getSingleton(beanName, false);
			// 如果从一级二级找到了。
			if (earlySingletonReference != null) {

				// 当然这一句是点睛之笔,为什么这么说呢?
				// 首先看 exposedObject 是如何获得的?
				// 从上面几行可以看到原因:
				// 是调用 initializeBean(beanName, exposedObject, mbd); 方法的来的
				// 我们都知道 initializeBean 里面 对bean 执行了 BeanPostProcessor,
				// 那么 BeanPostProcessor 都知道,我们可以在里面对bean 进行各种操作,
				// 甚至 可以做动态代理,再次返回一个不同的bean
				// 那么下面的 判断就有意思了,
				// 这句话的意思就是:
				// exposeObject: 初始化之后的bean
				// bean: 初始化之前的bean
				// 如果他两个相等,那么我们正常引用二级缓存就行
				// 那么如果是false,那么显然就说明了一个问题:
				// 什么问题呢?
				// 这两个bean 不相等的原因就是 你定义了一个BeanPostProcessor,  在里面你对bean 进行了更改,返回了一个新的bean
				// (地址都不同的哪种)
				// 那么这个判断就false ,
				// 就会走下面的 else if
				if (exposedObject == bean) {
					// 可以看到重新赋值了。(一般循环引用中没有aop代理的话,这两个对象是一样的。)
					exposedObject = earlySingletonReference;
				}

				// 首先allowRawInjectionDespiteWrapping未false,!首先allowRawInjectionDespiteWrapping为true
				// hasDependentBean(beanName) 可以  根据 beanName 判断 这个 bean 是否 依赖了其他的bean
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// 根据beanName 获取其依赖的bean 的beanName
					// 举个例子:比如我的service 注入了一个dao,
					// 那么比如现在的beanName 是service
					// 那么getDependentBeans(beanName) 就会获得 dao的bean的beanName
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					// 遍历此bean 依赖的beanName
					for (String dependentBean : dependentBeans) {
						//删除给定bean名的单例实例(如果有的话),但只有当它没有被用于类型检查之外的其他目的时才可以。
						// 可以理解为:将此bean依赖的bean,从三个级别中的缓存中删除(注意,如果父容器中也有,则也给他删了。)
						// 里面的代码:
						//			this.singletonObjects.remove(beanName);
						//			this.singletonFactories.remove(beanName);
						//			this.earlySingletonObjects.remove(beanName);
						//			this.registeredSingletons.remove(beanName);
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							// 删除玩了,加入到 这个变量中
							actualDependentBeans.add(dependentBean);
						}
					}

					// 如果删除的不是0个,说明删除过,直接给你抛异常。
					// 非常然后导致,涉嫌的这几个bean 全都从ioc容器中删除了。
					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.");
					}
				}
			}
		}
        
        // 下面的代码省略................................
    }
	// 下面的方法省略..............................
}

5.3 今后需要注意的问题

从上面的报错可知,我们以后在 有循环依赖的情况下,千万不要使用BeanPostProcessor 返回不一样的bean。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值