乐趣区

关于springboot:Spring三级缓存和循环依赖

1. 循环依赖

什么是依赖注入?假如有两个类 A 和 B,A 在实例化的时候须要 B 的实例,而 B 在实例化时又须要 A 的实例,在类的实例化过程就陷入死循环。这也就是传统逻辑上的,“到底是先有鸡,还是先有蛋”的问题?
上面举一个例子, 定义了两个类 Type 和 Org:

// Org.java
@Data
@Component
public class Org {
    private final Role role;

    public Org(Role role) {this.role = role;}
}

// Role.java
@Data
@Component
public class Role {
    private final Org org;

    public Role(Org org) {this.org = org;}
}

这是 spring 中典型的结构器注入形式,其实也代表了一般非 spring bean 之间,相互依赖时的实例化过程,但后果在运行的时候间接报循环依赖的谬误:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   demoController (field private pers.kerry.exercise.springexercise.pojo.Org pers.kerry.exercise.springexercise.controller.DemoController.org)
┌─────┐
|  org defined in file [/Users/kerry/code/idea/spring-exercise/target/classes/pers/kerry/exercise/springexercise/pojo/Org.class]
↑     ↓
|  role defined in file [/Users/kerry/code/idea/spring-exercise/target/classes/pers/kerry/exercise/springexercise/pojo/Role.class]
└─────┘

而如果咱们改一下代码,把结构器注入形式改成基于属性的注入(@Autowired、@Resouce),奇怪的是不报错了,而且相互依赖的两个 bean 都实例化胜利了。阐明 spring 框架有解决循环依赖的问题,咱们理解 spring 解决循环依赖的过程,其实有助于进一步理解 spring 中 bean 的流动过程。

2. 三级缓存

咱们在之前介绍 Bean 的生命周期时说过,spring 中 bean 的实例化过程,并非只是调用构造方法。除去 spring 框架自身提供的一些钩子或扩大办法,简略分成上面三个外围办法:

Spring 在创立 Bean 的过程中分为三步

  1. 实例化,对应办法:AbstractAutowireCapableBeanFactory 中的 createBeanInstance 办法,简略了解就是 new 了一个对象。
  2. 属性注入,对应办法:AbstractAutowireCapableBeanFactory 的 populateBean 办法,为实例化中 new 进去的对象填充属性和注入依赖。
  3. 初始化,对应办法:AbstractAutowireCapableBeanFactory 的 initializeBean,执行 aware 接口中的办法,初始化办法,实现 AOP 代理。

从单例 Bean 的初始化来看,次要可能产生循环依赖的环节就在第二步 populate。值得注意的是, 基于构造方法注入 的形式,其实是将第一步和第二步同时进行,因而马上就抛出谬误。而 spring 通过 基于属性注入 的形式,是否有其余非凡的解决呢,咱们这时候就要提到 spring 的三级缓存:

  • 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);
缓存 阐明
singletonObjects 第一级缓存,寄存可用的齐全初始化,成品的 Bean。
earlySingletonObjects 第二级缓存,寄存半成品的 Bean,半成品的 Bean 是已创建对象,然而未注入属性和初始化。用以解决循环依赖。
singletonFactories 第三级缓存,存的是 Bean 工厂对象,用来生成半成品的 Bean 并放入到二级缓存中。用以解决循环依赖。如果 Bean 存在 AOP 的话,返回的是 AOP 的代理对象。

3. 外围办法:getSingleton

咱们在获取 bean 实例的时候,其实是先从三级缓存中获取,getBean 办法的逻辑如下:

Object sharedInstance = getSingleton(beanName);

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 查问缓存中是否有创立好的单例
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果缓存不存在,判断是否正在创立中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加锁避免并发
        synchronized (this.singletonObjects) {
            // 从 earlySingletonObjects 中查问是否有 early 缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            // early 缓存也不存在,且容许 early 援用
            if (singletonObject == null && allowEarlyReference) {
                // 从单例工厂 Map 里查问 beanName
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // singletonFactory 存在,则调用 getObject 办法拿到单例对象
                    singletonObject = singletonFactory.getObject();
                    // 将单例对象增加到 early 缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 移除单例工厂中对应的 singletonFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
  1. 只针对单例的 bean,多例的前面探讨
  2. 默认的 singletonObjects 缓存不存在要 get 的 beanName 时,判断 beanName 是否正在创立中
  3. 从 early 缓存 earlySingletonObjects 中再查问,early 缓存是用来缓存已实例化但未组装实现的 bean
  4. 如果 early 缓存也不存在,从 singletonFactories 中查找是否有 beanName 对应的 ObjectFactory 对象工厂
  5. 如果对象工厂存在,则调用 getObject 办法拿到 bean 对象
  6. 将 bean 对象退出 early 缓存,并移除 singletonFactories 的对象工厂

这是 getBean的逻辑,三级缓存中一级一级地找匹配的 Bean,直到最初一级缓存,通过匹配 beanName 的 ObjectFactory 来获取 Bean。那么 singletonFactories 何时放入了能够通过 getObject 取得 bean 对象的 ObjectFactory 呢?

4. 外围办法:doCreateBean

Bean 的实例化,理论执行的源码是 AbstractAutowireCapableBeanFactory 类的 doCreateBean 办法:

 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        // 1、创立一个对 bean 原始对象的包装对象 -BeanWrapper,执行 createBeanInstance,即构造方法或工厂办法,给 BeanWrapper 赋值
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }

        Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}
        // 2、容许其余批改 beanDefinition,如应用 Annotation 加强 Bean 定义等,这通过类 MergedBeanDefinitionPostProcessor 来实现
        synchronized(mbd.postProcessingLock) {if (!mbd.postProcessed) {
                try {this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                } catch (Throwable var17) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
                }

                mbd.postProcessed = true;
            }
        }
        // 3、将以后 bean 的 ObjetFactory 放入 singletonFactories 中,boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
        if (earlySingletonExposure) {if (this.logger.isTraceEnabled()) {this.logger.trace("Eagerly caching bean'" + beanName + "'to allow for resolving potential circular references");
            }

            this.addSingletonFactory(beanName, () -> {return this.getEarlyBeanReference(beanName, mbd, bean);
            });
        }

        Object exposedObject = bean;
        // 4、执行 populateBean,设置属性值
        // 5、执行 initializeBean,调用 Bean 的初始化办法
        try {this.populateBean(beanName, mbd, instanceWrapper);
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable var18) {if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {throw (BeanCreationException)var18;
            }

            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
        }
        // 6、再次解决循环依赖问题
        if (earlySingletonExposure) {Object earlySingletonReference = this.getSingleton(beanName, false);
            if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {String[] dependentBeans = this.getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
                    String[] var12 = dependentBeans;
                    int var13 = dependentBeans.length;

                    for(int var14 = 0; var14 < var13; ++var14) {String dependentBean = var12[var14];
                        if (!this.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'getBeanNamesForType'with the'allowEagerInit'flag turned off, for example.");
                    }
                }
            }
        }
        // 7、注册 bean 的销毁回调办法,在 beanFactory 中注册销毁告诉,以便在容器销毁时,可能做一些后续解决工作
        try {this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
            return exposedObject;
        } catch (BeanDefinitionValidationException var16) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
        }
    }

BeanWrapper

BeanWrapper 接口,作为 spring 外部的一个外围接口,正如其名,它是 bean 的包裹类,即在外部中将会保留该 bean 的实例,提供其它一些扩大性能。同时,BeanWrapper 接口还继承了 PropertyAccessor, propertyEditorRegistry, TypeConverter、ConfigurablePropertyAccessor 接口,所以它还提供了拜访 bean 的属性值、属性编辑器注册、类型转换等性能。

咱们回顾一下 bean 的实例化过程:

  1. ResourceLoader 加载配置信息
  2. BeanDefinitionReader 读取并解析 <bean> 标签,并将 <bean> 标签的属性转换为 BeanDefinition 对应的属性,并注册到 BeanDefinitionRegistry 注册表中。
  3. 容器扫描 BeanDefinitionRegistry 注册表,通过反射机制获取 BeanFactoryPostProcessor 类型的工厂后处理器,并用这个工厂后处理器对 BeanDefinition 进行加工。
  4. 依据解决过的 BeanDefinition,实例化 bean。而后 BeanWrapper 联合 BeanDefinitionRegistry 和 PropertyEditorRegistry 对 Bean 的属性赋值。

4. 思考和总结

4.1. 问题:多例的循环依赖能够解决吗

单例 bean 的循环援用是因为每个对象都是固定的,只是提前裸露对象的援用,最终这个援用对应的对象是创立实现的。然而多例的状况下,每次 getBean 都会创立一个新的对象,那么应该援用哪一个对象呢,这自身就曾经是矛盾的了。多实例 Bean 是每次创立都会调用 doGetBean 办法,基本没有应用一二三级缓存,必定不能解决循环依赖。因此 spring 中对于多例之间互相援用是会提醒谬误的。

Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可见 spring 会认为多例之间的循环援用是无奈解决的。

4.2. 问题:结构器、setter 注入形式的循环依赖能够解决吗

这里还是拿 A 和 B 两个 Bean 举例说明:

注入形式 是否解决循环依赖
均采纳 setter 办法注入
均采纳结构器注入
A 中注入 B 的形式为 setter 办法,B 中注入 A 的形式为结构器
B 中注入 A 的形式为 setter 办法,A 中注入 B 的形式为结构器

4.3. 问题:为什么是三级缓存,二级不行吗?

咱们再整顿一下 spring 解决循环依赖的过程:一级缓存 singletonObject 存储成品的 Bean,二级缓存 earlySingletonObject 存储半成品的 Bean,当呈现循环依赖时能够先注入 earlySingletonObject 中的 Bean 实例。那三级缓存 singletonFactory 存在的意义何在?

singletonFactory 存储的对象工厂是 ObjectFactory,这是一个函数式接口,惟一形象办法是 getObject。在 doCreateBean 办法的第三步 addSingletonFactory,往 singletonFactory 增加 ObjectFactory 的匿名外部类中,返回对象的办法是 getEarlyBeanReference。

this.addSingletonFactory(beanName, () -> {return this.getEarlyBeanReference(beanName, mbd, bean);
            });

咱们再看看 getEarlyBeanReference 的办法实现:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {return exposedObject;}
            }
        }
    }
    return exposedObject;
}

这里也设置了一个 InstantiationAwareBeanPostProcessor 后置处理器的扩大点,容许在对象返回之前批改甚至替换 bean,总的来说,这是某一 AOP 办法的实现步骤。因而如果存在 AOP 的定义,singletonFactory 返回的不是原始的 Bean 实例,而是实现 AOP 办法的代理类。

那么如果在 doCreateBean 办法中,间接生成 Bean 基于 AOP 的代理对象,将代理对象存入二级缓存 earlySingleton,是不是还是能够不须要三级缓存 singletonFactory 呢?

如果这么做了,就把 AOP 中创立代理对象的机会提前了,不论是否产生循环依赖,都在 doCreateBean 办法中实现了 AOP 的代理。不仅没有必要,而且违反了 Spring 在联合 AOP 跟 Bean 的生命周期的设计!Spring 联合 AOP 跟 Bean 的生命周期自身就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来实现的,在这个后置解决的 postProcessAfterInitialization 办法中对初始化后的 Bean 实现 AOP 代理。如果呈现了循环依赖,那没有方法,只有给 Bean 先创立代理,然而没有呈现循环依赖的状况下,设计之初就是让 Bean 在生命周期的最初一步实现代理而不是在实例化后就立马实现代理。

因而在三级缓存的架构中,earlySingletonObject 和 singletonFactory 的意义在于:

  • 三级缓存 singletonFactory:裸露 ObjectFactory 目标是为了实现 AOP 代理。对象工厂分明如何创建对象的 AOP 代理,然而不会立马创立,而是到适合的机会进行 AOP 代理对象的创立。
  • 二级缓存 earlySingletonObject:存在的目标之一是保障对象只有一次 AOP 代理。当调用三级缓存的 getObject()办法返回的对象会存入二级缓存,这样,当接下来的依赖者调用的时候,会先判断二级缓存是否有指标对象,如果存在间接返回。

4.4. 总结

咱们再回顾 spring 中循环依赖的解决流程,网上看到一个流程图很能清晰的阐明其中过程。

Spring 通过三级缓存解决了循环依赖。一级缓存为单例池,二级缓存为晚期曝光对象,三级缓存为晚期曝光对象工厂。当 A、B 两类产生循环援用,在 A 实例化之后,将本人提前曝光 (即退出三级缓存),如果 A 初始 AOP 代理,该工厂对象返回的是被代理的对象,若未被代理,返回对象自身。当 A 进行属性注入时,通过之前实例化步骤,此时轮到 B 属性注入,调用 getBean(a) 获取 A 对象,因为 A 解决正在创立汇合中,此时也发了循环依赖,所以能够从三级缓存获取对象工厂(如果 A 被 AOP 代理,此时返回就是代理对象),并把对象放到二级缓存中,这样保障 A 只通过一次 AOP 代理。接下来,B 走完 Spring 生命周期流程,并放入单例池中。当 B 创立完后,会将 B 注入 A,A 走完 Spring 生命周期流程。到此,循环依赖完结。

退出移动版