本文主要内容分以下几个方面阐述:
1:什么是循环依赖?
2:什么是三级缓存?每一级缓存中都存储的是什么内容?
3:spring是如何解决循环依赖问题的,为什么使用三级缓存而不是二级缓存来解决循环依赖问题?
1:什么是循环依赖?
举个简单实例,当有两个类A类,和B类。
public class A{
private B b;
}
public class B{
private A a;
}
A类中有一个B类字段,而B类中有一个A类字段。此时,我们要创建一个A类型的对象时,需要为它B类型的字段赋值一个B类型对象。故我们要创建一个B类型对象实例,而想创建一个B类型实例,又需要为它的字段类型为A的字段赋值一个A类型的实例。如此反复,就称之为循环依赖。
当AB两类交付spring进行管理的时候,类中字段的赋值由spring完成,此时,spring就要解决这种循环依赖的问题。如何解决的呢?
spring使用三级缓存解决循环依赖问题。要知道三级缓存的具体应用,我们首先要了解什么是三级缓存。
2:spring中的三级缓存
所谓三级缓存,是spring中的“上帝”类:DefaultListableBeanFactory中的三个(继承自父类)Map类型字段;
我们每启动一个Spring环境,其实就是创建了一个DefaultListableBeanFactory类的实例。它关于三级缓存的三个字段分别是:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);singletonObjects 单例池,用来缓存走完全部创建流程的单例对象,包括已经完成依赖注入,完成各种代理如AOP代理(如果有的话)、BeanPostProcessor代理(如果有的话)的单例对象。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); singletonFactories 单例工厂,用来临时缓存一个还没有完成创建的半成品对象。创建流程完成以后,该处缓存将移除Bean,并存储到单例池中。
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); 早期单例对象,也是用来临时缓存一个还没有完成创建的半成品对象,此处的缓存值只在极特殊场景下会使用到,一般情况下使用上边两个缓存就能解决循环依赖问题。但是要想完全解决循环依赖问题,必须要有三级缓存同时协作。
3:spring是如何解决循环依赖问题的
要解释清楚这个问题,我们要有一些预备知识。
第一个背景知识:Bean实例化流程:
Bean的实例化流程在我的博文spring源码笔记(五)---Bean的实例化流程分析 中有详细解释,建议大家作为背景知识先大概做一个了解。对彻底了解三级缓存的运行机制很有必要。概要来说,有以下几个步骤:
第一步:通过无参构造创建Bean的实例。
第二步:将该Bean对应的工厂对象(注意不是对象本身)存储到singletonFactories 单例工厂缓存中。
第三步:对该Bean中需要注入的属性进行依赖注入。
第四步:依次调用BeanPostProcessor的实现类,前置目标方法, Bean的init方法,依次调用BeanPostProcessor的后置目标方法。(由于spring中的AOP实现也是一个BeanPostProcessor接口的子类,故此处也是的AOP代理时机(不存在循环依赖情况下),循环依赖发生时会有不同);
第五步:将完成依赖注入,并且完成BeanPostProcessor处理的对象,存储到单例池中。
第二个背景知识:依赖注入会导致Bean的创建流程跳转到其他Bean的创建流程中
这一点在我上边给出的链接的文章中也做了详细的解释,大家请着重看给出的流程图中标星的地方。这里专门拿出来强调,是因为这也是解决循环依赖必须要了解的前提。
在了解了这些背景知识之后,我们开始学习循环依赖的解决。
循环依赖的解决:
我们给出存在循环依赖问题的两个类:
public class Husband {
private Wife wife;
public void speak() {
System.out.println("I`m husband!");
}
public Wife getWife() {
return wife;
}
@Autowired
public void setWife(Wife wife) {
this.wife = wife;
}
}
public class Wife {
private Husband husband;
public Husband getHusband() {
return husband;
}
@Autowired
public void setHusband(Husband husband) {
this.husband = husband;
}
public void speak() {
System.out.println("I`m wife!");
}
}
4:尝试使用一级缓存解决循环依赖问题。
我们用上述两个类举例,尝试仅仅使用一层缓存来解决。这里直接省去两个临时缓存,保留单例池缓存singletonObjects 。下面给出调用流程。
我们假设Husband类先开始创建:
可见,使用一层缓存出现循环依赖问题是没有办法解决的。
5:尝试使用两层缓存解决循环依赖问题。
spring中使用最频繁的两层缓存是,工厂缓存objectFactories,和单例池缓存singtonObjects,我们使用这两层缓存尝试解决循环依赖问题。
注意,早期对象缓存earlySingletonObjects 好像从语义上更像是用来存储和获取早期对象的,许多人相当然的认为,最常用的应该是它和单例池缓存singtonObjects,但其实并不是。下面给出流程。
我们依然假设Husband类先开始创建:
以上为Husband和Wife两个类都不存在AOP代理的情况下, 使用两层缓存解决循环依赖问题。问题可以顺利解决。那么,存在AOP代理的情况下如何呢?是否依然能解决?
是的,如果是单纯是两个类之间相互发生循环依赖。 即使进行了AOP代理,两层循环依然能够解决!
假设在HUsBand类先于Wife类创建的条件下:
如果wife类发生AOP代理,那么流程与上述相同,因为根据前文提到的创建流程,wife类的AOP代理发生在DI之后,创建流程并没有打乱。
关键在于husband类发生AOP代理这种情况,这时候就有问题了!
我们从新走一遍创建流程:1、husband类调用默认构造创建实例;2、工厂类缓存到singletonFactories中,此时从工厂类中可以获取一个没有进行AOP代理的husband实例。3:husband进行DI,4:发现需要wife对象,husband创建流程挂起,转入wife创建流程中。5:wife工厂类缓存到singletonFactories中。6:wife进行DI,需要husband,将没有进行AOP代理的husband实例注入。
7:wife调用BeanPostProcessor;8:wife存储到单例池中。9:为husband进行DI,wife从缓存中取出一个实例,注入husband。10:husband的DI完成,开始调用BeanPostProcessor,由于husband进行了AOP代理。此处将会执行相关逻辑。代理过后的对象,不再是husband类型, 而是动态代理类型了!11:将动态类型的husband存储到单例池中。
发现问题没有,按照单例原则,husband在单例池缓存中,与在wife类的husband字段中应该是同一份,但是现在,husband在单例池中变为了动态类型,而在wife类中依然为没有进行AOP代理的husband!
原因就是,根据创建流程,DI在前,AOP代理在后,wife用未进行代理的对象进行了注入以后,AOP又创建出一个动态代理的husband类!
要解决这个矛盾,就要在发生循环依赖的时候,在wife还未DI之前,将husband类提前进行AOP代理!以保证一致性!DI之前嘛,一定是工厂缓存objectFactories那个阶段喽!
下边方法为spring中 BeanFactory接口的getObject()返回值;使用工厂缓存调用,getObejct()方法返回实例,就是调用如下方法。
通过在工厂缓存做了对husband类的判断,如果进行了AOP代理,那么不要等DI 完成,此处直接返回一个代理对象,这样,wife类接下类注入的husband字段自然也是经过代理的字段了。问题解决。
此处可见,通过两层缓存是可以解决上述AOP代理情况下循环依赖问题的。
earlySingletonObjects缓存到底有什么作用?真的是用来提高存取效率的吗?
那么,spring中的 earlySingletonObjects缓存到底有什么作用?上述情况的AOP完全没有用它的情况下也解决了,那它还有存在的意义吗?不会仅仅是用来提高存取速度的吧?
当然不是, earlySingletonObjects用来解决极少数情况下的循环依赖问题的!
下面给出具体场景:
如图中场景所示,Husband又有了一个小老婆!Husband类即与wife循环依赖,又与LeftHandWife循环依赖。
根据上一节流程描述我们知道,当Wife类需要注入husband字段之前,spring对husband缓存的实例进行了提前AOP代理,生成了动态代理对象。用动态代理的husband对wife进行注入。然后一切如平常一样,wife所有创建流程走完。
回到挂起的husband的DI阶段,接下来要干嘛?继续husband的DI呗。
spring一看,呦呵,一个老婆不够,还有一个小老婆在和husband纠缠不清。于是,husband的DI继续挂起,流程来到的小老婆类的创建中。
LeftHandWife,创建默认构造,流程一路向下,来到了LeftHandWife的DI流程中,此时如果还是只有两层缓存,那么spring只能再从工程缓存中取出一个未进行代理的husband。再进行一个代理。这时候,这个动态代理对象husband,与wife类中的husband字段的代理对象必然不是同一个!一个spring家族里边,大小老婆发现自己的老公不是同一个,能不断创建出新的。那还得了!
所有啊,spring一想,看来两层缓存是不够的,当发生了循环依赖,且同时进行了AOP代理。又有不同的地方需要这个经过代理的husband的时候,必须将这个代理过后的husband也进行一下缓存,于是,earlySingletonObjects它来了!
创作不易,转载请注明出处。谢谢大家耐心观看。