再谈Spring的循环引用

1. 前言

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

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

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

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

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


说明的情况如图:

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

在这里插入图片描述

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

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

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

扫描二维码关注公众号,回复: 12656707 查看本文章

在这里插入图片描述

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

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

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 出错源码分析

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GqjqMFiM-1614645246391)(image\image-20210301141827513.png)]

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

	
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。

猜你喜欢

转载自blog.csdn.net/weixin_42041788/article/details/114275909