在Spring Boot(以及Spring框架)中,循环依赖是指两个或多个Bean互相依赖,导致Spring在创建这些Bean时无法正常进行依赖注入。例如,假设有两个类A
和B
,A
依赖于B
,而B
又依赖于A
。在这种情况下,Spring框架在尝试实例化其中一个Bean时,会发现另一个Bean尚未创建,从而产生循环依赖问题。
循环依赖的类型
循环依赖可以分为构造器循环依赖和属性循环依赖两种:
构造器循环依赖:
当两个或多个Bean通过构造器参数相互依赖时,就会出现构造器循环依赖。这种依赖情况是Spring无法处理的,因为在构造器注入阶段,Spring必须一次性完成所有Bean的实例化,因此无法解决构造器的循环依赖。
属性循环依赖(Setter循环依赖):
当Bean通过属性注入(Setter注入)方式相互依赖时,Spring可以通过创建Bean的半成品(只创建实例但不完全初始化)来解决这个问题。具体来说,Spring首先会实例化一个Bean(不调用其依赖的Setter方法),然后实例化另一个Bean,最后将依赖的属性注入回去。
Spring 如何处理循环依赖
单例Bean(Singleton Scope):
Spring默认是允许单例Bean的属性循环依赖的。它的解决机制是:Spring在创建Bean时,会先创建Bean的一个未完成的实例(放入一级缓存),即创建对象但不完全初始化(还没有设置属性)。然后,它会将这个未初始化的对象放入一个三级缓存,供后续可能需要的依赖查找使用。如果在实例化其他Bean时需要依赖这个Bean,Spring会通过三级缓存获取未完成的实例,并继续完成注入过程。
构造器注入:
Spring无法处理构造器的循环依赖,因为在构造器注入时,Bean必须在构造函数中完全初始化。由于无法在构造函数执行之前就创建依赖对象,Spring在这种情况下会抛出BeanCurrentlyInCreationException
。
非单例Bean(Prototype Scope):
Spring无法解决非单例Bean的循环依赖。在Prototype作用域下,Spring不管理Bean的生命周期,因此无法像单例Bean一样进行缓存处理。此时,如果出现循环依赖,会抛出异常。
解决循环依赖的方法
重构代码,消除循环依赖:这是最根本的解决方式。检查是否有设计上的问题导致了循环依赖,尽量将依赖解耦。可以通过引入中介类、事件机制或者工厂模式等方式重构代码。
使用Setter注入:如果循环依赖无法避免,可以尽量使用Setter注入而不是构造器注入,因为Spring能够处理属性级别的循环依赖。
使用@Lazy
注解:对于某些Bean依赖,可以使用@Lazy
注解,告诉Spring在需要时才初始化该Bean,从而打破循环依赖。例如:
@Service
public class A {
private final B b;
@Autowired
public A(@Lazy B b) {
this.b = b;
}
}
使用ApplicationContextAware:如果需要通过构造器注入的方式解决循环依赖,可以通过实现ApplicationContextAware
接口,在运行时手动获取依赖Bean。