今天有个粉丝来找我,说要耽误我5分钟的时间。他要我帮他理解Spring循环依赖的L3缓存,晕了一个星期,没想明白。我想今天,我就用最通俗易懂的方式给大家重新梳理一下,让大家看得懂。1.什么是循环依赖?循环依赖是指循环引用,它是两个或多个相互持有的bean之间的引用。循环依赖有三种形式:(1)相互依赖,即A依赖B,B又依赖A,它们之间形成循环依赖。(2)三者之间的依赖,即A依赖B,B依赖C,C依赖A,形成循环依赖。(3)自立,也是A依赖A,形成自立循环。2、如何解决循环依赖问题?循环依赖本身没有问题。问题是Spring加入了依赖注入机制,就是给属性自动赋值。Bean创建并实例化后,Bean中所有需要赋值的属性都需要自动赋值,然后才能交给用户使用。但是如果是循环依赖,以两个bean相互依赖的情况为例,假设BeanA已经实例化,但是BeanB需要在BeanA中自动赋值,还没有初始化,但是如果Spring马上初始化BeanB,发现B里面需要自动赋值的BeanBeanA没有初始化。如果互相等待,就会形成死循环。最终,Spring容器可能无法启动。比如我们以前学习的时候,老师经常教给我们一个考试的方法,就是遇到难题,答不出来的时候,不要作死,而是要继续做其他的题。否则卡在一道难题上,会影响整个答题进度,也会影响正常发挥,影响考试成绩。那么如何解决这个问题呢?使用缓存。就是把所有实例化的bean放到一个容器中缓存起来,对那些已经实例化但是没有赋值的进行标记。然后,在所有bean实例化完成后,再次重新扫描容器,对没有完成赋值的Bean属性进行赋值。这时候所有没有完成赋值的bean都已经可以找到对应的实例了。那么问题来了。解决循环依赖问题,一定要用二级缓存吗?答案不一定。但是为什么Spring要设计二级缓存呢?这时候,我们可以这样理解。假设我们只有一个缓存容器,缓存直接开放给用户使用。同一个容器中的所有Bean都放在同一个容器中。这时候调用者可能会得到未分配的Beans。这样的Bean对用户来说是不可用的,可能会导致空指针异常。所以Spring的设计者就有了这样的设计,将可以直接提供给用户的bean放在一级缓存中,这样的bean就叫做finalbeans,或者maturebeans。已经初始化但不能提供给用户的Bean放在单独的缓存容器中,这就是二级缓存。这样的bean称为临时bean,或早期bean。根据上面的分析,理论上二级缓存可以解决循环依赖问题,那么Spring为什么要设计三级缓存呢?3、如何理解三级缓存?我们都知道Spring中有很多注入的bean需要创建代理bean,但并不是所有的bean都需要在实例化后立即创建代理bean。需要等到bean初始化完成后才能创建代理bean。因此,随着循环依赖的出现,Spring不得不提前创建代理bean。如果不创建代理bean,注入原始bean将产生错误。所以Spring设计了三级缓存,专门用来存放代理bean。但是,创建代理bean的规则不同。所以在Spring的三级缓存中,并没有直接保存代理bean的引用,而是保存了创建代理bean的Factory。4.总结因此,总结的结论是只能使用二级缓存来解决循环依赖,但是如果涉及到代理对象的循环依赖,则需要三级缓存。实际上,一级、二级、三级缓存是按照对象获取的先后顺序来命名的。我们完全可以理解为一级缓存是最终状态缓存,二级缓存是临时缓存,三级缓存是代理工厂的缓存。这张图完整的描述了一级、二级、三级缓存的运行逻辑。
