Spring系列第56篇:一文搞懂spring到底为什么要用三级缓存??

共 21074字,需浏览 43分钟

 ·

2021-02-23 16:37

今天来聊一个面试中经常会被问到的问题,咱们一起必须把这个问题搞懂。

问题:spring 中为什么需要用三级缓存来解决这个问题?用二级缓存可以么?

我先给出答案:不可用

这里先声明下:

本文未指明 bean scope 默认情况下,所有 bean 都是单例的,即 scope 是 singleton,即下面所有问题都是在单例的情况下分析的。

代码中注释很详细,一定要注意多看代码中的注释。

1、循环依赖相关问题

1、什么是循环依赖?

2、循环依赖的注入对象的 2 种方式:构造器的方式、setter 的方式

3、构造器的方式详解

4、spring 是如何知道有循环依赖的?

5、setter 方式详解

6、需注意循环依赖注入的是半成品

7、为什么必须用三级缓存?

2、什么是循环依赖?

A 依赖于 B,B 依赖于 A,比如下面代码

public class A {
    private B b;
}

public class B {
    private A a;
}

3、循环依赖注入对象的 2 种方式

3.1、构造器的方式

通过构造器相互注入对方,代码如下

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

3.2、setter 的方式

通过 setter 方法注入对方,代码如下

public class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

4、构造器的方式详解

4.1、构造器的方式知识点

1、构造器的方式如何注入?

2、循环依赖,构造器的方式,spring 的处理过程是什么样的?

3、循环依赖构造器的方式案例代码解析

4.2、构造器的方式如何注入?

再来看一下下面这 2 个类,相互依赖,通过构造器的方式相互注入对方。

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

大家来思考一个问题:2 个类都只能创建一个对象,大家试试着用硬编码的方式看看可以创建这 2 个类的对象么?

我想大家一眼就看出来了,无法创建。

创建 A 的时候需要先有 B,而创建 B 的时候需要先有 A,导致无法创建成功。

4.3、循环依赖,构造器的方式,spring 的处理过程是什么样的?

spring 在创建 bean 之前,会将当前正在创建的 bean 名称放在一个列表中,这个列表我们就叫做 singletonsCurrentlyInCreation,用来记录正在创建中的 bean 名称列表,创建完毕之后,会将其从 singletonsCurrentlyInCreation 列表中移除,并且会将创建好的 bean 放到另外一个单例列表中,这个列表叫做 singletonObjects,下面看一下这两个集合的代码,如下:

代码位于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中

//用来存放正在创建中的bean名称列表
private final Set singletonsCurrentlyInCreation =
   Collections.newSetFromMap(new ConcurrentHashMap<>(16));

//用来存放已经创建好的单例bean,key为bean名称,value为bean的实例
private final Map singletonObjects = new ConcurrentHashMap<>(256);

下面我们来看下面 2 个 bean 的创建过程

@Compontent
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

@Compontent
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

过程如下

1、从singletonObjects查看是否有a,此时没有
2、准备创建a
3、判断a是否在singletonsCurrentlyInCreation列表,此时明显不在,则将a加入singletonsCurrentlyInCreation列表
4、调用a的构造器A(B b)创建A
5、spring发现A的构造器需要用到b
6、则向spring容器查找b,从singletonObjects查看是否有b,此时没有
7、spring准备创建b
8、判断b是否在singletonsCurrentlyInCreation列表,此时明显不在,则将b加入singletonsCurrentlyInCreation列表
9、调用b的构造器B(A a)创建b
10、spring发现B的构造器需要用到a,则向spring容器查找a
11、则向spring容器查找a,从singletonObjects查看是否有a,此时没有
12、准备创建a
13、判断a是否在singletonsCurrentlyInCreation列表,上面第3步中a被放到了这个列表,此时a在这个列表中,走到这里了,说明a已经存在创建列表中了,此时程序又来创建a,说明这么一直走下去会死循环,此时spring会弹出异常,终止bean的创建操作。

4.4、通过这个过程,我们得到了 2 个结论

1、循环依赖如果是构造器的方式,bean 无法创建成功,这个前提是 bean 都是单例的,bean 如果是多例的,大家自己可以分析分析。

2、spring 是通过 singletonsCurrentlyInCreation 这个列表来发现循环依赖的,这个列表会记录创建中的 bean,当发现 bean 在这个列表中存在了,说明有循环依赖,并且这个循环依赖是无法继续走下去的,如果继续走下去,会进入死循环,此时 spring 会抛出异常让系统终止。

判断循环依赖的源码在下面这个位置,singletonsCurrentlyInCreation是 Set 类型的,Set 的 add 方法返回 false,说明被 add 的元素在 Set 中已经存在了,然后会抛出循环依赖的异常。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

private final Set singletonsCurrentlyInCreation =
  Collections.newSetFromMap(new ConcurrentHashMap<>(16));

protected void beforeSingletonCreation(String beanName) {
    //bean名称已经存在创建列表中,则抛出循环依赖异常
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        //抛出循环依赖异常
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

//循环依赖异常
public BeanCurrentlyInCreationException(String beanName) {
    super(beanName,
          "Requested bean is currently in creation: Is there an unresolvable circular reference?");
}

4.5、spring 构造器循环依赖案例

创建类 A

package com.javacode2018.cycledependency.demo1;

import org.springframework.stereotype.Component;

@Component
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

创建类 B

package com.javacode2018.cycledependency.demo1;

import org.springframework.stereotype.Component;

@Component
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

启动类

package com.javacode2018.cycledependency.demo1;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        //刷新容器上下文,触发单例bean创建
        context.refresh();
        //关闭上下文
        context.close();
    }
}

运行上面的 main 方法,产生了异常,部分异常信息如下,说明创建 beana的时候出现了循环依赖,导致创建 bean 无法继续进行,以后大家遇到这个错误了,应该可以很快定位到问题了。

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)

5、setter 方式详解

再来看看 setter 的 2 个类的源码

public class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

大家试试通过硬编码的方式来相互注入,很简单吧,如下面这样

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

咱们通过硬编码的方式可以搞成功的,spring 肯定也可以搞成功,确实,setter 循环依赖,spring 可以正常执行。

下面来看 spring 中 setter 循环依赖注入的流程。

6、spring 中 setter 循环依赖注入流程

spring 在创建单例 bean 的过程中,会用到三级缓存,所以需要先了解三级缓存。

6.1、三级缓存是哪三级?

spring 中使用了 3 个 map 来作为三级缓存,每一级对应一个 map

第几级缓存对应的 map说明
第 1 级Map singletonObjects用来存放已经完全创建好的单例 bean

beanName->bean 实例
第 2 级Map earlySingletonObjects用来存放早期的 bean

beanName->bean 实例
第 3 级Map> singletonFactories用来存放单例 bean 的 ObjectFactory

beanName->ObjectFactory 实例

这 3 个 map 的源码位于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中。

6.2、单例 bean 创建过程源码解析

代码入口

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

step1:doGetBean

如下,这个方法首先会调用 getSingleton 获取 bean,如果可以获取到,就会直接返回,否则会执行创建 bean 的流程

step2:getSingleton(beanName, true)

源码如下,这个方法内部会调用getSingleton(beanName, true)获取 bean,注意第二个参数是true,这个表示是否可以获取早期的 bean,这个参数为 true,会尝试从三级缓存singletonFactories中获取 bean,然后将三级缓存中获取到的 bean 丢到二级缓存中。

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //从第1级缓存中获取bean
    Object singletonObject = this.singletonObjects.get(beanName);
    //第1级中没有,且当前beanName在创建列表中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //从第2级缓存汇总获取bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            //第2级缓存中没有 && allowEarlyReference为true,也就是说2级缓存中没有找到bean且beanName在当前创建列表中的时候,才会继续想下走。
            if (singletonObject == null && allowEarlyReference) {
                //从第3级缓存中获取bean
                ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
                //第3级中有获取到了
                if (singletonFactory != null) {
                    //3级缓存汇总放的是ObjectFactory,所以会调用其getObject方法获取bean
                    singletonObject = singletonFactory.getObject();
                    //将3级缓存中的bean丢到第2级中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //将bean从三级缓存中干掉
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

step3:getSingleton(String beanName, ObjectFactory singletonFactory)

上面调用getSingleton(beanName, true)没有获取到 bean,所以会继续走 bean 的创建逻辑,会走到下面代码,如下

进入getSingleton(String beanName, ObjectFactory singletonFactory),源码如下,只留了重要的部分

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    //从第1级缓存中获取bean,如果可以获取到,则自己返回
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
        //将beanName加入当前创建列表中
        beforeSingletonCreation(beanName);
        //①:创建单例bean
        singletonObject = singletonFactory.getObject();
        //将beanName从当前创建列表中移除
        afterSingletonCreation(beanName);
        //将创建好的单例bean放到1级缓存中,并将其从2、3级缓存中移除
        addSingleton(beanName, singletonObject);
    }
    return singletonObject;
}

注意代码,会调用singletonFactory.getObject()创建单例 bean,我们回头看看singletonFactory这个变量的内容,如下图,可以看出主要就是调用createBean这个方法

下面我们进入createBean方法,这个内部最终会调用doCreateBean来创建 bean,所以我们主要看doCreateBean

step4:doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException 
{

    // ①:创建bean实例,通过反射实例化bean,相当于new X()创建bean的实例
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

    // bean = 获取刚刚new出来的bean
    Object bean = instanceWrapper.getWrappedInstance();

    // ②:是否需要将早期的bean暴露出去,所谓早期的bean相当于这个bean就是通过new的方式创建了这个对象,但是这个对象还没有填充属性,所以是个半成品
    // 是否需要将早期的bean暴露出去,判断规则(bean是单例 && 是否允许循环依赖 && bean是否在正在创建的列表中)
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        //③:调用addSingletonFactory方法,这个方法内部会将其丢到第3级缓存中,getEarlyBeanReference的源码大家可以看一下,内部会调用一些方法获取早期的bean对象,比如可以在这个里面通过aop生成代理对象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 这个变量用来存储最终返回的bean
    Object exposedObject = bean;
    //填充属性,这里面会调用setter方法或者通过反射将依赖的bean注入进去
    populateBean(beanName, mbd, instanceWrapper);
    //④:初始化bean,内部会调用BeanPostProcessor的一些方法,对bean进行处理,这里可以对bean进行包装,比如生成代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);


    //早期的bean是否被暴露出去了
    if (earlySingletonExposure) {
        /**
         *⑤:getSingleton(beanName, false),注意第二个参数是false,这个为false的时候,
         * 只会从第1和第2级中获取bean,此时第1级中肯定是没有的(只有bean创建完毕之后才会放入1级缓存)
         */

        Object earlySingletonReference = getSingleton(beanName, false);
        /**
         * ⑥:如果earlySingletonReference不为空,说明第2级缓存有这个bean,二级缓存中有这个bean,说明了什么?
         * 大家回头再去看看上面的分析,看一下什么时候bean会被放入2级缓存?
         * (若 bean存在三级缓存中 && beanName在当前创建列表的时候,此时其他地方调用了getSingleton(beanName, false)方法,那么bean会从三级缓存移到二级缓存)
         */

        if (earlySingletonReference != null) {
            //⑥:exposedObject==bean,说明bean创建好了之后,后期没有被修改
            if (exposedObject == bean) {
                //earlySingletonReference是从二级缓存中获取的,二级缓存中的bean来源于三级缓存,三级缓存中可能对bean进行了包装,比如生成了代理对象
                //那么这个地方就需要将 earlySingletonReference 作为最终的bean
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                //回头看看上面的代码,刚开始exposedObject=bean,
                // 此时能走到这里,说明exposedObject和bean不一样了,他们不一样了说明了什么?
                // 说明initializeBean内部对bean进行了修改
                // allowRawInjectionDespiteWrapping(默认是false):是否允许早期暴露出去的bean(earlySingletonReference)和最终的bean不一致
                // hasDependentBean(beanName):表示有其他bean以利于beanName
                // getDependentBeans(beanName):获取有哪些bean依赖beanName
                String[] dependentBeans = getDependentBeans(beanName);
                Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    //判断dependentBean是否已经被标记为创建了,就是判断dependentBean是否已经被创建了
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                /**
                 *
                 * 能走到这里,说明早期的bean被别人使用了,而后面程序又将exposedObject做了修改
                 * 也就是说早期创建的bean是A,这个A已经被有些地方使用了,但是A通过initializeBean之后可能变成了B,比如B是A的一个代理对象
                 * 这个时候就坑了,别人已经用到的A和最终容器中创建完成的A不是同一个A对象了,那么使用过程中就可能存在问题了
                 * 比如后期对A做了增强(Aop),而早期别人用到的A并没有被增强
                 */

                if (!actualDependentBeans.isEmpty()) {
                    //弹出异常(早期给别人的bean和最终容器创建的bean不一致了,弹出异常)
                    throw new BeanCurrentlyInCreationException(beanName,"异常内容见源码。。。。。");
                }
            }
        }
    }

    return exposedObject;
}

上面的 step1~step4,大家要反复看几遍**,下面这几个问题搞清楚之后,才可以继续向下看,不懂的结合源码继续看上面几个步骤**

1、什么时候 bean 被放入 3 级缓存?

早期的 bean 被放入 3 级缓存

2、什么时候 bean 会被放入 2 级缓存?

当 beanX 还在创建的过程中,此时被加入当前 beanName 创建列表了,但是这个时候 bean 并没有被创建完毕(bean 被丢到一级缓存才算创建完毕),此时 bean 还是个半成品,这个时候其他 bean 需要用到 beanX,此时会从三级缓存中获取到 beanX,beanX 会从三级缓存中丢到 2 级缓存中。

3、什么时候 bean 会被放入 1 级缓存?

bean 实例化完毕,初始化完毕,属性注入完毕,bean 完全组装完毕之后,才会被丢到 1 级缓存。

4、populateBean 方法是干什么的?

填充属性的,比如注入依赖的对象。

6.3、下面来看 A、B 类 setter 循环依赖的创建过程

1、getSingleton("a", true) 获取 a:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 a

2、将 a 丢到正在创建的 beanName 列表中(Set singletonsCurrentlyInCreation)

3、实例化 a:A a = new A();这个时候 a 对象是早期的 a,属于半成品

4、将早期的 a 丢到三级缓存中(Map > singletonFactories)

5、调用 populateBean 方法,注入依赖的对象,发现 setB 需要注入 b

6、调用 getSingleton("b", true) 获取 b:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 b

7、将 b 丢到正在创建的 beanName 列表中

8、实例化 b:B b = new B();这个时候 b 对象是早期的 b,属于半成品

9、将早期的 b 丢到三级缓存中(Map > singletonFactories)

10、调用 populateBean 方法,注入依赖的对象,发现 setA 需要注入 a

11、调用 getSingleton("a", true) 获取 a:此时 a 会从第 3 级缓存中被移到第 2 级缓存,然后将其返回给 b 使用,此时 a 是个半成品(属性还未填充完毕)

12、b 通过 setA 将 11 中获取的 a 注入到 b 中

13、b 被创建完毕,此时 b 会从第 3 级缓存中被移除,然后被丢到 1 级缓存

14、b 返回给 a,然后 b 被通过 A 类中的 setB 注入给 a

15、a 的 populateBean 执行完毕,即:完成属性填充,到此时 a 已经注入到 b 中了

16、调用a= initializeBean("a", a, mbd)对 a 进行处理,这个内部可能对 a 进行改变,有可能导致 a 和原始的 a 不是同一个对象了

17、调用getSingleton("a", false)获取 a,注意这个时候第二个参数是 false,这个参数为 false 的时候,只会从前 2 级缓存中尝试获取 a,而 a 在步骤 11 中已经被丢到了第 2 级缓存中,所以此时这个可以获取到 a,这个 a 已经被注入给 b 了

18、此时判断注入给 b 的 a 和通过initializeBean方法产生的 a 是否是同一个 a,不是同一个,则弹出异常

从上面的过程中我们可以得到一个非常非常重要的结论

当某个 bean 进入到 2 级缓存的时候,说明这个 bean 的早期对象被其他 bean 注入了,也就是说,这个 bean 还是半成品,还未完全创建好的时候,已经被别人拿去使用了,所以必须要有 3 级缓存,2 级缓存中存放的是早期的被别人使用的对象,如果没有 2 级缓存,是无法判断这个对象在创建的过程中,是否被别人拿去使用了。

3 级缓存是为了解决一个非常重要的问题:早期被别人拿去使用的 bean 和最终成型的 bean 是否是一个 bean,如果不是同一个,则会产生异常,所以以后面试的时候被问到为什么需要用到 3 级缓存的时候,你只需要这么回答就可以了:三级缓存是为了判断循环依赖的时候,早期暴露出去已经被别人使用的 bean 和最终的 bean 是否是同一个 bean,如果不是同一个则弹出异常,如果早期的对象没有被其他 bean 使用,而后期被修改了,不会产生异常,如果没有三级缓存,是无法判断是否有循环依赖,且早期的 bean 被循环依赖中的 bean 使用了。

spring 容器默认是不允许早期暴露给别人的 bean 和最终的 bean 不一致的,但是这个配置可以修改,而修改之后存在很大的分享,所以不要去改,通过下面这个变量控制

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#allowRawInjectionDespiteWrapping

private boolean allowRawInjectionDespiteWrapping = false;

6.4、模拟 BeanCurrentlyInCreationException 异常

来个登录接口 ILogin

package com.javacode2018.cycledependency.demo2;

//登录接口
public interface ILogin {
}

来 2 个实现类

LoginA

这个上面加上@Component注解,且内部需要注入X

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class LoginA implements ILogin {
    @Autowired
    private X x;

    public X getX() {
        return x;
    }

    public void setX(X x) {
        this.x = x;
    }
}

LoginC,不需要 spring 来管理

package com.javacode2018.cycledependency.demo2;

//代理
public class LoginC implements ILogin {

    private ILogin target;

    public LoginC(ILogin target) {
        this.target = target;
    }
}

X 类,有@Component,且需要注入 Ilogin 对象,这个地方会注入 LoginA,此时 LoginA 和 X 会参数循环依赖

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class X {

    @Autowired
    private ILogin login;

    public ILogin getLogin() {
        return login;
    }

    public void setLogin(ILogin login) {
        this.login = login;
    }

}

添加一个 BeanPostProcessor 类,实现postProcessAfterInitialization方法,这个方法发现 bean 是 loginA 的时候,将其包装为 LoginC 返回,这个方法会在 bean 创建的过程中调用initializeBean时候被调用

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("loginA")) {
            //loginA实现了ILogin
            return new LoginC((ILogin) bean);
        } else {
            return bean;
        }
    }
}

spring 配置类

package com.javacode2018.cycledependency.demo2;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.refresh();
        context.close();
    }
}

运行输出,产生了BeanCurrentlyInCreationException异常,是因为注入给 x 的是 LoginA 这个类的对象,而最后容器中 beanname:loginA 对应的是 LoginC 了,导致注入给别人的对象和最终的对象不一致了,产生了异常。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'loginA': Bean with name 'loginA' has been injected into other beans [x] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

7、案例:若只使用 2 级缓存会产生什么后果?

下面来个案例,通过在源码中设置断点的方式,来模拟二级缓存产生的后果。

添加 A 类

我们希望 loginA 在 A 类之前被创建好,所以这里用到了@DependsOn 注解。

package com.javacode2018.cycledependency.demo3;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn("loginA"//类A依赖于loginA,但是又不想通过属性注入的方式强依赖
public class A {

}

接口 ILogin

package com.javacode2018.cycledependency.demo3;

//登录接口
public interface ILogin {
}

来 2 个实现类,LoginA 需要 spring 管理,LoginC 不需要 spring 管理。

LoginA

package com.javacode2018.cycledependency.demo3;

import org.springframework.stereotype.Component;

@Component
public class LoginA implements ILogin {

}

LoginC

package com.javacode2018.cycledependency.demo3;

//代理
public class LoginC implements ILogin {

    private ILogin target;

    public LoginC(ILogin target) {
        this.target = target;
    }
}

MyBeanPostProcessor

负责将 loginA 包装为 LoginC

package com.javacode2018.cycledependency.demo3;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("loginA")) {
            //loginA实现了ILogin
            return new LoginC((ILogin) bean);
        } else {
            return bean;
        }
    }
}

启动类 MainConfig

package com.javacode2018.cycledependency.demo3;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.refresh();
        context.close();
    }
}

下面模拟只使用二级缓存的情况

在 bean 被放到三级缓存之后,下面的一行代码处设置断点,操作如下

会弹出一个框,然后填入下面配置,这个配置表示满足条件的时候,这个断点才会起效

debug 方式运行程序

走到了这个断点的位置,此时 loginA 已经被放到第 3 级缓存中,此时如果我们调用this.getSingleton(beanName,true),loginA 会从第 3 级缓存移到第 3 级,这个时候就相当于只有 2 级缓存了,操作如下

点击下面的按钮,会弹出一个窗口,可以在窗口中执行代码,执行this.getSingleton(beanName,true),即将loginA从三级缓存放到 2 级缓存,这样相当于没有 3 级缓存了。

运行结果,最终也产生了BeanCurrentlyInCreationException异常,实际上这个程序并没有出现循环依赖的情况,但是如果只用了二级缓存,也出现了早期被暴露的 bean 和最终的 bean 不一致的问题所参数的异常。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException

这个程序如果不进行干预,直接运行,是可以正常运行的,只有在 3 级缓存的情况才可以正常运行。

8、总结

今天的内容有点多,大家慢慢消化,有问题欢迎留言!

9、案例源码

git地址:
https://gitee.com/javacode2018/spring-series
本文案例对应源码:
    spring-series\lesson-009-cycledependency

大家 star 一下,所有系列代码都会在这个里面,还有所有原创文章的连接也在里面,方便查阅!!!

10、Spring 系列

  1. Spring 系列第 1 篇:为何要学 spring?
  2. Spring 系列第 2 篇:控制反转(IoC)与依赖注入(DI)
  3. Spring 系列第 3 篇:Spring 容器基本使用及原理
  4. Spring 系列第 4 篇:xml 中 bean 定义详解(-)
  5. Spring 系列第 5 篇:创建 bean 实例这些方式你们都知道?
  6. Spring 系列第 6 篇:玩转 bean scope,避免跳坑里!
  7. Spring 系列第 7 篇:依赖注入之手动注入
  8. Spring 系列第 8 篇:自动注入(autowire)详解,高手在于坚持
  9. Spring 系列第 9 篇:depend-on 到底是干什么的?
  10. Spring 系列第 10 篇:primary 可以解决什么问题?
  11. Spring 系列第 11 篇:bean 中的 autowire-candidate 又是干什么的?
  12. Spring 系列第 12 篇:lazy-init:bean 延迟初始化
  13. Spring 系列第 13 篇:使用继承简化 bean 配置(abstract & parent)
  14. Spring 系列第 14 篇:lookup-method 和 replaced-method 比较陌生,怎么玩的?
  15. Spring 系列第 15 篇:代理详解(Java 动态代理&cglib 代理)?
  16. Spring 系列第 16 篇:深入理解 java 注解及 spring 对注解的增强(预备知识)
  17. Spring 系列第 17 篇:@Configration 和@Bean 注解详解(bean 批量注册)
  18. Spring 系列第 18 篇:@ComponentScan、@ComponentScans 详解(bean 批量注册)
  19. Spring 系列第 18 篇:@import 详解(bean 批量注册)
  20. Spring 系列第 20 篇:@Conditional 通过条件来控制 bean 的注册
  21. Spring 系列第 21 篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
  22. Spring 系列第 22 篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
  23. Spring 系列第 23 篇:Bean 生命周期详解
  24. Spring 系列第 24 篇:父子容器详解
  25. Spring 系列第 25 篇:@Value【用法、数据来源、动态刷新】
  26. Spring 系列第 26 篇:国际化详解
  27. Spring 系列第 27 篇:spring 事件机制详解
  28. Spring 系列第 28 篇:Bean 循环依赖详解
  29. Spring 系列第 29 篇:BeanFactory 扩展(BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor)
  30. Spring 系列第 30 篇:jdk 动态代理和 cglib 代理
  31. Spring 系列第 31 篇:aop 概念详解
  32. Spring 系列第 32 篇:AOP 核心源码、原理详解
  33. Spring 系列第 33 篇:ProxyFactoryBean 创建 AOP 代理
  34. Spring 系列第 34 篇:@Aspect 中@Pointcut 12 种用法
  35. Spring 系列第 35 篇:@Aspect 中 5 中通知详解
  36. Spring 系列第 36 篇:@EnableAspectJAutoProxy、@Aspect 中通知顺序详解
  37. Spring 系列第 37 篇:@EnableAsync & @Async 实现方法异步调用
  38. Spring 系列第 38 篇:@Scheduled & @EnableScheduling 定时器详解
  39. Spring 系列第 39 篇:强大的 Spel 表达式
  40. Spring 系列第 40 篇:缓存使用(@EnableCaching、@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig)
  41. Spring 系列第 41 篇:@EnableCaching 集成 redis 缓存
  42. Spring 系列第 42 篇:玩转 JdbcTemplate
  43. Spring 系列第 43 篇:spring 中编程式事务怎么用的?
  44. Spring 系列第 44 篇:详解 spring 声明式事务(@Transactional)
  45. Spring 系列第 45 篇:带你吃透 Spring 事务 7 种传播行为
  46. Spring 系列第 46 篇:Spring 如何管理多数据源事务?
  47. Spring 系列第 47 篇:spring 编程式事务源码解析
  48. Spring 系列第 48 篇:@Transaction 事务源码解析
  49. Spring 系列第 49 篇:通过 Spring 事务实现 MQ 中的事务消息
  50. Spring 系列第 50 篇:spring 事务拦截器顺序如何控制?
  51. Spring 系列第 51 篇:导致 Spring 事务失效常见的几种情况
  52. Spring 系列第 52 篇:Spring 实现数据库读写分离
  53. Spring 系列第 53 篇:Spring 集成 MyBatis
  54. Spring 系列第 54 篇:集成 junit
  55. Spring 系列第 55 篇:spring 上下文生命周期

11、更多好文章

  1. Java 高并发系列(共 34 篇)
  2. MySql 高手系列(共 27 篇)
  3. Maven 高手系列(共 10 篇)
  4. Mybatis 系列(共 12 篇)
  5. 聊聊 db 和缓存一致性常见的实现方式
  6. 接口幂等性这么重要,它是什么?怎么实现?
  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报