Spring循环依赖产生原理

原文链接:Spring循环依赖产生原理 – 编程屋

目录

1 概述

2 Spring循环依赖介绍

2.1 为什么会产生循环依赖

2.2  Spring IOC加载过程

2.3 spring一二三级缓存

2.3.1 一级缓存作用

2.3.2 二级缓存作用

2.3.3 三级缓存作用

3 二级缓存能解决循环依赖吗

4 一、二、三级缓存各自存储什么


1 概述

        最近在看spring循环依赖的知识,发现spring循环依赖是依靠三级缓存来解决的。当时很纳闷。大家都知道spring的循环依赖时是依照三级缓存来解决的,那么顾名思义既然有三级缓存,就有一级缓存二级缓存,那么一级缓存和二级缓存分别有着什么作用呢?既然有着一级缓存和二级缓存,为什么还要有一个三级缓存呢?解决循环依赖的问题不能放在一级缓存中解决或者二级缓存中解决吗?在网上查找了许多资料,于是今天来总结下:

2 Spring循环依赖介绍

2.1 为什么会产生循环依赖

Spring循环依赖产生原理_第1张图片

                                                                图2.1

如上图所示(在bean创建的时候才会出现循环依赖)

        在创建beanA的时候,spring发现你依赖beanB,然后会去创建beanB,创建beanB时发现你又依赖beanA,然后又会去依赖beanA,这样一直循环下去,始终不能有一个bean先创建完成,循环依赖就产生了。

分析以上就可以得出一个结论:产生循环依赖的问题:两个在bean在创建过程中互相依赖,但始终有一个bean不能够率先完成创建,导致了一个类似于死循环之类的问题。

2.2  Spring IOC加载过程

        要想了解spring产生循环依赖的原因,必须了解一下spring IOC的加载过程。因为spring的循环依赖是发生在两个或以上创建bean过程中的。

Spring循环依赖产生原理_第2张图片

                                                                图2.2

2.3 spring一二三级缓存

2.3.1 一级缓存作用

结合图2.1和图2.2可讨论为什么spring只有一级缓存时解决不了循环依赖的问题?

当获取beanA------>从缓存中获取----->获取不到创建(走到创建第二步属性注入时发现依赖beanB)----->获取beanB----->缓存中获取不到----->创建beanB(走到创建第二步时发现依赖beanA)----->又去获取beanA----->由于之前创建beanA时依赖beanB导致beanA没有初始化完成所以依然获取不到,死循环就此产生

        这个时候,有的同学可能就有问题了,既然它是在属性注入的时候出现的问题,那么将它在属性注入之前(第一步创建实例之后)就将它放入缓存,那么问题不就迎刃而解了吗?

        这个解决方案按道理来讲是没问题的,但是spring一级缓存储存的bean是一个完整的bean,如果在创建实例后就放入一级缓存,那么一级缓存中存储的bean就不是完整的了。

        假如在多线程情况下(线程1和线程2)同时去获取beanA,线程1先进入,然后在创建实例之后将其放入缓存中,然后线程2进来了,发现缓存中有线程A,那么线程2就没有获取到一个完成的beanA(其还没有完成属性注入和初始化)。

        既然牵扯到多线程的问题,那么不约而同的想到加锁解决,的确这个问题的确可以通过加锁解决,在创建bean的时候将其加一把锁,没有完成初始化就不释放锁,这样其他线程获取到的bean都是初始化完全的了。

        但是这样,如果有线程2想要获取一级缓存中已经创建完成的bean,但是此时有线程1工作已经将其锁住了,只能等待线程1完成之后才能获取,这样就带来性能问题。

获取beanA>>>>>(加锁)从一级缓存中获取>>>>>一级缓存获取不到则创建>>>>>第一步:创建实例>>>>>放入一级缓存

        但加锁就必然牵扯到性能问题?怎样,是不是很蛋疼?一个解决方案解决了一个问题就必然牵扯到另外一个问题

2.3.2 二级缓存作用

于是spring就引入了二级缓存,可以说spring二级缓存的引入就是为了解决加锁带来的性能问题,具体的操作步骤如下:

获取beanA>>>>>从一级缓存中获取>>>>>(加锁)一级获取不到从二级缓存获取>>>>>二级缓存获取不到则创建>>>>>创建bean第一步:创建实例>>>>>放入二级缓存>>>>>。。。。。。>>>>>将创建好的bean放入一级缓存>>>>>删除二级缓存

那么在多线程环境下这样写会有问题吗?不妨来假设下:

Spring循环依赖产生原理_第3张图片

(线程1,线程2)>>>>>线程1获取beanA>>>>>从一级缓存中获取>>>>>(加锁)一级获取不到从二级缓存获取>>>>>二级缓存获取不到则创建>>>>>创建bean第一步:创建实例>>>>>放入二级缓存>>>>>线程2从一级缓存中获取>>>>>一级缓存获取不到从二级缓存中获取(但此时二级缓存已经被线程1锁住)>>>>>线程1属性赋值(对B)>>>>>线程1获取beanB>>>>>从一级缓存中获取>>>>>(加锁)一级获取不到从二级缓存获取>>>>>二级缓存获取不到则创建>>>>>创建bean第一步:创建实例>>>>>放入二级缓存>>>>>线程1属性赋值(对A)>>>>>线程1获取beanA>>>>>从一级缓存中获取>>>>>(加锁)一级获取不到从二级缓存获取>>>>>线程1获取到了返回并释放锁>>>>>线程2从二级缓存中获取>>>>>获取不到(线程1执行完成之后将2级缓存删除了)

        所以这就导致了一个问题,在多线程环境下,率先执行完的线程会将二级缓存中的数据清理掉,导致其他线程从一级缓存获取不到来到了二级缓存获取发现二级缓存也没有,难道还要重新再走一遍吗?并不然,这个时候只需在其他线程再次从二级缓存获取时,再重新检查检查一遍一级缓存就好了,这就是所谓了双重检查锁。

所以二级缓存+双重检查锁解决了并发情况下获取不完整的bean

2.3.3 三级缓存作用

        那么到了这里,一级缓存和二级缓存都已经相继出现了,循环依赖问题貌似已经解决了,但是事实上真的是这样吗?一级缓存和二级缓存的出现按照之前的想法来看循环依赖问题的确已经得到了解决,但是我们少考虑了一个问题,这个问题的出现才是三级缓存出现的关键,那就是动态代理。

        对于spring来讲,spring的动态代理是在创建bean初始化时进行的。那么对于互相依赖的bean来说,如果在循环依赖时创建动态代理,出现那种多重依赖的情况(A依赖B,B依赖A,同时A依赖C,C依赖A)时,在这种情况下,A的代理对象就会被创建两次,为了解决这种情况,spring就引入了三级缓存

循环依赖中创建了动态代理,需要拿到循环依赖中的动态代理对象,并且在双重依赖的情况下,动态代理只创建一次

注意:循环依赖结束时,三级缓存被删除。多例bean没有解决循环依赖。

Spring循环依赖产生原理_第4张图片

3 二级缓存能解决循环依赖吗

一级缓存:单例池,存储所有完整的bean

二级缓存:解决循环依赖中的单例性和在并发下获取完整bean性能问题

三级缓存:为了保证动态代理只创建一次,三级缓存的出现是为了解决循环依赖的动态代理,如果出现多次循环依赖,会出现多次动态代理,所以才需要三级缓存

那么看到这里可能有的同学又要问了:只有二级缓存不能解决循环依赖的问题吗?

其实也能够解决,但是如果出现一个bean多次循环依赖的情况,会多次创建动态代理,所以需要三级缓存。

4 一、二、三级缓存各自存储什么

以上只是部分内容,为了维护方便,本文已迁移到新地址:Spring循环依赖产生原理 – 编程屋

你可能感兴趣的:(数据结构,Java基础,spring,单例模式,java)