在 Spring 中,循环依赖是指两个或多个 bean 之间相互依赖,形成一个循环引用的情况。Spring 对于循环依赖有一定的处理机制,但也存在一些限制。
一、Spring 处理循环依赖的方式
-
三级缓存解决构造函数注入的循环依赖:
- Spring 首先创建一个正在创建中的 bean 对象,并将其放入一个 "正在创建的 bean 缓存"(一级缓存)中。
- 当遇到循环依赖时,Spring 会尝试从 "正在创建的 bean 缓存" 中获取 bean 对象,如果获取到,则说明循环依赖已经被解决。
- 如果在 "正在创建的 bean 缓存" 中没有找到 bean 对象,Spring 会从 "早期单例对象缓存"(二级缓存)中获取 bean 对象。如果获取到,则说明该 bean 对象已经被创建,并且是一个单例对象,可以直接返回。
- 如果在 "早期单例对象缓存" 中也没有找到 bean 对象,Spring 会从 "单例对象缓存"(三级缓存)中获取 bean 对象。如果获取到,则说明该 bean 对象已经被创建,并且是一个单例对象,可以直接返回。
-
setter 注入的循环依赖处理:
- Spring 在处理 setter 注入的循环依赖时,会先创建 bean 对象,然后再设置其依赖的属性。
- 当遇到循环依赖时,Spring 会先创建一个 bean 对象,并将其放入 "正在创建的 bean 缓存" 中。然后,Spring 会尝试从 "正在创建的 bean 缓存" 中获取依赖的 bean 对象。如果获取到,则说明循环依赖已经被解决。如果没有获取到,则继续创建依赖的 bean 对象,并将其放入 "正在创建的 bean 缓存" 中。
二、Spring 处理循环依赖的限制
-
构造函数注入的限制:
- 如果循环依赖中的 bean 对象是通过构造函数注入的,并且构造函数中存在循环依赖的参数,那么 Spring 无法解决这种循环依赖。
- 例如,如果有两个 bean 对象 A 和 B,A 的构造函数中依赖 B,B 的构造函数中依赖 A,那么 Spring 无法创建这两个 bean 对象。
-
原型 bean 的限制:
- Spring 对于原型(prototype)bean 的循环依赖无法处理。
- 原型 bean 每次请求都会创建一个新的实例,因此无法在创建过程中解决循环依赖。
三、解决循环依赖的方法
-
重构代码:
- 检查代码中的循环依赖关系,尝试通过重构代码来消除循环依赖。
- 可以将循环依赖的部分提取到一个单独的 bean 中,或者使用依赖注入的替代方法,如 setter 注入或方法注入。
-
使用 @Lazy 注解:
- 在循环依赖的 bean 中,可以使用
@Lazy
注解来延迟加载依赖的 bean。 - 这样,当 Spring 创建 bean 对象时,不会立即创建依赖的 bean 对象,而是在需要时才创建。
- 这种方法可以在一定程度上解决循环依赖问题,但需要注意的是,使用
@Lazy
注解可能会影响性能,因为依赖的 bean 对象只有在需要时才会被创建。
- 在循环依赖的 bean 中,可以使用
-
使用 @DependsOn 注解:
@DependsOn
注解可以指定一个 bean 的初始化顺序。- 在循环依赖的情况下,可以使用
@DependsOn
注解来确保一个 bean 在另一个 bean 之前被初始化。 - 例如,如果有两个 bean 对象 A 和 B,A 的初始化依赖于 B,那么可以在 A 的类上使用
@DependsOn("B")
注解,确保 B 在 A 之前被初始化。
总之,Spring 对于循环依赖有一定的处理机制,但也存在一些限制。在实际应用中,应该尽量避免循环依赖的情况发生,如果无法避免,可以通过重构代码、使用@Lazy
注解或@DependsOn
注解等方法来解决循环依赖问题。
四、MyBatis-Plus的继承特性导致循环依赖(Spring的三级缓存机制无法正常工作):
在使用MyBatis-Plus时,服务实现类(如PXXServiceImpl)通常会继承ServiceImpl类,并实现对应的服务接口(如IPXXService)。这种继承关系可能会导致Spring在创建Bean时遇到循环依赖的问题
。
Spring的三级缓存机制:Spring的三级缓存机制主要解决的是字段注入(Field Injection)或setter方法注入(Setter Injection)引起的循环依赖问题。对于构造函数注入(Constructor Injection),Spring无法解决循环依赖,因为构造函数注入要求所有依赖在Bean创建时必须是可用的
。
MyBatis-Plus的Bean包装:有观点认为,由于Service集成了MyBatis-Plus的基类,Spring可能将其视为"包装过的类",这可能导致Spring的三级缓存机制无法正常工作,从而引发循环依赖问题。
解决方案
使用@Lazy注解:在Spring中,可以通过在注入的字段上添加@Lazy注解来延迟Bean的加载,这样可以避免循环依赖的问题。
抽象中间Service:可以通过创建一个中间Service层来避免直接的循环依赖,这样可以减少直接的依赖关系,从而避免循环依赖。
使用Setter方法注入:而不是字段注入或构造函数注入,Setter方法注入可以更好地与Spring的三级缓存机制配合,解决循环依赖问题。
重新设计架构:如果可能,重新设计服务层的架构,减少服务之间的直接依赖,或者将一些共用的服务抽象出来,以减少循环依赖的可能性。
综上所述,MyBatis-Plus的继承特性和Spring的Bean包装可能导致Spring的三级缓存机制无法完全解决循环依赖问题。您可以尝试上述解决方案来避免或解决循环依赖问题。