曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存

本文内容纲要:

- 写在前面的话

- 什么是三级缓存

- ioc容器,普通循环依赖,一级缓存够用吗

- ioc,一级缓存有什么问题

- 修改spring源码,只使用二级缓存

- 修改创建bean的代码,不放入第三级缓存,只放入第二级缓存

- 修改获取bean的代码,只从第一、第二级缓存获取,不从第三级获取

- 两级缓存,有啥问题?

- 三级缓存,怎么解决这个问题

- 关于SmartInstantiationAwareBeanPostProcessor

- getEarlyBeanReference

- postProcessBeforeInitialization

- postProcessAfterInitialization

- 整个流程串起来

- 源码

- 更新于2020-07-01:回答评论区问题

- 问题描述:只使用第一、第二级缓存,是否可行

- 修改源码

- 获取单例的地方,修改为只使用第一、第二级缓存

- 创建bean的地方,修改代码,获取earlyBeanReference后,手动放入第二级缓存

- 测试该场景下有什么问题

- 更新于2020-07-04:手动调用getEarlyReferencde,放到二级缓存,真的有问题吗

- 既然先行手动调用getEarlyBeanReference这种方案可以解决问题,为什么还要弄三级缓存

- 不错的参考资料

- 总结

写在前面的话

相关背景及资源:

曹工说Spring Boot" title="Spring Boot">Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享

曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志

曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了

曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的

曹工说Spring Boot源码(24)-- Spring注解扫描的瑞士军刀,asm技术实战(上)

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了

曹工说Spring Boot源码(28)-- Spring的component-scan机制,让你自己来进行简单实现,怎么办

工程代码地址 思维导图地址

工程结构图:

Image

什么是三级缓存

在获取单例bean的时候,会进入以下方法:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

// 1

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

// 2

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

// 3

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

// 4

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return singletonObject;

}

这里面涉及到了该类中的三个field。

/** 1级缓存 Cache of singleton objects: bean name to bean instance. */

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

/** 2级缓存 Cache of early singleton objects: bean name to bean instance. */

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

接着说前面的代码。

  • 1处,在最上层的缓存singletonObjects中,获取单例bean,这里面拿到的bean,直接可以使用;如果没取到,则进入2处

  • 2处,在2级缓存earlySingletonObjects中,查找bean;

  • 3处,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象(工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean),去获取包装后的bean,或者说,代理后的bean。

    什么是已经创建好的,但没有注入属性的bean?

    比如一个bean,有10个字段,你new了之后,对象已经有了,内存空间已经开辟了,堆里已经分配了该对象的空间了,只是此时的10个field还是null。

ioc容器,普通循环依赖,一级缓存够用吗

说实话,如果简单写写的话,一级缓存都没问题。给大家看一个我以前写的渣渣ioc容器:

曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

@Data

public class BeanDefinitionRegistry {

/**

* map:存储 bean的class-》bean实例

*/

private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();

/**

* 根据bean 定义获取bean

* 1、先查bean容器,查到则返回

* 2、生成bean,放进容器(此时,依赖还没注入,主要是解决循环依赖问题)

* 3、注入依赖

*

* @param beanDefiniton

* @return

*/

private Object getBean(MyBeanDefiniton beanDefiniton) {

Class<?> beanClazz = beanDefiniton.getBeanClazz();

Object bean = beanMapByClass.get(beanClazz);

if (bean != null) {

return bean;

}

// 0

bean = generateBeanInstance(beanClazz);

// 1 先行暴露,解决循环依赖问题

beanMapByClass.put(beanClazz, bean);

beanMapByName.put(beanDefiniton.getBeanName(), bean);

// 2 查找依赖

List<Field> dependencysByField = beanDefiniton.getDependencysByField();

if (dependencysByField == null) {

return bean;

}

// 3

for (Field field : dependencysByField) {

try {

autowireField(beanClazz, bean, field);

} catch (Exception e) {

throw new RuntimeException(beanClazz.getName() + " 创建失败",e);

}

}

return bean;

}

}

大家看上面的代码,我只定义了一个field,就是一个map,存放bean的class-》bean。

/**

* map:存储 bean的class-》bean实例

*/

private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();

  • 0处,生成bean,直接就是new
  • 1处,先把这个不完整的bean,放进map
  • 2处,获取需要注入的属性集合
  • 3处,进行自动注入,就是根据field的Class,去map里查找对应的bean,设置到field里。

上面这个代码,有啥问题没?spring为啥整整三级?

ioc,一级缓存有什么问题

一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。

如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。

所以,我们就要加一个map,这个map,用来存放那种不完整的bean。这里,还是拿spring举例。我们可以只用下面这两层:

/** 1级缓存 Cache of singleton objects: bean name to bean instance. */

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

/** 2级缓存 Cache of early singleton objects: bean name to bean instance. */

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

因为spring代码里是三级缓存,所以我们对源码做一点修改。

修改spring源码,只使用二级缓存

修改创建bean的代码,不放入第三级缓存,只放入第二级缓存

创建了bean之后,属性注入之前,将创建出来的不完整bean,放到earlySingletonObjects

这个代码,在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,我这边只有4.0版本的spring源码工程,不过这套逻辑,算是spring核心逻辑,和5.x版本差别不大。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {

BeanWrapper instanceWrapper = null;

if (mbd.isSingleton()) {

instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);

}

if (instanceWrapper == null) {

// 1

instanceWrapper = createBeanInstance(beanName, mbd, args);

}

final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

...

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

isSingletonCurrentlyInCreation(beanName));

if (earlySingletonExposure) {

// 2

earlySingletonObjects.put(beanName,bean);

registeredSingletonObjects.add(beanName);

// 3

// addSingletonFactory(beanName, new ObjectFactory() {

// public Object getObject() throws BeansException {

// return getEarlyBeanReference(beanName, mbd, bean);

// }

// });

}

  • 1处,就是创建对象,就是new
  • 2处,这是我加的代码,放入二级缓存
  • 3处,本来这就是增加三级缓存的位置,被我注释了。现在,就不会往三级缓存放东西了

修改获取bean的代码,只从第一、第二级缓存获取,不从第三级获取

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

之前的代码是文章开头那样的,我这里修改为:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

return singletonObject;

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

这样,就是只用两级缓存了。

两级缓存,有啥问题?

ioc循环依赖,一点问题都没有,完全够用了。

我这边一个简单的例子,

public class Chick{

private Egg egg;

public Egg getEgg() {

return egg;

}

public void setEgg(Egg egg) {

this.egg = egg;

}

}

public class Egg {

private Chick chick;

public Chick getChick() {

return chick;

}

public void setChick(Chick chick) {

this.chick = chick;

}

<bean id="chick" class="foo.Chick" lazy-init="true">

<property name="egg" ref="egg"/>

</bean>

<bean id="egg" class="foo.Egg" lazy-init="true">

<property name="chick" ref="chick"/>

</bean>

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(

"context-namespace-test-aop.xml");

Egg egg = (Egg) ctx.getBean(Egg.class);

结论:

Image

所以,一级缓存都能解决的问题,二级当然更没问题。

但是,如果我这里给上面的Egg类,加个切面(aop的逻辑,意思就是最终会生成Egg的一个动态代理对象),那还有问题没?

<aop:config>

<aop:pointcut id="mypointcut" expression="execution(public * foo.Egg.*(..))"/>

<aop:aspect id="myAspect" ref="performenceAspect">

<aop:after method="afterIncubate" pointcut-ref="mypointcut"/>

</aop:aspect>

</aop:config>

注意这里的切点:

execution(public * foo.Egg.*(..))

就是切Egg类的方法。

加了这个逻辑后,我们继续运行,在Egg egg = (Egg) ctx.getBean(Egg.class);行,会抛出如下异常:

Image

我涂掉了一部分,因为那是官方对这个异常的推论,因为我们改了代码,所以推论不准确,因此干脆隐去。

这个异常是说:

兄弟啊,bean egg已经被注入到了其他bean:chick中。(因为我们循环依赖了),但是,注入到chick中的,是Egg类型。但是,我们这里最后对egg这个bean,进行了后置处理,生成了代理对象。那其他bean里,用原始的bean,是不是不太对啊?

所以,spring给我们抛错了。

怎么理解呢? 以io流举例,我们一开始都是用的原始字节流,然后给别人用的也是字节流,但是,最后,我感觉不方便,我自己悄悄弄了个缓存字符流(类比代理对象),我是方便了,但是,别人用的,还是原始的字节流啊。

你bean不是单例吗?不能这么玩吧?

所以,这就是二级缓存,不能解决的问题。

什么问题?aop情形下,注入到其他bean的,不是最终的代理对象。

三级缓存,怎么解决这个问题

要解决这个问题,必须在其他bean(chick),来查找我们(以上面例子为例,我们是egg)的时候,查找到最终形态的egg,即代理后的egg。

怎么做到这点呢?

加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的egg。

ok,我们将前面修改的代码还原:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {

BeanWrapper instanceWrapper = null;

if (mbd.isSingleton()) {

instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);

}

if (instanceWrapper == null) {

// 1

instanceWrapper = createBeanInstance(beanName, mbd, args);

}

final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

isSingletonCurrentlyInCreation(beanName));

if (earlySingletonExposure) {

// 2

// Map<String, Object> earlySingletonObjects = this.getEarlySingletonObjects();

// earlySingletonObjects.put(beanName,bean);

//

// Set<String> registeredSingletonObjects = this.getRegisteredSingletonObjects();

// registeredSingletonObjects.add(beanName);

// 3

addSingletonFactory(beanName, new ObjectFactory() {

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

}

  • 1处,创建bean,单纯new,不注入

  • 2处,revert我们的代码

  • 3处,这里new了一个ObjectFactory,然后会存入到如下的第三级缓存。

    /** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    注意,new一个匿名内部类(假设这个匿名类叫AA)的对象,其中用到的外部类的变量,都会在AA中隐式生成对应的field。

    Image

    大家看上图,里面的3个字段,和下面代码1处中的,几个字段,是一一对应的。

    addSingletonFactory(beanName, new ObjectFactory() {

    public Object getObject() throws BeansException {

    // 1

    return getEarlyBeanReference(beanName, mbd, bean);

    }

    });

ok,现在,egg已经把自己存进去了,存在了第三级缓存,1级和2级都没有,那后续chick在使用getSingleton查找egg的时候,就会进入下面的逻辑了(就是文章开头的那段代码,下面已经把我们的修改还原了):

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

// Object singletonObject = this.singletonObjects.get(beanName);

// if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

// synchronized (this.singletonObjects) {

// singletonObject = this.earlySingletonObjects.get(beanName);

// return singletonObject;

// }

// }

// return (singletonObject != NULL_OBJECT ? singletonObject : null);

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

ObjectFactory singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

// 1

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

上面就会进入1处,调用singletonFactory.getObject();

而前面我们知道,这个factory的逻辑是:

addSingletonFactory(beanName, new ObjectFactory() {

public Object getObject() throws BeansException {

// 1

return getEarlyBeanReference(beanName, mbd, bean);

}

});

1处就是这个工厂方法的逻辑,这里面,简单说,就会去调用各个beanPostProcessor的getEarlyBeanReference方法。

其中,主要就是aop的主力beanPostProcessor,AbstractAutoProxyCreator#getEarlyBeanReference

其实现如下:

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

this.earlyProxyReferences.add(cacheKey);

// 1

return wrapIfNecessary(bean, beanName, cacheKey);

}

这里的1处,就会去对egg这个bean,创建代理,此时,返回的对象,就是个代理对象了,那,注入到chick的,自然也是代理后的egg了。

关于SmartInstantiationAwareBeanPostProcessor

我们上面说的那个getEarlyBeanReference就在这个接口中。

Image

这个接口继承了BeanPostProcessor

而创建代理对象,目前就是在如下两个方法中去创建:

public interface BeanPostProcessor {

Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

这两个方法,都是在实例化之后,创建代理。那我们前面创建代理,是在依赖解析过程中:

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {

...

Object getEarlyBeanReference(Object bean, String beanName) throws BeansException;

}

所以,spring希望我们,在这几处,要返回同样的对象,即:既然你这几处都要返回代理对象,那就不能返回不一样的代理对象。

Image

那我们再看看,到底,AbstractAutoProxyCreator有没有遵守约定呢,这几个方法里,有没有去返回同样的代理包装对象呢?

getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

// 1

this.earlyProxyReferences.add(cacheKey);

return wrapIfNecessary(bean, beanName, cacheKey);

}

1处,往field:

private final Set<Object> earlyProxyReferences =

Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>(16));

里,加了个cachekey,这个cachekey,主要也就是如下的字符串,用来唯一标识而已。

protected Object getCacheKey(Class<?> beanClass, String beanName) {

return beanClass.getName() + "_" + beanName;

}

我们可以看看这个field在哪里被用到了。

Image

也就两处,一处就是当前位置;另外一处,下面讲。

这里,主要就是看看到底要不要生成代理对象,要的话,就生成,不要就算了,另外,做了个标记:在earlyProxyReferences加了当前bean的key,表示:当前bean,已经被getEarlyBeanReference方法处理过了。

至于,最终到底有没有生成代理对象,另说。毕竟调用wrapIfNecessary也不是说,一定就满足切面,要生成代理对象。

可能返回的仍然是原始对象。

postProcessBeforeInitialization

public Object postProcessBeforeInitialization(Object bean, String beanName) {

return bean;

}

这一处,没做处理。

postProcessAfterInitialization

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

if (bean != null) {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

// 1

if (!this.earlyProxyReferences.contains(cacheKey)) {

return wrapIfNecessary(bean, beanName, cacheKey);

}

}

return bean;

}

这里,1处这个判断哈,就用到了前面我们说的那个field。那个field,只在两处用,一处就是调用getEarlyBeanReference,会往里面把当前bean的key放进去;另外一处,就是这里。

这里判断,如果field里不包含当前bean,就去调用wrapIfNecessary;如果包含(意味着,getEarlyBeanReference处理过了),就不调用了。

这里,说到底,就是保证了,wrapIfNecessary只被调用一次。

Image

看吧,wrapIfNecessary也就这两处被调用了。

所以,我们可以得出结论,在aop这个beanPostProcessor中,有多处机会可以返回一个proxy对象,但是,最终,只要在其中一处处理了,其他处,根本不再继续处理。

另外,还有一点很重要,在这个aop beanPostProcessor中,传入了原始的bean,我们会去判断,是否要给它创建代理,如果要,就创建;如果不要则:

返回原始对象。

整个流程串起来

上面这个后置处理器看明白了,接下来,再看看创建bean的核心流程:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {

// 1

BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

final Object bean = instanceWrapper.getWrappedInstance();

if (earlySingletonExposure) {

// 2

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

}

// 3

Object exposedObject = bean;

// 4

populateBean(beanName, mbd, instanceWrapper);

// 5

if (exposedObject != null) {

exposedObject = initializeBean(beanName, exposedObject, mbd);

}

if (earlySingletonExposure) {

// 6

Object earlySingletonReference = getSingleton(beanName, false);

if (earlySingletonReference != null) {

// 7

if (exposedObject == bean) {

exposedObject = earlySingletonReference;

}

else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {

// 8

...

}

}

}

return exposedObject;

}

上面流程中,做了部分删减。但基本创建一个bean,就这几步了。

  • 1处,创建bean对象,此时,属性什么的全是null,可以理解为,只是new了,field还没设置

  • 2处,添加到第三级缓存;加进去的,只是个factory,只有循环依赖的时候,才会发挥作用

  • 3处,把原始bean,存到exposedObject

  • 4处,填充属性;循环依赖情况下,A/B循环依赖。假设当前为A,那么此时填充A的属性的时候,会去:

    new B;

    填充B的field,发现field里有一个是A类型,然后就去getBean("A"),然后走到第三级缓存,拿到了A的ObjectFactory,然后调用ObjectFactory,然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);

    经过上面的步骤后,B里面,field已经填充ok,其中,且填充的field是代理后的A,这里命名为proxy A。

    B 继续其他的后续处理。

    B处理完成后,被填充到当前的origin A(原始A)的field中

  • 5处,对A进行后置处理,此时调用aop后置处理器的,postProcessAfterInitialization;前面我们说了,此时不会再去调用wrapIfNecessary,所以这里直接返回原始A,即 origin A

  • 6处,去缓存里获取A,拿到的A,是proxy A

  • 7处,我们梳理下:

    exposedObject:origin A

    bean:原始A

    earlySingletonReference: proxy A

    此时,下面这个条件是满足的,所以,exposedObject,最终被替换为proxy A:

    if (exposedObject == bean) {

    exposedObject = earlySingletonReference;

    }

源码

文章用到的aop循环依赖的demo,自己写一个也可以,很简单:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-aop-xml-demo-cycle-reference

更新于2020-07-01:回答评论区问题

问题描述:只使用第一、第二级缓存,是否可行

Image

Image

这两个问题,本质是一个问题,就是,不用第三级的ObjectFactory行不行,代码直接调用getEarlyBeanReference,拿到bean A的早期引用后,放到第二级缓存,后续bean B去解析依赖时,,注入的不就是最终形态的bean A了吗。

我回答的时候,只是思考了一下,并没有实际测试。下面我们修改下源码,测试一下。

修改源码

获取单例的地方,修改为只使用第一、第二级缓存

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

return singletonObject;

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

创建bean的地方,修改代码,获取earlyBeanReference后,手动放入第二级缓存

如下是原始代码:

AbstractAutowireCapableBeanFactory#doCreateBean

// 原始代码长这样

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);

if (earlyBeanReference != bean) {

log.info("{} has been wrapped to {}", bean,earlyBeanReference);

}

return earlyBeanReference;

}

});

修改后,长这样:

/**

* 只使用第一、第二级缓存,即,只使用:

* singletonObjects

* earlySingletonObjects

* 不使用第三级:

* singletonFactories

*/

// 1

Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);

// 2

addEarlyReference(beanName, earlyBeanReference);

1处,主要是,获取了早期引用,然后2处,调用我们自己写的一个方法:

DefaultSingletonBeanRegistry#addEarlyReference

/**

* 修改版本,直接添加早期引用

* {@link SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)}

* 拿到早期引用,然后添加到earlySingletonObjects

* 不使用第三级缓存singletonFactories

* @param beanName

* @param earlyReference 调用getEarlyBeanReference(java.lang.Object, java.lang.String)

*/

protected void addEarlyReference(String beanName, Object earlyReference) {

synchronized (this.singletonObjects) {

if (!this.singletonObjects.containsKey(beanName)) {

// 1

this.earlySingletonObjects.put(beanName,earlyReference);

this.registeredSingletons.add(beanName);

}

}

}

1处代码,把早期引用,存到了二级缓存。

测试该场景下有什么问题

  1. 首先,我们继续用前面的例子,我们要getBean(egg),egg这个bean,依赖了chick,chick又依赖egg。egg最终要生成代理对象。

  2. 首先,创建egg,获取其早期引用,放到二级缓存。

    Image

    这里注意,egg虽然生成了代理对象,但是,其属性,chick是null。

  3. 接下来,开始egg解析依赖的时候,发现依赖了chick,所以,会去创建chick,并创建chick的早期引用

    Image

    到此为止,早期引用中,已经存放了两个对象,egg(代理对象,但chick属性为null),chick。

  4. 填充chick的field:egg

Image

此时,chick已经好了。唯一的问题是,egg还没好,egg里面的chick属性,还是null。

  1. egg此时的field依赖,chick已经解决了,此时的egg,长这样:

    Image

  2. 此时,egg的属性已经填充了,是不是该去生成代理了

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

    if (bean != null) {

    Object cacheKey = getCacheKey(bean.getClass(), beanName);

    // 1

    if (!this.earlyProxyReferences.contains(cacheKey)) {

    return wrapIfNecessary(bean, beanName, cacheKey);

    }

    }

    return bean;

    }

不过这里的1处,earlyProxyReferences已经包含了egg这个bean了,所以不会再去生成代理。

  1. 使用不完整的早期引用,替换了populateDependency完成的egg,出大事了

    if (earlySingletonExposure) {

    Object earlySingletonReference = getSingleton(beanName, false);

    if (earlySingletonReference != null) {

    // 1

    if (exposedObject == bean) {

    // 2

    exposedObject = earlySingletonReference;

    }

    }

    Image

    此时,1处是满足的,见上图。所以,进入2处,2处的earlySingletonReference,就是我们从二级缓存拿到的早期引用,这个早期引用,一切都好,唯一的问题是,这里的field:chick是null

    Image

    所以,问题,就是这么个问题,只用二级缓存,此时的chick是null。

更新于2020-07-04:手动调用getEarlyReferencde,放到二级缓存,真的有问题吗

这次的评论区问题,说实话,我非常感谢,因为,借此问题,我发现,我上面讲的:

更新于2020-07-01的那部分,结论是错误的。

Image

这个问题是什么意思呢?就是这位同学认为:

更新于2020-07-01那部分的试验,最终我的结论是,最终这个Egg对象,是一个代理对象,但是其中的Chick field是null,所以,有问题。

但是,正常情况下,生成的代理对象,其中的field,本来就是null。

我举个例子,是我们实际中的业务代码:

@Service

public class SeatInformationServiceImpl extends ServiceImpl<SeatInformationMapper, SeatInformation> implements ISeatInformationService {

@Autowired

private SeatInformationMapper seatInformationMapper;

@Autowired

private ICenterService centerService;

@Autowired

private RestTemplate restTemplate;

// 1

@Transactional(rollbackFor = Exception.class)

@Override

public void delete(String token,Long seatId) throws BusinessException {

...

}

这个service,就是我从业务代码里copy的,其中,1处,注解了Transactional注解,而事务一般就是基于aop实现的,所以,最终这个service,肯定是会生成代理对象的。

我们看看这个生成的代理对象,长什么样子?

Image

而代理对象,要获取真正的target时,是可以拿到的,如下所示。

Image

ok,所以,我2020-07-01的试验中,过程是没问题的,但结论有问题。

  • 错误结论:手动调用getEarlyReference放入二级缓存,去掉三级缓存,这样有问题
  • 正确结论:手动调用getEarlyReference放入二级缓存,去掉三级缓存,这样没有问题

而且,关键是,我在这种试验场景下,最终去执行如下代码,切面也是生效了的:

public static void main(String[] args) {

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(

"context-namespace-test-aop.xml");

Egg egg = (Egg) ctx.getBean(Egg.class);

egg.incubate();

}

开始孵化

09:45:41.754 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'performenceAspect'

孵化完成 --------这部分是切面打印的

既然先行手动调用getEarlyBeanReference这种方案可以解决问题,为什么还要弄三级缓存

问题就在于,我们每次都去调用getEarlyReference,是可以解决问题,没错。但是,这一步,很多时候都是没有必要的。

在没有循环依赖的时候,这个方法,是从来不会被调用的;也就是说,我们为了解决系统中那百分之1可能出现的循环依赖问题,而让百分之99的bean,创建时,都去走上这么一圈。

效率说不过去吧?

ok,大家要明白,这个方法,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference,其存在的意义,就是为了解决aop场景下的循环依赖。

没有这个场景,就不需要这个方案。

Image

大家可以仔细思考下,上面的红圈这里,这个条件,什么时候才会是true?

不错的参考资料

https://blog.csdn.net/f641385712/article/details/92801300

总结

如果有问题,欢迎指出;欢迎加群讨论;有帮助的话,请点个赞吧,谢谢

本文内容总结:写在前面的话,什么是三级缓存,ioc容器,普通循环依赖,一级缓存够用吗,ioc,一级缓存有什么问题,修改spring源码,只使用二级缓存,修改创建bean的代码,不放入第三级缓存,只放入第二级缓存,修改获取bean的代码,只从第一、第二级缓存获取,不从第三级获取,两级缓存,有啥问题?,三级缓存,怎么解决这个问题,关于SmartInstantiationAwareBeanPostProcessor,getEarlyBeanReference,postProcessBeforeInitialization,postProcessAfterInitialization,整个流程串起来,源码,更新于2020-07-01:回答评论区问题,问题描述:只使用第一、第二级缓存,是否可行,修改源码,获取单例的地方,修改为只使用第一、第二级缓存,创建bean的地方,修改代码,获取earlyBeanReference后,手动放入第二级缓存,测试该场景下有什么问题,更新于2020-07-04:手动调用getEarlyReferencde,放到二级缓存,真的有问题吗,既然先行手动调用getEarlyBeanReference这种方案可以解决问题,为什么还要弄三级缓存,不错的参考资料,总结,

原文链接:https://www.cnblogs.com/grey-wolf/p/13034371.html

以上是 曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存 的全部内容, 来源链接: utcz.com/z/295974.html

回到顶部