spring为何会出现循环依赖问题?
我们举个会产生循环依赖的例子,如下所示,可以看到AService类中依赖了BService类,同理呢,BService类中依赖了AService类,这就是所谓的循环依赖。
@Component("aService")
public class AService {
@Autowired
private BService bService;
}
@Component("bService")
public class BService {
@Autowired
private AService aService;
}
如果我们使用如下方式就不会有问题。
AService a = new AService();
BService b = new BService();
a.bService = b;
b.aService = a;
那为何spring框架中怎么就会有循环依赖问题?我们需要从spring bean的生命周期去理解,生命周期主要包括如下几个步骤:
1. Bean定义加载阶段
2.实例化阶段
3.属性填充阶段
我们在这一步要将BService的实例赋值给AService类中的bService属性,但是BService此时还没有实例化呢,于是BService要进行实例化,但是BService实例化的过程中发现要注入AService实例,于是就又要去实例化AService,两个类走到这一步都还没有完成Bean的初始化呢,也就是这时还不能被拿来使用呢,这样的话,两者就就会互相依赖,永远无法结束了。
4.Aware接口回调阶段
5.初始化前处理阶段
6.初始化阶段
7.初始化后处理阶段
这个阶段如果我们的类需要进行AOP的话,得到的对象是AOP对象,也就是代理对象,我们都知道AOP是spring的核心特性之一,通过AOP我们可以在不改变原有类代码的情况下对功能进行增强。最终就绪的Bean是要放到单例池中的。
8.Bean就绪阶段
9.销毁阶段
1.1 什么是第一级缓存,为何需要它?
我们都知道使用@Component注解将类交给spring容器管理,这种是单例模式的,也就是spring默认情况下,相同名称的类只给这个类生成单个实例。那我们如何来实现我们获取的是同一个实例对象呢?这时候就用到了我们的第一级缓存(singletonObjects),也叫做单例池缓存,它是ConcurrentHashMap,之所以用它是因为ConcurrentHashMap是线程安全的,从而确保大家拿到的都是同一个对象。
1.2 什么是第二级缓存,为何需要它?
上面我们在属性填充阶段AService和BService两个类互相循环依赖,由于两个类都还没有完成初始化,因此陷入死循环了,要想打破死循环,就得想办法增加第二级缓存(earlySingletonObjects),这个缓存我们要缓存的是实例化完但还未初始化的半成品对象,这样上面属性填充阶段,AService实例化之后就将半成品存放到二级缓存当中,我们从上面bean生命周期中第7步可以看到我们的类如果需要进行AOP的话,最终完成初始化的完整bean是要放到单例池中的,而且放到单例池中的对象是AOP对象,显然我们目前无论AService实例化还是BService实例化,都不是代理对象而是真正的对象本身,但是呢我们又不能等到初始化后再放到二级缓存,这时我们便不得不将AOP提前到实例化阶段了,然后填充BService属性的时候,实例化BService,而BService实例化后也将半成品AOP对象存放到二级缓存当中,然后BSerivce实例化后填充属性AService的时候,发现二级缓存中有这个类的半成品AOP对象,于是就从二级缓存中拿到未初始化完成的AService类的AOP对象,这样就解决了循环依赖的问题。
1.3 什么是第三级缓存,为何需要它?
如果我们的AService和BService不需要进行AOP,那么我们在填充属性阶段到底该向二级缓存中存入半成品AOP对象呢还是半成品实际对象?这时光靠我们目前已知的两级缓存已经不够用了,我们还需要引入第三级缓存(singletonFactories),这一级缓存会将对象的进行AOP所需要的参数组装成一个ObjectFactory,存入第三级缓存时只是将ObjectFactory存放到第三级缓存了,还没有执行里面的进行是否需要AOP判断以及进行AOP的动作。有了这一级缓存的好处就是,当我们先检查第一级和第二级缓存里面都没有要注入的对象时,就可以从第三级缓存中取出类相关的信息,判断是否需要进行AOP,如果需要就提前进行AOP,从第三级缓存中取出ObjectFactory之后,就从第三级缓存中删除了,因为我们提前进行AOP或者不需要进行AOP场景下将实例化完但还未初始化的半成品对象就要放到二级缓存了。第三级缓存就没有用处了,第三级缓存和第二级等于是互斥的。
上面在初始化之后进行如果需要进行AOP,由于我们前面已经提前进行AOP了,因此直接从二级缓存中拿就行了,到了这一步AOP对象也是完整的了。如果不需要进行AOP,那么这时拿到的也是初始化完整的实际对象,最后将完整AOP对象或实际对象存放到一级缓存单例池中。
以上便是spring通过三级缓存解决循环依赖的情况。