循环依赖的定义
循环依赖是指两个或多个Bean相互依赖,形成闭环。例如Bean A依赖Bean B,Bean B又依赖Bean A。Spring Boot默认支持单例作用域的循环依赖,但原型作用域不支持。
使用构造器注入的解决方案
构造器注入是解决循环依赖的首选方式。通过在构造器中明确依赖关系,可以避免循环依赖问题。这种方式在启动时就会抛出异常,便于早期发现问题。
java
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
使用Setter注入的解决方案
Setter注入通过方法注入依赖,Spring会先创建Bean实例,再通过Setter方法注入依赖。这种方式允许循环依赖的存在,但可能导致运行时问题。
java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
使用@Lazy注解延迟加载
@Lazy注解可以延迟依赖的初始化,打破循环依赖的闭环。适用于某些特定场景,但可能增加调试难度。
java
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
重新设计代码结构
从根本上解决循环依赖的方法是重新设计组件关系。常见的重构方式包括:
- 提取公共逻辑到第三方服务
- 使用事件驱动架构
- 引入接口隔离
使用ApplicationContext获取Bean
在极端情况下,可以通过ApplicationContext手动获取Bean,但不推荐作为常规解决方案。
java
@Service
public class ServiceA {
@Autowired
private ApplicationContext context;
public ServiceB getServiceB() {
return context.getBean(ServiceB.class);
}
}
@Service
public class ServiceB {
@Autowired
private ApplicationContext context;
public ServiceA getServiceA() {
return context.getBean(ServiceA.class);
}
}
配置允许循环依赖
Spring Boot 2.6+默认禁用了循环依赖,如需允许可以在配置中设置:
properties
spring.main.allow-circular-references=true
但这种方法只是临时解决方案,长期应优化代码结构。