登录 |  注册
首页 >  面试合集 >  Java大厂面经-复习笔记-更新中 >  Spring的getBean流程之多级缓存解决循环依赖

Spring的getBean流程之多级缓存解决循环依赖

面试:二级缓存能解决循环依赖吗?

都知道Spring通过三级缓存来解决循环依赖的问题。但是是不是必须三级缓存才能解决,二级缓存不能解决吗?

要分析是不是可以去掉其中一级缓存,就先过一遍Spring是如何通过三级缓存来解决循环依赖的。

循环依赖

所谓的循环依赖,就是两个或则两个以上的bean互相依赖对方,最终形成闭环。比如“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;类似以下代码:

public class A {
    private B b;
}
public class B {
    private A a;
}

常规情况下,会出现以下情况:

通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。

A对象需要注入B对象,发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后,会放入对象缓存里)。

通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。

B对象需要注入A对象,发现对象池里还没有A对象。

创建A对象,循环以上步骤。

三级缓存

Spring解决循环依赖的核心思想在于提前曝光:

  • 通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。

  • A对象需要注入B对象,发现缓存里还没有B对象,将半成品对象A放入半成品缓存。

  • 通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。

  • B对象需要注入A对象,从半成品缓存里取到半成品对象A。

  • B对象继续注入其他属性和初始化,之后将完成品B对象放入完成品缓存。

  • A对象继续注入属性,从完成品缓存中取到完成品B对象并注入。

  • A对象继续注入其他属性和初始化,之后将完成品A对象放入完成品缓存。

其中缓存有三级:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
缓存说明
singletonObjects第一级缓存,存放可用的成品Bean
earlySingletonObjects第二级缓存,存放半成品的Bean半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖。
singletonFactories第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。

要了解原理,最好的方法就是阅读源码,从创建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。

先上图,getBean流程图:

getBean.jpg

1. 在构造Bean对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// 是否提前曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
……

2. 提前曝光的对象被放入Map<String, ObjectFactory<?>> singletonFactories缓存中,这里并不是直接将Bean放入缓存,而是包装成ObjectFactory对象再放入。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 一级缓存
if (!this.singletonObjects.containsKey(beanName)) {
// 三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 二级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
}
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}

3. 为什么要包装一层ObjectFactory对象?


如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:


不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。


不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?

Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));:

为了防止对象在后面的初始化(init)时重复代理,在创建代理时,earlyProxyReferences缓存会记录已代理的对象。

4. 注入属性和初始化

提前曝光之后:

通过populateBean方法注入属性,在注入其他Bean对象时,会先去缓存里取,如果缓存没有,就创建该对象并注入。

通过initializeBean方法初始化对象,包含创建代理。

5. 放入已完成创建的单例缓存

在经历了以下步骤之后,最终通过addSingleton方法将最终生成的可用的Bean放入到单例缓存里。

  • AbstractBeanFactory.doGetBean ->

  • DefaultSingletonBeanRegistry.getSingleton ->

  • AbstractAutowireCapableBeanFactory.createBean ->

  • AbstractAutowireCapableBeanFactory.doCreateBean ->

  • DefaultSingletonBeanRegistry.addSingleton

二级缓存

上面第三步《为什么要包装一层ObjectFactory对象?》里讲到有两种选择:

不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。

不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

Sping选择了第二种,如果是第一种,就会有以下不同的处理逻辑:

在提前曝光半成品时,直接执行getEarlyBeanReference创建到代理,并放入到缓存earlySingletonObjects中。

有了上一步,那就不需要通过ObjectFactory来延迟执行getEarlyBeanReference,也就不需要singletonFactories这一级缓存。

这种处理方式可行吗?

这里做个试验,对AbstractAutowireCapableBeanFactory做个小改造,在放入三级缓存之后立刻取出并放入二级缓存,这样三级缓存的作用就完全被忽略掉,就相当于只有二级缓存。

测试结果是可以的,并且从源码上分析可以得出两种方式性能是一样的,并不会影响到Sping启动速度。那为什么Sping不选择二级缓存方式,而是要额外加一层缓存?

如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

上一篇: Spring生命周期之自定义orm框架对接Spring
下一篇: 说说ThreadLocal 你是怎么用的?
推荐文章
  • 架构师在进行系统设计时,遵循一套复杂且综合的方法论,主要包括以下核心步骤:需求分析:理解并明确业务需求是架构设计的第一步。架构师需要与各利益相关者深入沟通,收集和分析业务需求、性能需求、安全性需求、扩展性需求等。领域建模:基于需求分析,构建抽象的业务模型或数据模型,明确系统的边界、核心实体及其关系。
  • 根据阿里交易型业务的特点,以及在双十一这样业内罕有的需求推动下,我们在官方的MySQL基础上增加了非常多实用的功能、性能补丁。而在使用MySQL的过程中,数据一致性是绕不开的话题之一。本文主要从阿里巴巴“去IOE”的后时代讲起,向大家简单介绍下我们过去几年在MySQL数据一致性上的努力和实践,以及目
  • 随着深度学习在图像、语言、广告点击率预估等各个领域不断发展,很多团队开始探索深度学习技术在业务层面的实践与应用。而在广告CTR预估方面,新模型也是层出不穷:WideandDeep、DeepCrossNetwork、DeepFM、xDeepFM,美团很多篇深度学习博客也做了详细的介绍。但是,当
  • 1.背景搜索优化问题,是个典型的AI应用问题,而AI应用问题首先是个系统问题。经历近10年的技术积累和沉淀,美团搜索系统架构从传统检索引擎升级转变为AI搜索引擎。当前,美团搜索整体架构主要由搜索数据平台、在线检索框架及云搜平台、在线AI服务及实验平台三大体系构成。在AI服务及实验平台中,模型训练平台
  • 行业算法版简介OpenSearch-行业算法版是基于阿里巴巴自主研发的大规模分布式搜索引擎搭建的一站式智能搜索业务开发平台,目前为包括淘宝、天猫在内的阿里集团核心业务提供搜索服务支持。通过内置各行业的查询语义理解、机器学习排序算法等能力,提供充分开放的引擎能力,助力开发者快速搭建智能搜索服务。Ope
  • 一.概述我们在考虑MySQL数据库的高可用的架构时,主要要考虑如下几方面:如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中断。用作备份、只读副本等功能的非主节点的数据应该和主节点的数据实时或者最终保持一致。当业务发生数据库切换时
学习大纲