Spring如何解决循环依赖的

1.什么是循环依赖

就是我们有两个服务,A服务,B服务,然后我们在A里注入了B,然后在B里注入了A,这就是循环依赖了,这种情况如果我们不解决的话,那就会出现一个相互依赖注入的死循环。

二 循环依赖的解决方案 - 三级缓存

2.1什么是三级缓存

    /** 一级缓存 单例缓存池 用于保存我们所有的单实例bean */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** 二级缓存 保存半成品bean实例,当对象需要被AOP切面代时,保存代理bean的实例beanProxy*/
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);


    /** 三级缓存 存放ObjectFactory,传入的是匿名内部类,ObjectFactory.getObject() 方法最终会
      调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式。*/
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2.2 三级缓存如何解决循环依赖的问题

前置知识:Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。

2.2.1 重点方法

循环依赖涉及的重点方法是DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

//一个纯bean获取流程,这里不进行创建
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存singletonObjects获取bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 一级缓存没有,判断该bean是否在创建中,通过Set的contains来判断
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 从二级缓存中获取bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 二级缓存没有&&允许提前引用
            if (singletonObject == null && allowEarlyReference) {
                // 从三级缓存中获取lambda表达式
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
               // 如果获取到该lambda表达式,进行回调填充
                if (singletonFactory != null) {
                    // 调用三级缓存的lambda表示获取早期不完整对象
                    singletonObject = singletonFactory.getObject();
                    // 写入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 三级缓存移除该bean的lambda表达式
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}

2.2.5 提前透知下AB存在循环依赖的情况大概是怎样一个加载流程,源码解读放下面

现在来分析一下A B循环依赖的情况

  • A doCreateBean()实例化,由于还未创建,从一级缓存查不到,且不是正在创建,这时候调用bean创建流程,进行实例化,将不完整对象以及BeanDefinition代表的lambda表达式写入三级缓存singletonFactories;

  • A populateBean()进行属性注入时候发现自己需要B对象,但是三级缓存中未发现B,就去创建B同样把创建B bean的lambda表达式放入singletonFactories;

  • B populateBean() 发现自己需要A对象,从一级缓存singletonObjects和二级缓存earlySingletonObjects中未发现A,但是在三级缓存singletonFactories中发现A,执行singletonFactories 里A的回调函数getEarlyBeanReference(),创建不完整的A bean,将其放入二级缓存earlySingletonObjects,同时从三级缓存删除;

  • 将A注入到对象B中,B完成属性填充,执行初始化方法,将自己放入第一级缓存中(此时B是一个完整的对象);

  • 返回,A得到对象B,将B注入到A中,A完成属性填充,初始化,并放入到一级缓存中。

  • 在创建过程中,都是从三级缓存(对象工厂里创建不完整对象),将提前暴露的对象放入到二级缓存,从二级缓存拿到后,完成初始化,放入一级缓存。

三 原码分析

流程提前透知一下,便于描述

3.1. BeanServiceA的创建:

在创建bean时,会调用doGetBean方法,首先通过getSingleton方法从缓存中看是否能获取到该bean

  • a. 先从一级缓存singletonObjects中获取,发现获取不到,然后看是否在创建中,显然初次创建时不成立,即getSingleton返回null
  • b. 调用第14行的getSingleton方法触发createBean回调,进行bean的生命周期
    protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        ... ... ...
        //先从一级缓存singletonObjects中获取,发现获取不到,然后看是否在创建中,显然初次创建时不成立,即getSingleton返回null
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            ... ... ... 
        }else {
            ... ... ...
            try {
                ... ... ...
                if (mbd.isSingleton()) {
                    //getSingleton方法触发createBean回调,进行bean的生命周期  
                    //这里会将当前beanName放入singletonsCurrentlyInCreation,表示当前bean正在创建
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
            ... ... ...
        }
        return (T) bean;
    }

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ... ... ...
                // 会将当前beanName放入singletonsCurrentlyInCreation,表示当前bean正在创建
                beforeSingletonCreation(beanName);
                ... ... ...
                try {
                    // lambda表达式回到createBean
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
            ... ... ... 
            }
            return singletonObject;
        }
    }

3.1.1. 实例化BeanServiceA不完整对象:

  • 一般通过createBeanInstancec实例化不完整的BeanServiceA对象
  • 将不完整对象以及BeanDefinition代表的lambda表达式写入三级缓存
  • 属性填充BeanServiceB
  • 初始化BeanServiceA时调用AOP后置处理器进行AOP处理
  • 处理提前暴露的场景,保证返回同一个代理对象
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    ... ... ...
    if (instanceWrapper == null) {
        // a. 一般通过createBeanInstancec实例化不完整的BeanServiceA对象
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    ... ... ...
    // 默认单例&&默认循环引用&&该bean正在创建,条件成立
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        ... ... ...
        // b. 将不完整对象以及BeanDefinition代表的lambda表达式写入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // c. 属性填充BeanServiceB
        populateBean(beanName, mbd, instanceWrapper);
        // d.初始化时调用AOP后置处理器进行AOP处理
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    ... ... ...
    // e.处理提前暴露的场景,保证返回同一个代理对象
    if (earlySingletonExposure) {
        // 见上文第1节的节速,由于第二个参数是false,所以只会查到第二季缓存
        // 所以这里就是查看第二级缓存能不能取到值,取到就意味着涉及提前AOP
        Object earlySingletonReference = getSingleton(beanName, false);
        // 涉及提前AOP,从二级缓存中获取提前AOP的代理对象
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                // 保证循环依赖且涉及AOP时,返回同一个代理对象,下文有结束
                exposedObject = earlySingletonReference;
            }
            ... ... ...
        }
    }
    return exposedObject;
}

上面 addSingletonFactory将不完整对象以及BeanDefinition代表的lambda表达式写入三级缓存

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 将不完整对象以及BeanDefinition代表的lambda表达式写入三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

3.1.2. 属性填充BeanServiceB:

> 关于属性填充方法的详细介绍可参考链接:属性填充populateBean

当填充BeanServiceB会重复上文第1节中的内容:

  • a. 先从一级缓存singletonObjects中获取,发现获取不到,然后看是否在创建中,显然初次创建时不成立,即getSingleton返回null
  • b. 调用第14行的getSingleton方法触发createBean回调,进行bean的生命周期
  • c. 实例化BeanServiceB的不完整对象,并将lambda写入三级缓存
  • d. 属性填充BeanServiceA(见下文1.1.2.1)
  • e. initializeBean初始化BeanServiceB
3.1.2.1. 上面的d,循环依赖处理属性填充BeanServiceA:

同样,会重复上文第1节的内容,但此时会有不一样的处理:

  • a. 先从一级缓存singletonObjects中获取,发现获取不到,然后看是否在创建中,显然正在创建,singletonsCurrentlyInCreation有beanServiceA
  • b. 从二级缓存中获取 → 获取不到 → 从三级缓存获取 → lambda表达式回调(见下文1.1.2.1.1)
  • c. 将不完整对象BeanServiceA写入二级缓存,三级缓存删除该对象lambda表达式(上文1中getSingleton方法)
  • d. 返回不完整的BeanServiceA对象
3.1.1.2.1.1. AbstractAutoProxyCreator#getEarlyBeanReference:

这里涉及Aop,关于Aop源码有兴趣可以查看链接:Aop代理过程

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //遍历后置处理器
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 调用后置处理器的getEarlyBeanReference进行提前暴露bean
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

lambda回调会调用后置处理器的getEarlyBeanReference,来获取不完整的bean

  • 如果当前依赖的bean不涉及Aop,则返回实例化时创建的不完整bean对象
  • 如果当前依赖的bean涉及Aop,则返回一个代理该不完整bean的代理对象
  • 本处返回实例化创建的不完整beanServiceA对象
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 写入earlyProxyReferences,在后面postProcessAfterInitialization会用到
    this.earlyProxyReferences.put(cacheKey, bean);
    // 判断是否创建代理对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}
3.1.2.2 初始化BeanServiceB时调用AOP后置处理器进行AOP处理

属性填充beanServiceA后,此时填充的beanServiceA是不完整的对象,
在initializeBean初始化调用后置处理器的postProcessAfterInitialization方法

由于BeanServiceB不涉及AOP,所以返回原始的B对象,此时填充的beanServiceA还是不完整的对象

  • B不涉及提前AOP,所以earlyProxyReferences没有beanServiceB(见上文3.1.2.1.1)
  • remove返回null,条件成立,执行wrapIfNecessary方法
  • 由于beanServiceB不涉及AOP,所以返回原始的B对象
3.1.2.3. 处理提前暴露的场景,保证返回同一个代理对象

BeanServiceB不涉及AOP,getSingleton返回null,所以直接返回原始对象exposedObject

3.1.2.4. BeanServiceB对象写入一级缓存,移除二、三级缓存

beanServiceB生命周期执行完,返回到3.1节第14行的getSingleton方法,此时返回的还是不完整的beanServiceB对象
singletonsCurrentlyInCreation移除bean,表明不再是正在创建的bean

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ... ... ...
                // 会将当前beanName放入singletonsCurrentlyInCreation,表示当前bean正在创建
                beforeSingletonCreation(beanName);
                ... ... ...
                try {
                    // lambda表达式回到createBean
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                ... ... ..
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    // singletonsCurrentlyInCreation中移除beanName,表示该bean不是正在创建了
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    // 将不完整的beanServiceB对象写入一级缓存,移除二、三级缓存
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

beanServiceB“不完整对象”写入一级缓存,移除二、三级缓存

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 写入一级缓存
        this.singletonObjects.put(beanName, singletonObject);
        // 移除二、三级缓存
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

3.1.3. BeanServiceA属性填充BeanServiceB(完整bean)

此时ioc容器已经有了beanServiceB,虽然暂时还是不完整的,因为A还没填充初始化完
当beanServiceA填充完beanServiceB后,使得互相循环依赖对方,此时二者都变成了完整的bean
此时一级缓存的beanServiceB也由不完整的bean变成了完整的bean,因为是同一个地址
A涉及AOP,所以循环依赖时,A会进行提前AOP,所以B中填充的是A的代理对象
当A填充完B时,构成互相循环依赖对方

3.1.4. 初始化BeanServiceA时调用AOP后置处理器进行AOP处理

由于beanServiceA提前AOP了,所以所以earlyProxyReferences有beanServiceA(见上文3.1.2.1.1)
remove返回原始的bean,即earlyProxyReferences对应的value,条件不成立,直接==返回原始bean ==

3.1.5. 处理提前暴露的场景,保证返回同一个代理对象

beanServiceA涉及提前AOP,getSingleton返回保存在二级缓存中提前AOP的代理对象A

 这里面有exposedObject == bean判断,这就是为什么上述3.1.4中AOP后置处理器返回原始bean的原因
条件成立,将代理对象赋值放回,保证返回同一个代理对象,即B中的A和A都是同一个代理对象

3.2. BeanServiceA完整对象写入一级缓存,移除二、三级缓存

同3.1.2.4节一样,beanServiceA生命周期执行完,会将beanServiceA写入一级缓存,移除二、三级缓存

四 整体流程

4.1涉及循环依赖&&涉及AOP的场景

该场景大概流程如下图,详细可见上文介绍

五 疑问:

5.1 为什么三级缓存用HashMap,而不像一级缓存使用ConcurrentHashMap

现有逻辑是,三级缓存的操作是在synchronized代码块里面操作的,是安全的

那为什么要用synchronized而不直接用ConcurrentHashMap来保证线程安全呢?
二级缓存put的同时要保证三级缓存remove;三级缓存put时要保证二级缓存remove,也就是说二三级缓存操作要保证原子性
因为要保证同一个bean是单例的,不然都会lambda回调创建bean,就不是单例的了
如果使用ConcurrentHashMap并不能保证二三级缓存操作的原子性,所以要用synchronized
这三级缓存都是在synchronized内操作的,至于一级缓存为什么用ConcurrentHashMap,可能其他场景的原因吧,我也不不清楚

5.2 什么要第三级缓存?

主要用于循环依赖的bean需要AOP时提前AOP,我们必须直接创建代理对象
image.png

那如果将实例化的原始对象放入二级缓存呢?
没有第三级缓存,就无法提前AOP,则B属性填充完的A为A原始对象
而A在属性填充完B后,需要进行AOP,则经过AOP后置处理器会去创建代理对象A返回
这就导致B的属性A不是代理对象,而A却是代理对象,这与Spring的单例bean是矛盾的。

5.3代码里如果出现了循环依赖怎么处理比较好?

  1. 使用@Lazy注解,延迟加载
  2. 使用@DependsOn注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

6.多例和构造器为什么无法解决循环依赖

6.1 为什么多例Bean不能解决循环依赖?

解决循环依赖的核心是利用三个map,三级缓存,来解决这个问题的
如果单例只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入,但是如果我们是多例,那么根本用不到三级缓存,所以不能解决多例循环依赖

6.2 为什么Spring不能解决构造器的循环依赖?

因为构造器是在实例化时调用的,此时bean还没有实例化完成,如果此时出现了循环依赖,一二三级缓存并没有Bean实例的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

7 @Async造成的循环依赖报错流程

假设AB互相依赖,beanB加了@Async

  • beanA开始初始化,beanA实例化完成后给beanA的依赖属性beanB进行赋值
  • beanB开始初始化,beanB实例化完成后给beanB的依赖属性beanA进行赋值
  • 因为beanA是支持循环依赖的,所以可以在earlySingletonObjects中可以拿到beanA的早期引用的,但是因为beanB打了@Aysnc注解并不能在earlySingletonObjects中可以拿到早期引用
  • 接下来执行执行initializeBean(Object existingBean, String beanName)方法,这里beanA可以正常实例化完成,但是因为beanB打了@Aysnc注解,所以向Spring IOC容器中增加了一个代理对象,也就是说beanA的beanB并不是一个原始对象,而是一个代理对象
  • 接下来进行依赖检测的时候,发现actualDependentBeans不为空

8. SPRING在创建BEAN的时候,在哪里创建的动态代理?

①:如果没有循环依赖的话,在bean初始化完成后创建动态代理
②:如果有循环依赖,在bean实例化之后创建!

9 为什么有了三级缓存依然会报循环依赖的错误

@Async引起的循环依赖错误

参考

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

推荐阅读更多精彩内容