Spring的三级循环及循环依赖
循环依赖
循环依赖是指两个对象相互依赖,形成了一个环形的调用链路。比如下面的例子:
1 | public class A { |
上面的例子中,当创建A对象时,发现依赖B,转而去创建B对象,但在创建B对象时又发现依赖A,从而周而复始,形成了一个循环依赖。
像这样的例子在实际的开发过程中是很难避免甚至是常见的,但我们在使用Spring的过程中却并不会遇到循环依赖的问题,这是为什么呢?答案就是Spring中通过使用三级缓存来避免了出现循环依赖的问题。
Spring中Bean的生命周期
在开始讲解Spring如何通过三级缓存来避免循环依赖之前,我们先来了解一下Spring中的Bean的生命周期。
Spring在初始化Bean时,BeanFactory会根据BeanDefinition创建Bean对象,创建的过程如下:
- 实例化:实例化该Bean对象
- 填充属性:给该Bean赋值
- 初始化:
- 如果实现了Aware接口,会通过其接口获取容器资源
- 如果实现了BeanPostProcessor接口,则会回调该接口的前置和后置处理增强
- 如果配置了 init-method 方法,会执行该方法
- 销毁:
- 如果实现了 DisposableBean 接口,则会回调该接口的 destroy 方法
- 如果配置了 destroy-method 方法,则会执行 destroy-method 配置的方法
Spring使用三级缓存解决循环依赖
Spring的三级缓存分别是:
- 第一级缓存:用来保存实例化,初始化都完成的对象
- 第二级缓存:用来保存实例化完成,但是未初始化完成的对象
- 第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
三级缓存的核心逻辑就是:把实例化和初始化的步骤分开,然后放入缓存中,供另一个对象调用。
Spring解决循环依赖有两个前提条件: - 不全是构造器方式的循环依赖(否则无法分离初始化和实例化的操作)
- 必需是单例(否则无法保证是同一对象)
一个例子
我们以上面提到的例子来看一下三级缓存是怎么解决循环依赖的:但A,B两个类发生循环引用时。
- A完成实例化后,去创建一个工厂对象,并放入三级缓存当中
- 如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象
- 如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象
- A进行属性注入时,去创建B
- B进行属性注入时,需要A,则从三级缓存中去取A工厂代理获取A的早期对象,并将这个早期对象注入二级缓存,然后删除三级缓存中的A工厂,将尚未创建完毕的A的引用赋值给B
- B完成后续属性注入,直到初始化结束,将B放入一级缓存
- A从一级缓存中取到B并且注入B,直到完成后续操作,将A从二级缓存删除,并放入一级缓存,循环依赖结束
为什么要使用三级缓存
从解决循环依赖的核心逻辑:把实例化和初始化的步骤分开,然后放入缓存中来看,其实只使用二级缓存就可以解决了(一级缓存存放实例化和初始化完成的实例,二级缓存存放实例化完成但尚未初始化完成的对象),那么为什么还需要引入一个三级缓存呢?
这是因为Spring中还有另外一个问题需要解决,就是初始化过程中的AOP实现,Spring中的AOP有两种实现方式:一种是JDK自带基于接口的动态Proxy技术,另一种是CGlib基于字节码动态生成的Proxy技术。Spring中AOP的实现是通过后置处理器 BeanPostProcessor
来实现的,而后置处理器是在填充属性结束后才执行的。
- 实例化对象
- 对象填充属性
- BeanPostProcessor doBefore
- init-method
- BeanPostProcessor doAfter – AOP在这个阶段实现
所以如果循环依赖的对象中有AOP代理的对象的话,通过二级缓存就不容易解决循环依赖的问题。如果只使用二级缓存解决循环依赖,那么意味着所有的 Bean 在实例化后就要完成AOP代理。
三级缓存只会创建一个工厂对象并将其放入三级缓存中,也就是说只有在真正方式循环依赖时才会去生成代理对象。
- 标题: Spring的三级循环及循环依赖
- 作者: zjun
- 创建于 : 2019-08-02 22:31:22
- 更新于 : 2023-12-06 11:48:55
- 链接: https://zjun.site/2019/08/4c2d0cec00e5.html
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。