写点什么

逐行解读 Spring- 没人比我更懂循环依赖

发布于: 2021 年 04 月 12 日
逐行解读Spring- 没人比我更懂循环依赖

一、前言

这一篇博文主要讲一下我们 spring 是怎么解决循环依赖的问题的。

二、什么是循环依赖

首先我们需要明确,什么是循环依赖呢?这里举一个简单的例子:

@Servicepublic class A {    @Autowired    private B b;}@Servicepublic class B {    @Autowired    private A a;}复制代码
复制代码

以这个例子来看,我们声明了 a、b 两个 bean,且 a 中需要注入一个 b,b 中需要注入一个 a。

结合我们上篇博文的 bean 生命周期的知识,我们来模拟一下这两个 bean 创建的流程:

如果没有缓存的设计,我们的虚线所示的分支将永远无法到达,导致出现无法解决的循环依赖问题....

三、三级缓存设计

1. 自己解决循环依赖问题

现在,假如我们是 spring 的架构师,我们应该怎么解决这个循环依赖问题呢?

1.1. 流程设计

首先如果要解决这个问题,我们的目标应该是要把之前的级联的无限创建流程切到,也就是说我们的流程要变为如下所示:

 

也就是说,我们需要在 B 实例创建后,注入 A 的时候,能够拿到 A 的实例,这样才能打破无限创建实例的情况。

而 B 实例的初始化流程,是在 A 实例创建之后,在 populateBean 方法中进行依赖注入时触发的。那么如果我们 B 实例化过程中,想要拿到 A 的实例,那么 A 实例必须在 createBeanInstance 创建实例后(实例都没有就啥也别说了)、populateBean 方法调用之前,就暴露出去,让 B 能通过 getBean 获取到!(同学们认真想一下这个流程,在现有的流程下改造,是不是只能够这样操作?自己先想清楚这个流程,再去结合 spring 源码验证,这一块的知识点你以后想忘都忘不掉)

那么结合我们的思路,我们再修改一下流程图:

 

1.2. 伪代码实现

流程已经设计好了,那么我们其实也可以出一下这个流程的伪代码(伪代码就不写加锁那些流程了):

// 正真已经初始化完成的mapprivate Map<String, Object> singleMap = new ConcurrentHashMap<>(16);// 缓存的mapprivate Map<String, Object> cacheMap = new ConcurrentHashMap<>(16);
protected Object getBean(final String beanName) { // 先看一下目标bean是否完全初始化完了,完全初始化完直接返回 Object single = singleMap.get(beanName); if (single != null) { return single; } // 再看一下目标bean实例是否已经创建,已经创建直接返回 single = cacheMap.get(beanName); if (single != null) { return single; } // 创建实例 Object beanInstance = createBeanInstance(beanName); // 实例创建之后,放入缓存 // 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了 // 之后的属性注入等逻辑还是在这个实例上做的 cacheMap.put(beanName, beanInstance); // 依赖注入,会触发依赖的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法调用 initializeBean(beanName, beanInstance); // 从缓存移除,放入实例map singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) return beanInstance;}复制代码
复制代码

可以看到,如果我们自己实现一个缓存结构来解决循环依赖的问题的话,可能只需要两层结构就可以了,但是 spring 却使用了 3 级缓存,它有哪些不一样的考量呢?

2. Spring 源码

我们已经知道该怎么解决循环依赖问题了,那么现在我们就一起看一下 spring 源码,看一下我们的分析是否正确。

由于之前我们已经详细讲过整个 bean 的生命周期了,所以这里就只挑三级缓存相关的代码段来讲了,会跳过比较多的代码,同学们如果有点懵,可以温习一下万字长文讲透 bean 的生命周期。

2.1. Spring 的三级缓存设计

2.1.1. 三级缓存源码

首先,在我们的 AbstractBeanFactory#doGetBean 的逻辑中:

// 初始化是通过getBean触发bean创建的,依赖注入最终也会使用getBean获取依赖的bean的实例public Object getBean(String name) throws BeansException {    return doGetBean(name, null, null, false);}protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name); Object bean;
// 获取bean实例 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // beanFactory相关,之后再讲 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 跳过一些代码 // 创建bean的逻辑 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // 跳过一些代码 } // 跳过一些代码 // 返回bean实例 return (T) bean;}复制代码
复制代码

可以看到,如果我们使用 getSingleton(beanName)直接获取到 bean 实例了,是会直接把 bean 实例返回的,我们一起看一下这个方法(这个方法属于 DefaultSingletonBeanRegistry):

// 一级缓存,缓存正常的bean实例/** Cache of singleton objects: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存,缓存还未进行依赖注入和初始化方法调用的bean实例/** Cache of early singleton objects: bean name to bean instance. */private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存,缓存bean实例的ObjectFactory/** Cache of singleton factories: bean name to ObjectFactory. */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
public Object getSingleton(String beanName) { return getSingleton(beanName, true);}
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 先尝试中一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName); // 获取不到,并且当前需要获取的bean正在创建中 // 第一次容器初始化触发getBean(A)的时候,这个isSingletonCurrentlyInCreation判断一定为false // 这个时候就会去走创建bean的流程,创建bean之前会先把这个bean标记为正在创建 // 然后A实例化之后,依赖注入B,触发B的实例化,B再注入A的时候,会再次触发getBean(A) // 此时isSingletonCurrentlyInCreation就会返回true了 // 当前需要获取的bean正在创建中时,代表出现了循环依赖(或者一前一后并发获取这个bean) // 这个时候才需要去看二、三级缓存 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 加锁了 synchronized (this.singletonObjects) { // 从二级缓存获取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 二级缓存也没有,并且允许获取早期引用的话 - allowEarlyReference传进来是true ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 从三级缓存获取ObjectFactory if (singletonFactory != null) { // 通过ObjectFactory获取bean实例 singletonObject = singletonFactory.getObject(); // 放入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); // 从三级缓存删除 // 也就是说对于一个单例bean,ObjectFactory#getObject只会调用到一次 // 获取到早期bean实例之后,就把这个bean实例从三级缓存升级到二级缓存了 this.singletonFactories.remove(beanName); } } } } // 不管从哪里获取到的bean实例,都会返回 return singletonObject;}复制代码
复制代码

一二级缓存都好理解,其实就可以理解为我们伪代码里面的那两个 Map,但是这个三级缓存是怎么回事?ObjectFactory 又是个什么东西?我们就先看一下这个 ObjectFactory 的结构:

@FunctionalInterfacepublic interface ObjectFactory<T> {	// 好吧,就是简简单单的一个获取实例的函数接口而已	T getObject() throws BeansException;}复制代码
复制代码

我们回到这个三级缓存的结构,二级缓存是是在 getSingleton 方法中 put 进去的,这跟我们之前分析的,创建 bean 实例之后放入,好像不太一样?那我们是不是可以推断一下,其实创建 bean 实例之后,是放入三级缓存的呢(总之实例创建之后是需要放入缓存的)?我们来跟一下 bean 实例化的代码,主要看一下上一篇时刻意忽略掉的地方:

// 代码做了很多删减,只把主要的逻辑放出来的protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)    throws BeanCreationException {
// 创建bean实例 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); // beanPostProcessor埋点调用 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 加入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
Object exposedObject = bean; try { // 依赖注入 populateBean(beanName, mbd, instanceWrapper); // 初始化方法调用 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); }
if (earlySingletonExposure) { // 第二个参数传false是不会从三级缓存中取值的 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 如果发现二级缓存中有值了 - 说明出现了循环依赖 if (exposedObject == bean) { // 并且initializeBean没有改变bean的引用 // 则把二级缓存中的bean实例返回出去 exposedObject = earlySingletonReference; } } } try { // 注册销毁逻辑 registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(...); }
return exposedObject;}复制代码
复制代码

可以看到,初始化一个 bean 是,创建 bean 实例之后,如果这个 bean 是单例 bean&&允许循环依赖 &&当前 bean 正在创建,那么将会调用 addSingletonFactory 加入三级缓存:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {    synchronized (this.singletonObjects) {        if (!this.singletonObjects.containsKey(beanName)) {            // 加入三级缓存            this.singletonFactories.put(beanName, singletonFactory);            this.earlySingletonObjects.remove(beanName);            this.registeredSingletons.add(beanName);        }    }}复制代码
复制代码

也就是说我们伪代码中的这一段有了:

// 创建实例Object beanInstance = createBeanInstance(beanName);// 实例创建之后,放入缓存// 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了// 之后的属性注入等逻辑还是在这个实例上做的cacheMap.put(beanName, beanInstance);复制代码
复制代码

那么接下来,完全实例化完成的 bean 又是什么时候塞入我们的实例 Map(一级缓存)singletonObjects 的呢?

这个时候我们就要回到调用 createBean 方法的这一块的逻辑了:

if (mbd.isSingleton()) {    // 我们回到这个位置    sharedInstance = getSingleton(beanName, () -> {        try {            return createBean(beanName, mbd, args);        }        catch (BeansException ex) {            destroySingleton(beanName);            throw ex;        }    });    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}复制代码
复制代码

可以看到,我们的 createBean 创建逻辑是通过一个 lamdba 语法传入 getSingleton 方法了,我们进入这个方法看一下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {    synchronized (this.singletonObjects) {        Object singletonObject = this.singletonObjects.get(beanName);        if (singletonObject == null) {            // 一级缓存拿不到            // 注意一下这个方法,这里会标记这个bean正在创建            beforeSingletonCreation(beanName);            boolean newSingleton = false;            try {                // 调用外部传入的lamdba,即createBean逻辑                // 获取到完全实例化好的bean                // 需要注意的是,这个时候这个bean的实例已经在二级缓存或者三级缓存中了                // 三级缓存:bean实例创建后放入的,如果没有循环依赖/并发获取这个bean,那会一直在三级缓存中                // 二级缓存:如果出现循环依赖,第二次进入getBean->getSingleton的时候,会从三级缓存升级到二级缓存                singletonObject = singletonFactory.getObject();                // 标记一下                newSingleton = true;            }            catch (IllegalStateException ex) {                singletonObject = this.singletonObjects.get(beanName);                if (singletonObject == null) {                    throw ex;                }            }            catch (BeanCreationException ex) {                throw ex;            }            finally {                // 这里是从正在创建的列表移除,到这里这个bean要么已经完全初始化完成了                // 要么就是初始化失败,都需要移除的                afterSingletonCreation(beanName);            }            if (newSingleton) {                // 如果是新初始化了一个单例bean,加入一级缓存                addSingleton(beanName, singletonObject);            }        }        return singletonObject;    }}复制代码
复制代码

哈哈,加入实例 Map(一级缓存)singletonObjects 的逻辑明显就是在这个 addSingleton 中了:

protected void addSingleton(String beanName, Object singletonObject) {    synchronized (this.singletonObjects) {        // 这个逻辑应该一点也不意外吧        // 放入一级缓存,从二、三级缓存删除,这里就用判断当前bean具体是在哪个缓存了        // 反正都要删的        this.singletonObjects.put(beanName, singletonObject);        this.singletonFactories.remove(beanName);        this.earlySingletonObjects.remove(beanName);        this.registeredSingletons.add(beanName);    }}复制代码
复制代码

也就是说,我们伪代码的这一块在 spring 里面也有对应的体现,完美:

// 初始化方法调用initializeBean(beanName, beanInstance);// 从缓存移除,放入实例mapsingleMap.put(beanName, beanInstance);cacheMap.remove(beanName)复制代码
复制代码

就这样,spring 通过缓存设计解决了循环依赖的问题。

2.1.2. 三级缓存解决循环依赖流程图

什么,看完代码之后还是有点模糊?那么把我们的流程图再改一下,按照 spring 的流程来:

 

2.1.3. 三级缓存解决循环依赖伪代码

// 一级缓存private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
protected Object getBean(final String beanName) { // !以下为getSingleton逻辑! // 先从一级缓存获取 Object single = singletonObjects.get(beanName); if (single != null) { return single; } // 再从二级缓存获取 single = earlySingletonObjects.get(beanName); if (single != null) { return single; } // 从三级缓存获取objectFactory ObjectFactory<?> objectFactory = singletonFactories.get(beanName); if (objectFactory != null) { single = objectFactory.get(); // 升到二级缓存 earlySingletonObjects.put(beanName, single); singletonFactories.remove(beanName); return single; } // !以上为getSingleton逻辑! // !以下为doCreateBean逻辑 // 缓存完全拿不到,需要创建 // 创建实例 Object beanInstance = createBeanInstance(beanName); // 实例创建之后,放入三级缓存 singletonFactories.put(beanName, () -> return beanInstance); // 依赖注入,会触发依赖的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法调用 initializeBean(beanName, beanInstance); // 依赖注入完之后,如果二级缓存有值,说明出现了循环依赖 // 这个时候直接取二级缓存中的bean实例 Object earlySingletonReference = earlySingletonObjects.get(beanName); if (earlySingletonReference != null) { beanInstance = earlySingletonObject; } // !以上为doCreateBean逻辑 // 从二三缓存移除,放入一级缓存 singletonObjects.put(beanName, beanInstance); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return beanInstance;}复制代码
复制代码

把所有逻辑放到一起之后会清晰很多,同学们只需要自行模拟一遍,再 populateBean 中再次调用 getBean 逻辑进行依赖注入,应该就能捋清楚了。

2.1.4. 标记当前 bean 正在创建

在我们刚刚看到的将 bean 实例封装成 ObjectFactory 并放入三级缓存的流程中,有一个判断是当前 bean 是正在创建,这个状态又是怎么判断的呢:

// 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&                                  isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {    // 加入三级缓存    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}复制代码
复制代码

我们看一下这个 isSingletonCurrentlyInCreation 的逻辑:

private final Set<String> singletonsCurrentlyInCreation =    Collections.newSetFromMap(new ConcurrentHashMap<>(16));public boolean isSingletonCurrentlyInCreation(String beanName) {    return this.singletonsCurrentlyInCreation.contains(beanName);}复制代码
复制代码

可以看到额,其实就是判断当前 beanName 是不是在这个 singletonsCurrentlyInCreation 容器中,那么这个容器中的值又是什么时候操作的呢?

希望同学们还记得 getSingleton(beanName, singletonFactory)中有调用的 beforeSingletonCreation 和 afterSingletonCreation:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {    synchronized (this.singletonObjects) {        Object singletonObject = this.singletonObjects.get(beanName);        if (singletonObject == null) {            // 一级缓存拿不到            // 注意一下这个方法,这里会标记这个bean正在创建            beforeSingletonCreation(beanName);            boolean newSingleton = false;            try {                // 调用外部传入的lamdba,即createBean逻辑                singletonObject = singletonFactory.getObject();                // 标记一下                newSingleton = true;            }            catch (BeanCreationException ex) {                throw ex;            }            finally {                // 这里是从正在创建的列表移除,到这里这个bean要么已经完全初始化完成了                // 要么就是初始化失败,都需要移除的                afterSingletonCreation(beanName);            }            if (newSingleton) {                // 如果是新初始化了一个单例bean,加入一级缓存                addSingleton(beanName, singletonObject);            }        }        return singletonObject;    }}复制代码
复制代码

我们现在来看一下这两个方法的逻辑:

protected void beforeSingletonCreation(String beanName) {    // 加入singletonsCurrentlyInCreation,由于singletonsCurrentlyInCreation是一个set    // 如果加入失败的话,说明在创建两次这个bean    // 这个时候会抛出循环依赖异常    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {        throw new BeanCurrentlyInCreationException(beanName);    }}
protected void afterSingletonCreation(String beanName) { // 从singletonsCurrentlyInCreation中删除 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); }}复制代码
复制代码

可以看到,我们这两个方法主要就是对 singletonsCurrentlyInCreation 容器进行操作的,inCreationCheckExclusions 这个容器可以不用管它,这名称一看就是一些白名单之类的配置。

这里需要主要的是 beforeSingletonCreation 中,如果 singletonsCurrentlyInCreation.add(beanName)失败的话,是会抛出 BeanCurrentlyInCreationException 的,这代表 spring 遇到了无法解决的循环依赖问题,此时会抛出异常中断初始化流程,毕竟单例的 bean 不允许被创建两次。

2.2. 为什么要设计为三级结构?

2.2.1. 只做两级缓存会有什么问题?

其实到这里,我们已经清楚,三级缓存的设计已经成功地解决了循环依赖的问题。

可是按我们自己的设计思路,明明只需要两级缓存就可以解决,spring 却使用了三级缓存,难道是为了炫技么?

这个时候,就需要我们再细致地看一下 bean 初始化过程了:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)    throws BeanCreationException {    // ...    if (earlySingletonExposure) {        // 放入三级缓存        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));    }    Object exposedObject = bean;    try {        populateBean(beanName, mbd, instanceWrapper);        // 这里这个引用被替换了        exposedObject = initializeBean(beanName, exposedObject, mbd);    }    // ...    return exposedObject;}复制代码
复制代码

仔细观察,initializeBean 方法是可能返回一个新的对象,从而把 createBeanInstance 创建的 bean 实例替换掉的:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {    // 调用aware接口    invokeAwareMethods(beanName, bean);    Object wrappedBean = bean;    if (mbd == null || !mbd.isSynthetic()) {        // 埋点        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);    }
try { // 初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } if (mbd == null || !mbd.isSynthetic()) { // 埋点 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }
return wrappedBean;}复制代码
复制代码

可以看到,我们的 postProcessBeforeInitialization 和 postProcessAfterInitialization 的埋点方法都是有可能把我们的 bean 替换掉的。

那么结合整个流程来看,由于我们放入缓存之后,initializeBean 方法中可能存在替换 bean 的情况,如果只有两级缓存的话:

 

这会导致 B 中注入的 A 实例与 singletonObjects 中保存的 AA 实例不一致,而之后其他的实例注入 a 时,却会拿到 singletonObjects 中的 AA 实例,这样肯定是不符合预期的。

2.2.2. 三级缓存是如何解决问题的

那么这个问题应该怎么解决呢?

这个时候我们就要回到添加三级缓存的地方看一下了。addSingletonFactory 的第二个参数就是一个 ObjectFactory,并且这个 ObjectFactory 最终将会放入三级缓存,现在我们再回头看调用 addSingletonFactory 的地方:

 // 加入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));复制代码
复制代码

熟悉 lamdba 语法的同学都知道,getEarlyBeanReference 其实就是放入三级缓存中的 ObjectFactory 的 getObject 方法的逻辑了,那我们一起来看一下,这个方法是做了什么:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {    Object exposedObject = bean;    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {        for (BeanPostProcessor bp : getBeanPostProcessors()) {            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;                // 调用了beanPostProcessor的一个埋点方法                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);            }        }    }    // 返回的是埋点替换的bean    return exposedObject;}复制代码
复制代码

咦,这里也有个埋点,可以替换掉 bean 的引用。

原来为了解决 initializeBean 可能替换 bean 引用的问题,spring 就设计了这个三级缓存,他在第三级里保存了一个 ObjectFactory,其实具体就是 getEarlyBeanReference 的调用,其中提供了一个 getEarlyBeanReference 的埋点方法,通过这个埋点方法,它允许开发人员把需要替换的 bean,提早替换出来。

比如说如果在 initializeBean 方法中希望把 A 换成 AA(这个逻辑肯定是通过某个 beanPostProcessor 来做的),那么你这个 beanPostProcessor 可以同时提供 getEarlyBeanReference 方法,在出现循环依赖的时候,可以提前把 A->AA 这个逻辑做了,并且 initializeBean 方法不再做这个 A->AA 的逻辑,并且,当我们的循环依赖逻辑走完,A 创建->注入 B->触发 B 初始化->注入 A->执行缓存逻辑获取 AA 实例并放入二级缓存->B 初始化完成->回到 A 的初始化逻辑时,通过以下代码:

protected Object doCreateBean(...) {    populateBean(beanName, mbd, instanceWrapper);    Object exposedObject = initializeBean(beanName, exposedObject, mbd);
if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { // 如果二级缓存存在,直接使用二级缓存 exposedObject = earlySingletonReference; } } } return exposedObject;}复制代码
复制代码

这样就能保证当前 bean 中注入的 AA 和 singletonObjects 中的 AA 实例是同一个对象了。

将会把二级缓存中的 AA 直接返回,这时就能保证 B 中注入的 AA 实例与 spring 管理起来的最终的 AA 实例是同一个了。

整个流程梳理一下就是这样:

 

2.2.3. 三级缓存的实际应用

既然设计了这个三级缓存,那么肯定是有实际需求的,我们上面分析了一大堆,现在正好举一个例子看一下,为什么 spring 需要三级缓存。

我们都知道,Spring 的 AOP 功能,是通过生成动态代理类来实现的,而最后我们使用的也都是代理类实例而不是原始类实例。而 AOP 代理类的创建,就是在 initializeBean 方法的 postProcessAfterInitialization 埋点中,我们直接看一下 getEarlyBeanReference 和 postProcessAfterInitialization 这两个埋点吧(具体类是 AbstractAutoProxyCreator,之后讲 AOP 的时候会细讲):

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport    implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {        private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);    // 如果出现循环依赖,getEarlyBeanReference会先被调用到    public Object getEarlyBeanReference(Object bean, String beanName) {        Object cacheKey = getCacheKey(bean.getClass(), beanName);        // 这个时候把当前类放入earlyProxyReferences        this.earlyProxyReferences.put(cacheKey, bean);        // 直接返回了一个代理实例        return wrapIfNecessary(bean, beanName, cacheKey);    }        public Object postProcessAfterInitialization(Object bean, String beanName) {        if (bean != null) {            Object cacheKey = getCacheKey(bean.getClass(), beanName);            // 注意这个判断,如果出现了循环依赖,这个if块是进不去的            if (this.earlyProxyReferences.remove(cacheKey) != bean) {                // 如果没有出现循环依赖,会在这里创建代理类                return wrapIfNecessary(bean, beanName, cacheKey);            }        }        return bean;    }}复制代码
复制代码

就这样,Spring 巧妙地使用三级缓存来解决了这个不同实例的问题。当然,如果我们需要自己开发类似代理之类的可能改变 bean 引用的功能时,也需要遵循 getEarlyBeanReference 方法的埋点逻辑,学习 AbstractAutoProxyCreator 中的方式,才能让 spring 按照我们的预期来工作。

四、三级缓存无法解决的问题

1. 构造器循环依赖

刚刚讲了很多三级缓存的实现,以及它是怎么解决循环依赖的问题的。

但是,是不是使用了三级缓存,就能解决所有的循环依赖问题呢?

当然不是的,有一个特殊的循环依赖,由于 java 语言特性的原因,是永远无法解决的,那就是构造器循环依赖。

比如以下两个类:

public class A {    private final B b;    public A(final B b) {        this.b = b;    }}public class B {    private final A a;    public B(final A a) {        this.a = a;    }}复制代码
复制代码

抛开 Spring 来讲,同学们你们有办法让这两个类实例化成功么?

不会有同学说,这有何难看我的:

// 你看,这样不行么~final A a = new A(new B(a));复制代码
复制代码

不好意思,这个真的不行,不信可以去试试。从语法上来讲,java 的语言特性决定了不允许使用未初始化完成的变量。我们只能无限套娃:

// 这样明显就没有解决问题,是个无限套娃的死循环final A a = new A(new B(new A(new B(new A(new B(...))))));复制代码
复制代码

所以,连我们都无法解决的问题,就不应该强求 spring 来解决了吧~

@Servicepublic class A {    private final B b;    public A(final B b) {        this.b = b;    }}@Servicepublic class B {    private final A a;    public B(final A a) {        this.a = a;    }}复制代码
复制代码

启动之后,果然报错了:

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?复制代码
复制代码

2. Spring 真的对构造器循环依赖束手无策么?

难道,spring 对于这种循环依赖真的束手无策了么?其实不是的,spring 还有 @Lazy 这个大杀器...只需要我们对刚刚那两个类小小的改造一下:

都说了成功了,运行结果同学们也能猜到了吧:
in a prt复制代码都说了成功了,运行结果同学们也能猜到了吧:
in a prt复制代码@Servicepublic class A { private final B b; public A(final B b) { this.b = b; } public void prt() { System.out.println("in a prt"); }}@Servicepublic class B { private final A a; public B(@Lazy final A a) { this.a = a; } public void prt() { a.prt(); }}// 启动@Testpublic void test() { applicationContext = new ClassPathXmlApplicationContext("spring.xml"); B bean = applicationContext.getBean(B.class); bean.prt();}复制代码
复制代码

都说了成功了,运行结果同学们也能猜到了吧:

in a prt复制代码
复制代码

(同学们也可以自己尝试一下~

3. @Lazy 原理

这个时候我们必须要想一下,spring 是怎么通过 @Lazy 来绕过我们刚刚解决不了的无限套娃问题了。

因为这里涉及到之前没有细讲的参数注入时候的参数解析问题,我这边就不带大家从入口处一步一步深入了,这边直接空降到目标代码 DefaultListableBeanFactory#resolveDependency:

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {	// 跳过...    // 这个地方是我们获取依赖的地方    // 尝试获取一个懒加载代理    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(        descriptor, requestingBeanName);    if (result == null) {        // 如果没获取到懒加载代理,就直接去获取bean实例了,这里最终会调用getBean        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);    }    return result;}复制代码
复制代码

我们直接看一下这个 getLazyResolutionProxyIfNecessary,这个方法就是获取 LazyProxy 的地方了:

public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
@Override @Nullable public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { // 如果是懒加载的,就构建一个懒加载的代理 return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); } // 判断是否是懒加载的,主要就是判断@Lazy注解,简单看下就好了 protected boolean isLazy(DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; }
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); // 构造了一个TargetSource TargetSource ts = new TargetSource() { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic() { return false; } @Override public Object getTarget() { // 再对应的getTarget方法里,才会去正真加载依赖,进而调用getBean方法 Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null); if (target == null) { Class<?> type = getTargetClass(); if (Map.class == type) { return Collections.emptyMap(); } else if (List.class == type) { return Collections.emptyList(); } else if (Set.class == type || Collection.class == type) { return Collections.emptySet(); } throw new NoSuchBeanDefinitionException(...); } return target; } @Override public void releaseTarget(Object target) { } }; // 创建代理工厂ProxyFactory ProxyFactory pf = new ProxyFactory(); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } // 创建返回代理类 return pf.getProxy(beanFactory.getBeanClassLoader()); }}复制代码
复制代码

同学们可能对 TargetSource 和 ProxyFactory 这些不熟悉,没关系,这不妨碍我们理解逻辑。

从源码我们可以看到,对于 @Lazy 的依赖,我们其实是返回了一个代理类(以下称为 LazyProxy)而不是正真通过 getBean 拿到目标 bean 注入。而真正地获取 bean 的逻辑,被封装到了一个 TargetSource 类的 getTarget 方法中,而这个 TargetSource 类最终被用来生成 LazyProxy 了,那么我们是不是可以推测,LazyProxy 应该持有这个 TargetSource 对象。

而从我们懒加载的语意来讲,是说真正使用到这个 bean(调用这个 bean 的某个方法时)的时候,才对这个属性进行注入/初始化。

那么对于当前这个例子来讲,就是说其实 B 创建的时候,并没有去调用 getBean("a")去获取构造器的参数,而是直接生成了一个 LazyProxy 来做 B 构造器的参数,而 B 之后正真调用到 A 的方法时,才会去调用 TargetSource 中的 getTarget 获取 A 实例,即调用 getBean("a"),这个时候 A 早就实例化好了,所以也就不会有循环依赖问题了。

4. 伪代码描述

还是同样,我们可以用伪代码来描述一下这个流程,伪代码我们就直接用静态代理来描述了:

public class A {    private final B b;    public A(final B b) {        this.b = b;    }    public void prt() {        System.out.println("in a prt");    }}
public class B { private final A a; public B(final A a) { this.a = a; } public void prt() { a.prt(); }}// A的懒加载代理类public class LazyProxyA extends A { private A source; private final Map<String, Object> ioc; private final String beanName; public LazyProxyA(Map<String, Object> ioc, String beanName) { super(null); this.ioc = ioc; this.beanName = beanName; } @Override public void prt() { if (source == null) { source = (A) ioc.get(beanName); } source.prt(); }}复制代码
复制代码

那么整个初始化的流程简单来描述就是:

Map<String, Object> ioc = new HashMap<>();void init() {    B b = new B(new LazyProxyA(ioc, "a"));    ioc.put("b", b);    A a = new A((B)ioc.get("b"));    ioc.put("a", a);}复制代码
复制代码

我们也模拟一下运行:

void test() {    // 容器初始化    init();    B b = (B)ioc.get("b");    b.prt();}复制代码
复制代码

当然是能成功打印的:

in a prt复制代码
复制代码

六、总结

关于循环依赖的问题,Spring 提供了通过设计缓存的方式来解决的,而设计为三级缓存,主要是为了解决 bean 初始化过程中,实例被放入缓存之后,实例的引用还可能在调用 initializeBean 方法时被替换的问题。

对于构造器的循环依赖,三级缓存设计是无法解决的,这属于 java 语言的约束;但是 spring 提供了一种使用 @Lazy 的方式,绕过这个限制,使得构造器的循环依赖在特定情况下(循环链中的某个注入打上 @Lazy 注解)也能解决。

(小声 BB,接下来更新应该会变慢了...

原文链接:https://juejin.cn/post/6860785780307492871

最后,小编还整理了面试指南大全,有需要的添加小助理 vx:mxzFAFAFA 即可!!




发布于: 2021 年 04 月 12 日阅读数: 30
用户头像

领取文章中资料添加小助理vx:mxzFAFAFA 2021.02.05 加入

Java架构大数据每天分享干货!

评论

发布
暂无评论
逐行解读Spring- 没人比我更懂循环依赖