前言
循环依赖基本是一个会必然出现的问题,所以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的循环依赖就说完了,其实也不能说很复杂,核心的点就是那几个东西,多看几遍就记住了