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 出错源码分析
现在我们定位到源码,源码的位置如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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。