1 什么是循环依赖
当 bean A 依赖另一个 bean B,并且 bean B 同时又依赖 bean A 的时候,就会出现循环依赖的情况:
bean A → bean B → bean A
当然,可以涉及到更多的 bean,例如:
bean A → bean B → bean C → bean D → bean E → bean A
2 在 Spring 中会发生什么
当 Spring 上下文加载所有的 bean 时,它会尝试按照依赖的顺序依次创建 bean
首先假设没有循环依赖,就像这样:bean A → bean B → bean C
此时 Spring 会先创建 bean C,然后创建 bean B(并将 bean C 注入进去),最后创建 bean A(并将 bean B 注入进去)
但是,当出现循环依赖的时候,Spring 就不知道应该先创建哪一个 bean 了
此时,如果使用的是构造器注入的方式,就会抛出 BeanCurrentlyInCreationException 异常
若使用的是其他的注入方式(例如 setter 注入),则不会出问题,此时依赖会在被需要的时候进行注入,而不是在加载 bean 的时候
3 例子
定义两个相互依赖的类
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}
当我们启动 Spring 时,会得到以下异常
BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?
4 解决办法
4.1 重新设计
很简单,重新设计类
4.2 使用 @Lazy
直接上例子
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
此时 Spring 会延迟初始化这个 bean,circB 只会被第一次需要的时候去被注入
4.3 使用 Setter 注入
这是 Spring 文档推荐的解决办法:使用 setter 注入 此时依赖会在第一次被需要的时候进行注入,例如:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
4.4 使用 @PostConstruct
这种解决办法是,在一个类上使用 @Autowired 进行注入,在另一个类使用 @PostConstruct 去 set 另一个依赖 它们之间的顺序是这样子的 构造器执行 -> @Autowired 注入 -> @PostConstruct 执行
4.5 实现 ApplicationContextAware 和 InitializingBean 接口
当一个类实现了 ApplicationContextAware 接口时,它可以得到 Spring 上下文,并获得里面的 bean 当一个类实现了 InitializingBean 接口时,它会在设置好其所有的属性之后执行一些操作,在这里我们会手动设置它的依赖关系 代码示例如下:
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
// 手动设置
circB = context.getBean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
5 总结
尽管 Spring 给我们提供了很多方式去解决循环依赖的问题,但是我们应该首先考虑不要设计出这样的类关系,因为循环依赖不是一个好的设计,当无法避免时,我们可以使用以上的解决办法。
最推荐的方法同时也是 Spring 文档推荐的方法是使用 setter 注入。