Spring循环依赖解决核心思想

前言

循环依赖基本是一个会必然出现的问题,所以spring是必须解决的,初看起来很复杂,但是如果分开理解就不会过于复杂了,本篇文章主要是讲解spring中循环依赖的解决方案

循环依赖

比如有以下的两个类,如下

public class A {
    @Autowire
    private B b;
}

public class B {
    @Autowire
    private A a;
}

普通的循环依赖解决

如果不是在spring中,我们可以很容易的解决这个问题,如下


B b = new B();
A a = new A();

b.a = a;
a.b = b;

只需要进行如此的设置就可以解决了,但是如果在spring中那么这个流程就会变得复杂,主要是spring中bean都是有生命周期的,如下

spring中的循环依赖

解设A先创建的,有如下的过程

1. 初始化A
2. 填充A的属性,从单例池获取B
3. bean后置处理器的前置方法
4. bean后置处理器的后置方法
5. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.1. 初始化B
2.2. 填充B的属性,从单例池获取A
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

如上面所示,如果不进行任何处理,那么就会陷入死循环,spring肯定是需要进行处理的,那么是如何处理的,我们就一步步往下面看

spring循环依赖解决

首先,可以先说下解决的核心点,主要有以下的数据结构与作用

Map<String, Object> singletonObjects: 单例池,存储了成熟的bean
Map<String, Object> earlySingletonObjects: 早期未经过完整bean周期的bean
Map<String, ObjectFactory<?>> singletonFactories:用于解决是否需要aop返回对应的对象的
Set<String> singletonsCurrentlyInCreation:当前正在创建的bean集合
Map<Object, Object> earlyProxyReferences:是否已经提前进行aop了

下面就慢慢进行分析整个过程了

打破循环依赖

在上面的问题中,就是出现了死循环的依赖,那么最简单的方法就是加入一个map打破这个循环即可,如下

解设A先创建的,有如下的过程

1. 初始化A->zxcMap<a,A的原始对象>
2. 填充A的属性,从单例池获取B
3. bean后置处理器的前置方法
4. bean后置处理器的后置方法
5. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.1. 初始化B
2.2. 填充B的属性,从单例池获取a->zxcMap获取到了
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

如上,只要添加一个zxcMap就可以打破这个循环了,那么是不是就解决这个问题了呢?如果不考虑aop的话,确实是已经解决了,但是如果出现了aop就有可能出现问题了

aop下循环的问题

解设在实例化A到第4. bean后置处理器的后置方法的时候,A需要进行aop,那么最后放到单列池的就是AOP的代理对象,但B从zxcMap获取的对象是原始对象,那么很明显就有问题了

aop循环解决方案一

可以直接这样 1. 初始化A->aop-> zxcMap<a,A的代理对象>,这样就可以解决了,但是如果这样bean的生命周期就有问题了,因为不是所有bean都需要提前进行aop的,所以就有问题了

aop循环解决方案二

那么就不能这样做,我们需要确认是否发生了循环依赖以后再提前aop,那么如何弄,只需要进行下面的操作

解设A先创建的,有如下的过程

0. createset放置a正在创建中
1. 初始化A->zxcMap<a,A的原始对象>
2. 填充A的属性,从单例池获取B
3. bean后置处理器的前置方法
4. bean后置处理器的后置方法
5. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.0  createset放置b正在创建中
2.1. 初始化B                                     ->a原始对象    
2.2. 填充B的属性,从单例池获取a->createset是否在创建a->提前aop->a的代理对象
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

如上,只要在a创建的时候放到一个set,在b获取a的时候就可以判断是否需要提前进行aop了,但是这里又引出了新的问题,假设此时A还和C进行了相互依赖,如下

public class A {
    @Autowire
    private B b;
    @Autowire
    private C c;
}

public class B {
    @Autowire
    private A a;
}
public class C {
    @Autowire
    private A a;
}

那么过程会变为如下

解设A先创建的,有如下的过程

0. createset放置a正在创建中
1. 初始化A->zxcMap<a,A的原始对象>
2. 填充A的属性,从单例池获取B
3. 填充A的属性,从单例池获取C
4. bean后置处理器的前置方法
5. bean后置处理器的后置方法
6. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.0  createset放置b正在创建中
2.1. 初始化B                                     ->a原始对象    
2.2. 填充B的属性,从单例池获取a->createset是否在创建a->提前aop->a的代理对象
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

当在添加A中的c属性时又会触发C的创建

3.0  createset放置C正在创建中
3.1. 初始化C                                    ->a原始对象    
3.2. 填充C的属性,从单例池获取a->createset是否在创建a->提前aop->a的代理对象
3.3. bean后置处理器的前置方法
3.4. bean后置处理器的后置方法
3.5. 放到单例池

这种情况下会发生了a的代理对象创建了两个,所以这里也是二级缓存出现的原因,逻辑就应该变为如下

解设A先创建的,有如下的过程

0. createset放置a正在创建中
1. 初始化A->zxcMap<a,A的原始对象>
2. 填充A的属性,从单例池获取B
3. 填充A的属性,从单例池获取C
4. bean后置处理器的前置方法
5. bean后置处理器的后置方法
6. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.0  createset放置b正在创建中
2.1. 初始化B                                     ->a原始对象    
2.2. 填充B的属性,从单例池获取a->createset是否在创建a->从earlySingletonObjects获取->提前aop->a的代理对象->放到earlySingletonObjects中
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

当在添加A中的c属性时又会触发C的创建

3.0  createset放置C正在创建中
3.1. 初始化C                                    ->a原始对象    
3.2. 填充C的属性,从单例池获取a->createset是否在创建a->earlySingletonObjects获取到直接返回->提前aop->a的代理对象
3.3. bean后置处理器的前置方法
3.4. bean后置处理器的后置方法
3.5. 放到单例池

所以earlySingletonObjects的作用就出来了,他是为了解决当bean发生aop的时候代理对象不会创建多个,那么实际上这样就解决这个问题了吗,为啥需要三级map,这是因earlySingletonObjects存的不一定是代理对象因为不一定要进行aop,也有可能是原始对象,那么原始对象如何返回呢,就需要三级map的帮助

三级map存在的原因

三级map其实是一串表达式,逻辑如下

解设A先创建的,有如下的过程

0. createset放置a正在创建中
1. 初始化A->zxcMap<a,lambame>
2. 填充A的属性,从单例池获取B
3. 填充A的属性,从单例池获取C
4. bean后置处理器的前置方法
5. bean后置处理器的后置方法
6. 放到单例池

当在添加A中的b属性时又会触发B的创建

2.0  createset放置b正在创建中
2.1. 初始化B                                        
2.2. 填充B的属性,从单例池获取a->createset是否在创建a->从earlySingletonObjects->zxcMap<a,lambame>->放到earlySingletonObjects中
2.3. bean后置处理器的前置方法
2.4. bean后置处理器的后置方法
2.5. 放到单例池

zxcMap<a,lambame>的表达式逻辑如下

lambame逻辑如下

if(a需要aop) 则返回a的代理对象
else 则返回a的原始对象

所以Map<String, ObjectFactory<?>> singletonFactories就提现出三级map的作用了,而二级map的earlySingletonObjects的内容就来源于三级singletonFactories的判定,这样就清晰多了,那么最后的数据结构earlyProxyReferences有什么作用呢?如下

earlyProxyReferences的作用

正常bean的aop逻辑是在bean后置处理器的后置方法进行实现的,如果在发生循环依赖的时候进行了aop

lambame逻辑如下

if(a需要aop) 则返回a的代理对象,earlyProxyReferences.add(对象)
else 则返回a的原始对象

那么在

bean后置处理器的时候,如果earlyProxyReferences.contains(对象),那么就不需要进行再次aop
防止对同一个对象进行了多次aop处理

这样一来,spring的循环依赖就解决了

spring中的getSingle方法

这是5.2.8的源码,不同版本可能有不同的实现

@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //先从单例池获取
        Object singletonObject = this.singletonObjects.get(beanName);
        //如果单例池没有并且bean正在创建
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            //加锁
            synchronized (this.singletonObjects) {
                //再次从单例池获取对象
                singletonObject = this.earlySingletonObjects.get(beanName);
                //如果还是为空并且支持循环依赖
                if (singletonObject == null && allowEarlyReference) {
                    //从三级缓存中获取表达式
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        //调用方法,这里返回的可能是原始对象也可能是经过aop的代理对象
                        singletonObject = singletonFactory.getObject();
                        //放到二级缓存中。以便于上面说到C的对象中进行使用
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        //使用完了就进行移除,防止出现bug或者内存占用
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

spring解决不了的情况

有以下几种情况spring没有办法直接解决循环依赖问题

如果a和b都是原型的,那么是无法解决的

如果a和b只有一个原型,一个单例那么是可以解决的

如果a和b的构造器相互依赖对方,那只能通过@Lazy注解来解决,标记在任何一个上面即可

如果a自依赖自己那么也是可以解决的

总结

至此spring的循环依赖就说完了,其实也不能说很复杂,核心的点就是那几个东西,多看几遍就记住了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值