Spring循环依赖
当两个或更多个Bean之间相互依赖时,就会出现Spring循环依赖的问题。这意味着,每个Bean都需要其他Bean才能被创建,而其他Bean又需要该Bean才能被创建。这种情况下,Spring IoC容器会抛出一个异常,告诉你存在循环依赖。
什么是循环依赖
循环依赖是指两个或多个Bean相互依赖,导致Spring IoC容器无法正确创建它们。通常,循环依赖会导致无限递归,最终导致堆栈溢出。下面是一个简单的示例:
public class A { private B b; public A(B b) { this.b = b; } } public class B { private A a; public B(A a) { this.a = a; } }
在这个示例中,A和B相互依赖,因为A需要B才能被创建,而B又需要A才能被创建。这种情况下,Spring IoC容器会抛出一个异常,告诉你存在循环依赖。
spring的三级缓存
什么是三级缓存,是哪三级?
- singletonObjects:用于存放单例bean实例的缓存,即作用域为singleton的bean。当创建完一个bean后,Spring会将其放入singletonObjects缓存中。在后续获取该bean时,Spring会直接从该缓存中获取,不再需要重新创建。
- earlySingletonObjects:用于存放创建中的bean实例的缓存。在创建一个bean的过程中,如果需要引用到该bean,则会先从earlySingletonObjects缓存中获取。如果该缓存中不存在该bean的实例,则会通过实例化工厂创建一个新的bean实例。
- singletonFactories:用于存放创建中的bean实例的工厂缓存。在创建一个bean的过程中,如果需要引用到该bean,则会先从singletonFactories缓存中获取。如果该缓存中不存在该bean的工厂实例,则会通过实例化工厂创建一个新的bean工厂实例。在后续获取该bean时,Spring会从该缓存中获取bean工厂实例,并通过该工厂创建新的bean实例。
在以上三个缓存中,earlySingletonObjects缓存和singletonFactories缓存的作用是相同的,即存放创建中的bean实例。它们的区别在于earlySingletonObjects缓存存放的是已经创建但还没有完全初始化的bean实例,而singletonFactories缓存存放的是bean实例工厂。
缓存的初始化
当我们获取一个bean时,Spring首先会从singletonObjects缓存中查找是否存在该bean的实例。如果存在,则直接返回该实例;否则,Spring会检查该bean是否在earlySingletonObjects缓存中。如果在,Spring会等待该bean完全初始化后再返回该bean实例;否则,Spring会检查singletonFactories缓存中是否存在该bean实例的工厂。如果存在,Spring会通过该工厂创建一个新的bean实例,并将其放入earlySingletonObjects缓存中,等待其完全初始化后再放入singletonObjects缓存中。
缓存的清理
Spring的缓存机制采用的是弱引用的方式,因此当一个bean实例不再被引用时,会被垃圾回收机制回收。同时,Spring还提供了一些缓存清理的方法,以便在需要时手动清理缓存。具体而言,Spring提供了以下几种清理缓存的方法:
- destroySingletons():销毁所有singleton作用域的bean实例,并清空相关的缓存。
- clearSingletonCache():清空singletonObjects、earlySingletonObjects和singletonFactories三个缓存。
- removeSingleton(String beanName):从缓存中移除指定bean
循环依赖的解决
spring三级缓存解决循环依赖
在 Spring 中,当出现循环依赖时,通过三级缓存解决。Spring 使用三级缓存来存储正在创建的 bean,以便在创建 bean 的过程中检测循环依赖。以下是 Spring 如何使用三级缓存解决循环依赖的步骤:
- 在创建 bean 的过程中,Spring 首先会将正在创建的 bean 放入三级缓存中。
- 当创建 bean 的过程中需要依赖另一个 bean 时,Spring 会检查三级缓存中是否存在需要依赖的 bean。如果存在,Spring 会返回缓存中的 bean 实例。
- 如果三级缓存中不存在需要依赖的 bean,则 Spring 会将需要依赖的 bean 标记为正在创建中,并继续创建该 bean。在创建 bean 的过程中,Spring 会将需要依赖的 bean 放入二级缓存中。
- 如果需要依赖的 bean 还依赖当前正在创建的 bean,则 Spring 会检测二级缓存中是否存在需要依赖的 bean。如果存在,则 Spring 会返回缓存中的 bean 实例。
- 如果二级缓存中不存在需要依赖的 bean,则 Spring 会将需要依赖的 bean 标记为正在创建中,并继续创建该 bean。在创建 bean 的过程中,Spring 会将需要依赖的 bean 放入一级缓存中。
- 在创建 bean 的过程中,如果发现依赖的 bean 依然依赖当前正在创建的 bean,Spring 将抛出 BeanCurrentlyInCreationException 异常,以避免出现死循环依赖。
- 当所有依赖项都已创建完毕时,Spring 会从一级缓存中获取 bean 实例,并将其放入二级缓存和三级缓存中。此时,bean 已经可以使用。
总的来说,Spring 的三级缓存是一种很好的机制,可以解决循环依赖问题,但是如果循环依赖过于复杂,还是需要手动解决。
手动解决循环依赖
当程序报错是org.springframework.beans.factory.BeanCurrentlyInCreationException,就需要手动去解决循环依赖了。在Spring中,手动解决循环依赖可以使用以下两种方式:
- 使用构造函数注入:将循环依赖的Bean通过构造函数注入,而不是使用属性注入。这样在创建Bean时就能够通过构造函数注入其他Bean,从而避免了循环依赖。
- 使用@Lazy注解:在Spring中,@Lazy注解可以将Bean的初始化时机推迟到第一次使用时。这样,如果存在循环依赖,其中一个Bean在初始化时会直接使用另一个Bean的代理对象,从而避免了循环依赖。
⚠️需要注意的是,手动解决循环依赖可能会导致代码复杂度提高,并且需要谨慎处理,以避免出现其他问题。因此,在实际开发中,建议尽可能地避免循环依赖,以便减少出现问题的可能性。