什么是循环依赖?
Bean的循环依赖指的是在Spring应用上下文中,两个或多个bean互相依赖,形成一个闭环,导致Spring在实例化这些bean时遇到的问题。具体来说,当尝试初始化一个bean A 时,发现它依赖于另一个bean B,而在尝试初始化bean B 时,又发现它反过来依赖于bean A,这就构成了一个循环依赖。
假设有两个类A和B,A依赖于B,B依赖于A
三级缓存
Spring容器提供了相应的方案来解决Bean的循环依赖问题
Spring容器维护了三个级别的缓存来管理Bean的生命周期状态。
一级缓存用于存储已经初始化完成的Bean实例以及代理
二级缓存用于存储半成品的Bean——已经实例化但是没有依赖注入(二级缓存的作用就是避免多次调用工厂导致多例的产生)
三级缓存用于存储Bean和代理的工厂(用于创建对象)
提前暴露未完成的Bean:
在Bean实例化过程中,Spring容器采用一种称为“提前曝光”(Early Exposure)的策略。当容器开始创建一个Bean(假设为Bean A),但在构造函数注入或直接字段注入之前,它会将一个未完全初始化的Bean A的引用(一个半成品Bean)提前放置到一个特殊的缓存(通常是二级缓存,即earlySingletonObjects
)中。这意味着即使Bean A还没有完成所有的依赖注入,它的一个引用已经被创建并且可以被其他正在初始化的Bean(比如Bean B)所访问。
依赖注入的时机:
当一个Bean(如Bean B)依赖于另一个正在创建的Bean(Bean A)时,Spring会检查是否已经有Bean A的半成品引用存在于二级缓存中。如果存在,就直接使用这个半成品引用,而不是等待Bean A完全初始化完成,从而打破了循环依赖的链条。之后,Spring会继续完成Bean A的依赖注入,并将其从二级缓存移动到一级缓存,表示它已经完全初始化好了。
需要注意的点:
- 构造器循环依赖无法解决:如果循环依赖是通过构造器注入形成的,Spring无法解决这种依赖,因为构造器注入必须在对象实例化时完成。
- 非singleton作用域的Bean不处理循环依赖:对于prototype作用域的Bean,Spring不会处理循环依赖问题,因为每个请求都会创建一个新的实例。
@Lazy
注解:使用@Lazy
注解可以在一定程度上缓解循环依赖问题,因为它推迟了依赖Bean的初始化,直到真正需要使用它的时候。