zoukankan      html  css  js  c++  java
  • 彻底讲透Spring三级缓存,原理源码深度剖析!

    一、前言
    循环依赖:就是N个类循环(嵌套)引用。
    通俗的讲就是N个Bean互相引用对方,最终形成闭环。在日常的开发中,我们都会碰到类似如下的代码

    @Service
    public class AServiceImpl implements AService {
    @Autowired
    private BService bService;
    ...
    }
    @Service
    public class BServiceImpl implements BService {
    @Autowired
    private AService aService;
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这其实就是Spring环境下典型的循环依赖场景。但是很显然,这种循环依赖场景,Spring已经完美的帮我们解决和规避了问题。那么Spring是如何实现的呢?

    二、Spring循环依赖分析
    2.1 循环依赖场景
    在Spring环境中,因为我们的Bean的实例化、初始化都是交给了容器,因此它的循环依赖主要表现为下面三种场景。

    2.1.1 构造器注入循环依赖
    @Service
    public class A {
    public A(B b) {
    }
    }
    @Service
    public class B {
    public B(A a) {
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    构造器注入构成的循环依赖,此种循环依赖方式是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。这也是构造器注入的最大劣势(它有很多独特的优势,请小伙伴自行发掘)

    根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决~~~

    2.1.2 field属性注入(setter方法注入)循环依赖
    这种方式是我们最最最最为常用的依赖注入方式(所以猜都能猜到它肯定不会有问题啦):

    @Service
    public class A {
    @Autowired
    private B b;
    }

    @Service
    public class B {
    @Autowired
    private A a;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    2.1.3 prototype field属性注入循环依赖
    prototype在平时使用情况较少,但是也并不是不会使用到,因此此种方式也需要引起重视。

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Service
    public class A {
    @Autowired
    private B b;
    }

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Service
    public class B {
    @Autowired
    private A a;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    结果:需要注意的是本例中启动时是不会报错的(因为非单例Bean默认不会初始化,而是使用时才会初始化),所以很简单咱们只需要手动getBean()或者在一个单例Bean内@Autowired一下它即可

    // 在单例Bean内注入
    @Autowired
    private A a;
    1
    2
    3
    这样子启动就报错(在一个单例bean中,自动注入一个多例bean实例,肯定报错的)。

    对于Spring循环依赖的情况总结如下:

    不能解决的情况:
    (1) 构造器注入循环依赖
    (2) prototype field属性注入循环依赖
    能解决的情况:
    (1) field属性注入(setter方法注入)循环依赖

    2.2 原理分析
    Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。

    2.2.1 Spring创建Bean的流程
    首先需要了解是Spring它创建Bean的流程,我把它的大致调用栈绘图如下:


    对Bean的创建最为核心三个方法解释如下:

    createBeanInstance:例化,其实也就是调用对象的构造方法实例化对象;
    populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired);
    initializeBean:回到一些形如initMethod、InitializingBean等方法;
    从对单例Bean的初始化可以看出,循环依赖主要发生在第二步(populateBean),也就是field属性注入的处理。

    2.2.2 Spring容器的三级缓存
    在Spring容器的整个声明周期中,单例Bean有且仅有一个对象。这很容易让人想到可以用缓存来加速访问。

    从源码中也可以看出Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至不惜使用了“三级缓存”,这也便是它设计的精妙之处~

    三级缓存其实它更像是Spring容器工厂的内的术语,采用三级缓存模式来解决循环依赖问题,这三级缓存分别指:

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    // 从上至下 分表代表这“三级缓存”
    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); // 三级缓存
    ...

    /** Names of beans that are currently in creation. */
    // 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
    // 它在Bean开始创建时放值,创建完成时会将其移出~
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** Names of beans that have already been created at least once. */
    // 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
    // 至少被创建了一次的 都会放进这里~~~~
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    注:AbstractBeanFactory继承自DefaultSingletonBeanRegistry

    singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用;
    earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖;
    singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖;
    为啥Spring需要设计成三级缓存?

    一级缓存需要的原因,大家应该都已了解,现在简单说下为啥Spring需要设计成三级缓存。

    (1) 为啥需要二级缓存?

    一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。所以,我们就要加一个map,这个map,用来存放那种不完整的bean。也就是需要二级缓存。

    (2) 为啥需要三级缓存?

    怎么理解呢? 以io流举例,我们一开始都是用的原始字节流,然后给别人用的也是字节流,但是,最后,我感觉不方便,我自己悄悄弄了个缓存字符流(类比代理对象),我是方便了,但是,别人用的,还是原始的字节流啊。你bean不是单例吗?不能这么玩吧?所以,这就是二级缓存,不能解决的问题。
    注:为什么不直接将类的代理对象生成,然后放入二级缓存?

    因为类的代理对象必须是在类的实例对象已生成的基础上去生成的,如果中间存在类的代理对象的循环依赖,是无法先生成类的代理对象,然后放入二级缓存。也就是二级缓存只能解决普通实例对象的循环依赖,如果存在代理对象的循环依赖,是无法解决的。

    三级缓存设计:

    @Service
    public class A {
    @Autowired
    private B b;
    }

    @Service
    public class B {
    @Autowired
    private A a;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    还是拿上面这个示例举例,另外类A、B上假如都有切面AOP,那么Spring容器中会存在proxyA、proxyB,并分别自动注入到对方实例的属性上。

    要解决类代理对象的循环依赖这个问题,比如beanA在填充beanB的实例时,查找到最终形态的beanB,即代理后的proxyB。
    怎么做到这点呢?
    加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的proxyB。

    具体是如何实现的,我们可以后面看源码分析下。

    2.2.3 源码分析
    前面提到过,Spring创建bean过程主要是:

    源码分析过程中,主要要注意的方法如下:
    注:主要是分析类第一次被Spring容器创建的流程

    (1) org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class, java.lang.Object[])
    (2) org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
    (3) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)
    (4) org.springframework.beans.factory.support.AbstractBeanFactory#createBean
    (5) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
    (6) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
    (7) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
    (8) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
    (9) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
    1
    2
    3
    4
    5
    6
    7
    8
    9
    下面一起探讨下Spring容器中getBean()方法源码的流程,看其是如何解决循环依赖问题的。

    前面的源码流程已经贴出,跟着一步步看过来即可,

    // Create bean instance.
    if (mbd.isSingleton()) {
    // (1) 首先从一级缓存中获取实例,如果没有,则从ObjectFactory中获取
    sharedInstance = getSingleton(beanName, new ObjectFactory() {
    public Object getObject() throws BeansException {
    try {
    // (2) ObjectFactory中创建bean实例的方法
    return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
    // Explicitly remove instance from singleton cache: It might have been put there
    // eagerly by the creation process, to allow for circular reference resolution.
    // Also remove any beans that received a temporary reference to the bean.
    destroySingleton(beanName);
    throw ex;
    }
    }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ObjectFactory中创建bean实例的方法中的核心方法doCreateBean,也是最重要的一部分,如下所示:

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
    instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
    // 创建Bean对象,并且将对象包裹在BeanWrapper 中
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    //注意:此处是原始对象,这点非常的重要
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
    Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
    if (!mbd.postProcessed) {
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    mbd.postProcessed = true;
    }
    }

    // earlySingletonExposure 用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
    // 对于单例Bean,该变量一般为 true 但你也可以通过属性allowCircularReferences = false来关闭循环引用
    // isSingletonCurrentlyInCreation(beanName) 表示当前bean必须在创建中才行
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
    logger.debug("Eagerly caching bean '" + beanName +
    "' to allow for resolving potential circular references");
    }
    //注释1
    addSingletonFactory(beanName, new ObjectFactory() {
    public Object getObject() throws BeansException {
    return getEarlyBeanReference(beanName, mbd, bean);
    }
    });
    }

    //exposedObject 是最终返回的对象
    Object exposedObject = bean;
    try {
    // 填充属性,解决@Autowired依赖~
    populateBean(beanName, mbd, instanceWrapper);
    // 执行初始化回调方法们
    // 注释2
    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);
    }
    }

    // earlySingletonExposure:如果你的bean允许被早期暴露出去 也就是说可以被循环引用 那这里就会进行检查
    if (earlySingletonExposure) {
    //注意,注意:第二参数为false 表示不会再去三级缓存里查了
    //注释3
    Object earlySingletonReference = getSingleton(beanName, false);
    //注释4
    if (earlySingletonReference != null) {
    if (exposedObject == bean) {
    exposedObject = earlySingletonReference;
    }
    // (3) 注释5
    else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    String[] dependentBeans = getDependentBeans(beanName);
    Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
    for (int i = 0; i < dependentBeans.length; i++) {
    String dependentBean = dependentBeans[i];
    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    actualDependentBeans.add(dependentBean);
    }
    }
    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.");
    }
    }
    }
    }

    // Register bean as disposable.
    registerDisposableBeanIfNecessary(beanName, bean, mbd);

    return exposedObject;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    注释说明:

    注释1:

    上面讲过调用此方法放进一个ObjectFactory,二级缓存会对应删除的
    getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 否则啥都不做;
    也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑;
    比如在getEarlyBeanReference()里可以实现AOP的逻辑,参考自动代理创建器AbstractAutoProxyCreator 实现了这个方法来创建代理对象;
    若不需要执行AOP的逻辑,直接返回Bean;
    注释2

    initializeBean方法会执行BeanPostProcessor接口的两个方法,在这过程中可能会将原始的bean对象给手动代理替换掉(注意不是Spring容器管理的自动代理,正常的AOP都是受Spring容器管理的,如果手动使用ProxyFactory为bean创建代理,那么原始的bean就会被手动创建的代理对象给替换掉,且该手动创建的代理对象不受Spring容器自动管理)
    注释3

    此处非常巧妙的一点,因为上面各式各样的实例化、初始化的后置处理器都执行了, 那么此处得到的earlySingletonReference的引用只有两种结果,一种是null,代表着这个bean不存在被其它bean引用的循环依赖情况,直接返回原始对象即可,一种是该bean的二级缓存,代表着该bean被其它bean引用的循环依赖情况。
    对象的循环依赖和代理对象的循环依赖都是靠三级缓存生成最终对象引用,放入二级缓存(三级缓存主要是解决代理对象的循环依赖)。因为前面通过addSingletonFactory方法暴露了三级缓存的ObjectFactory,参考注释1中getEarlyBeanReference()说明。
    注释4

    这个判断主要是针对代理对象的循环引用场景。
    这个意思是如果经过了initializeBean()后,exposedObject还是没有变,则需要返回拿到的二级缓存earlySingletonReference。因为前面说过,存在循环依赖的场景时,不管普通对象还是代理对象的循环依赖,都是靠三级缓存生成对象并放入二级缓存的,如果exposedObject没有变,返回的就是exposedObject的代理对象的引用。
    注释5

    如果exposedObject变了,则会去检查这个bean的依赖是否已经创建好,如果没有则会抛异常。
    allowRawInjectionDespiteWrapping这个值默认是false
    hasDependentBean:若它有依赖的bean 那就需要继续校验了(若没有依赖的 就放过它~)
    上述有些地方可能比较难理解,结合下面示例就清楚了,比如实现BeanPostProcessor接口,手动为bean通过ProxyFactory创建代理类:

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
    Class<?> clazz = bean.getClass();

    boolean flag = AopUtils.isJdkDynamicProxy(bean) &&
    AnnotationUtils.findAnnotation(clazz, Repository.class) != null &&
    AnnotationUtils.findAnnotation(clazz, DataSourceAdapter.class) != null &&
    Arrays.stream(clazz.getInterfaces()).anyMatch(cls -> cls.getName().startsWith("com.ggj.business"));

    if (flag) {
    ProxyFactory factory = new ProxyFactory();
    factory.setTarget(bean);
    factory.addAdvice(new DataSourceAdapterAdvice(applicationContext));
    return factory.getProxy();
    }
    return bean;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    后面判断exposedObject == bean时,肯定是不相等的,那么就会检查bean的依赖是否已经创建好,如果没有创建好会抛异常,这是因为Spring容器觉得既然你自己手动创建了代理对象,代理对象是原始对象的基础上生成的,那么会检查原始对象的依赖,如果发现依赖的对象还没有创建好,就抛异常,避免使用时报错。

    前面我们已经了解,如果当前bean被其它bean依赖,则会生成二级缓存,二级缓存可能是普通对象,也可能是普通对象的代理对象,代理对象主要是通过三级缓存的ObjectFactory的getEarlyBeanReference()方法生成的引用,然后最终返回exposedObject,exposedObject可能是二级缓存,也可能是原始对象,最后通过org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton放进一级缓存,至此整个Spring的循环依赖基本分析完成。

    三、总结
    (1) 什么是循环依赖,循环依赖的场景有哪些,Spring容器能解决什么样场景的循环依赖;
    (2) 要了解Spring容器bean的创建流程。
    (3) 了解一、二、三级缓存的设计原因以及各级缓存之间的关联;

    三级缓存的是ObjectFactory,当前bean被其它bean依赖时,就会去循环allowEarlyReference=true去查找依赖,并放入二级缓存;

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
    synchronized (this.singletonObjects) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
    ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
    }
    }
    }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    最后再通过addSingleton方法将已经创建并实例化好的bean放入一级缓存。

    protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
    this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
    this.singletonFactories.remove(beanName);
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    (4) 了解当前beanA依赖beanB的几种场景:

    beanB是受Spring容器管理的普通对象;
    beanB是受Spring容器管理的代理对象;
    beanB是被BeanPostProcessor接口替换原始bean对象的新对象;
    参考:
    https://blog.csdn.net/f641385712/article/details/92801300
    https://www.cnblogs.com/grey-wolf/p/13034371.html#_label6


    淡淡的倔强
    关注

    ————————————————
    版权声明:本文为CSDN博主「淡淡的倔强」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u012834750/article/details/108968103

  • 相关阅读:
    python提供的网络接口API和Linux Socket API间的关系探究 liushu
    业务领域建模Domain Modeling liushu
    分析一套源代码的代码规范和风格并讨论如何改进优化代码 liushu
    案例分析:设计模式与代码的结构特性 liushu
    如何提高程序员的键盘使用效率 liushu
    网络相关的命令工具研究报告 liushu
    用例建模Use Case Modeling liushu
    WdatePicker日历控件使用方法
    添加Web服务引用,服务引用,WCF服务开发
    JS大总结
  • 原文地址:https://www.cnblogs.com/hanease/p/14939314.html
Copyright © 2011-2022 走看看