Spring如何解决循环依赖

1.背景说明

● 循环依赖是什么?

有一个Bean为AService,另一个Bean为BService

AService里面引用了属性BServiceBService里面又引用了属性AService,这样在加载Bean的时候,造成对彼此的循环依赖。

这样会导致Bean无法加载。

● 解决循环依赖的思路

说明:这里只解决单例bean的循环依赖问题。

核心思路:只需要增加一个 缓存 来存放 原始对象 即可,Spring解决此类循环依赖主要是靠三级缓存在【属性注入】阶段产生作用。

具体做法:在创建 AService 时,实例化后将 原始对象 存放到缓存中(提早暴露),然后依赖注入时发现需要 BService,便会去创建 BService,实例化后同样将 原始对象 存放到缓存中,然后依赖注入时发现需要 AService 便会从缓存中取出并注入,这样 BService 就完成了创建,随后 AService 也就能完成属性注入,最后也完成创建。这样就打破了环形调用,避免循环依赖问题。

2.三级缓存

● 一级缓存:SingletonObjects,缓存的是已经实例化、属性注入、初始化后的bean对象。

● 二级缓存:earlySingletonObjects,缓存的是实例化后,但未属性注入、初始化的Bean对象,用于提前暴露Bean

● 三级缓存:singletonFactories,缓存的是一个ObjectFactory,主要作用是生成原始对象进行AOP操作后的代理对象

3.源码分析

3.1 入口

AbstractApplicationContext.java的 这个方法进去:

// 初始化 非懒加载的单例 bean

beanFactory.preInstantiateSingletons();

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();

        // Instantiate all remaining (non-lazy-init) singletons.
        beanFactory.preInstantiateSingletons();

进入到 DefaultListableBeanFactory的preInstantiateSingletons方法

        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged(
                                    (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

3.2 getBean方法

DefaultListableBeanFactory中,进入getBean

这里获取的是这种类型的bean:

(1)非抽象的BeanDefinition

(2)单例的BeanDefinition

(3)非懒加载的BeanDefinition

else {
    getBean(beanName);
}

3.3 doGetBean方法

实际干活的是 AbstractBeanFactory中的 doGetBean

    protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException

如果不存在已有的单例对象,且beanDefination是单例,那么进入 createBean方法

                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            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);
                }

3.4 createBean方法

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

createBean方法 返回一个Object类型

核心逻辑:

try {
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    if (logger.isTraceEnabled()) {
        logger.trace("Finished creating instance of bean '" + beanName + "'");
    }
    return beanInstance;
}

3.5 doCreateBean方法

3.5.1 实例化对象

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

● 首先,实例化一个对象:

instanceWrapper = createBeanInstance(beanName, mbd, args)

3.5.2 加入三级缓存

接着,把创建对象的lambda表达式放到三级缓存。

这里是为了避免后期的循环依赖,所以在bean初始化完成前将创建实例的ObjectFactory加入工厂。

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));
}

说明:

(1)getEarlyBeanReference(beanName, mbd, bean) 方法 会返回遍历工厂内的所有后置处理器,去处理原始的bean,并返回最终经过层层包装后的 代理对象

(2)这里并不会马上执行getEarlyBeanReference方法,有循环依赖的时候才执行。

3.5.3 属性赋值

场景:AService 中要注入属性BService

进入populateBean方法

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) 

核心方法:

if (pvs != null) {
 applyPropertyValues(beanName, mbd, bw, pvs);
}

applyPropertyValues中的核心方法:

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

点进去:

if (value instanceof RuntimeBeanReference) {
   RuntimeBeanReference ref = (RuntimeBeanReference) value;
   return resolveReference(argName, ref);
}

核心方法:resolveReference(argName, ref),点进去:

else {
    String resolvedName;
    if (beanType != null) {
      NamedBeanHolder<?> namedBean = 
      this.beanFactory.resolveNamedBean(beanType);
      bean = namedBean.getBeanInstance();
      resolvedName = namedBean.getBeanName();
     } else {
        resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
        bean = this.beanFactory.getBean(resolvedName);
     }
     this.beanFactory.registerDependentBean(resolvedName, this.beanName);
}

注意:这里开始无限套娃之旅,即:

(1)这里有一句:bean = this.beanFactory.getBean(resolvedName),在该场景下,作用是获取属性Bservice

(2)然后 B在创建过程中,最终又会执行这句bean = this.beanFactory.getBean(resolvedName),去获取属性Aservice

3.6 循环依赖的闭环

● 闭环形成的关键点

当 Aservice --> Bservice --> Aservice 这个过程,进行到第二次 获取 Aservice 的时候,会通过 getSingleton 方法

获取Aservice,即:

可以看到在第三级缓存中调用了 singletonFactories.get(beanName)

按照上文所说,会触发执行有 AOP 操作,返回代理对象。如果没有AOP操作的话,返回原始对象。

此外,还会把Aservice上移到二级缓存中、并删除三级缓存的数据。

● Bservice创建完成

如此一来,容器在创建Bservice的时候,就可以拿到 Aservice,完成BService的创建,并将Bservice加入一级缓存。

● Aservice创建完成

当Bservice创建完成后,就可以在Aservice中完成属性Bservice的注入,从而完成AService的创建,将Aservice加入一级缓存。

4.总结

梳理整个过程如下:

● 首先会获取 AService 对应的 Bean 对象。

先是调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,有就直接返回了。(显然这里没有)

然后调用 doGetBean() 中的第二个 getSingleton() 方法来执行 doCreateBean() 方法。

先进行实例化操作(也就是利用构造函数实例化),此时实例化后生成的是原始对象。

将原始对象通过 lambda表达式 进行封装成 ObjectFactory 对象,通过 addSingletonFactory 加入三级缓存中。

然后再进行属性注入,此时发现需要注入 BService 的 Bean,会通过 doGetBean() 去获取 BService 对应的 Bean。

● 获取BService 对应的 Bean。

同样调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,显然这里也是不会有 BService 的 Bean 的。

然后只能调用 doGetBean() 中的第二个 getSingleton() 方法来执行 doCreateBean() 方法来创建一个 BService 的 Bean。

同样地先进行实例化操作,生成原始对象后封装成 ObjectFactory 对象放入三级缓存中。

然后进行属性注入,此时发现需要注入 AService 的 Bean。

● 第二次获取AService 对应的 Bean

此时调用调用 doGetBean() 中的第一个 getSingleton(beanName) 查找是否有 AService 的 Bean。此时会触发三级缓存,也就是调用 singletonFactories.get(beanName)

因为三级缓存中有 AService 的原始对象封装的 ObjectFactory 对象,所以可以获取到的代理对象或原始对象,并且上移到二级缓存中,提前暴露给 BService 调用。

所以 BService 可以完成属性注入,然后进行初始化后,将 Bean 放入一级缓存,这样 AService 也可以完成创建。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容