先说明:推荐使用构造注入依赖的方式来解决循环依赖。还有不要把实例化与初始化搞混了,注意这两个的时机。
Spring中循环依赖的解决方案主要有以下几种:
构造注入不会循环依赖的原因在于,Spring在创建bean实例时,会立即解析依赖关系,并将依赖对象注入到bean实例中。例如,以下代码中,A类和B类之间存在循环依赖:
public class A {
private B b;
//步骤1.创建A的实例,未初始化
//步骤3获取到B的实例并对A进行初始化
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
//步骤2创建B的实例未初始化
//步骤4获取到B的实例并对A进行初始化
public B(A a) {
this.a = a;
}
}
构造注入的时候创建B的实例也需要先创建A的实例。但是,Spring在创建bean实例时,会使用一种特殊的机制来解决循环依赖的问题。
Spring在创建bean实例时,会使用循环依赖解析器(Circular Dependency Resolver)来解决循环依赖的问题。循环依赖解析器会按照一定的顺序来创建bean实例。对于构造注入,循环依赖解析器会按照如下顺序来创建bean实例:
1. 创建A的实例,但是A的实例还没有初始化。
2. 创建B的实例,但是B的实例还没有初始化。
3. 初始化A的实例,此时A的实例可以访问到B的实例。
4. 初始化B的实例,此时B的实例可以访问到A的实例。
流程大概是这样,就不会出现循环依赖的问题了。
相比之下,setter方法注入是在bean实例创建完成后再解析依赖关系,并将依赖对象注入到bean实例中。例如,以下代码中,A类和B类之间也存在循环依赖:
Java
public class A {
private B b;
//步骤1.他是先创建A的实例再创建B的实例(在调用A的Set方法之前肯定是已经有了A的实例)
//步骤3.将B实例注入到A就会报错,因为A实例已经存在了
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
//步骤2.在创建B的实例的时候A的实例已经存在了,可以直接获取到B的实例
public void setA(A a) {
this.a = a;
}
}
当Spring创建A类的实例时,A类的实例已经创建完成了。但是,B类的实例还没有创建。Spring会在调用setB方法时,解析依赖关系,并将B类的实例注入到A类中。但是,由于A类的实例已经创建完成了,所以Spring无法创建B类的实例。这样,就出现了循环依赖的问题。
大概过程如下:
1. 创建A的实例
2. 将B注入到A中
3. 创建B的实例
因此,如果要避免循环依赖,建议使用构造注入。
延迟加载是指在bean实例真正需要使用依赖对象时才进行依赖注入。延迟加载可以避免循环依赖的问题,但可能会导致性能下降。
示例
Java
@Lazy
public class A {
private B b;
public A() {
}
public B getB() {
if (b == null) {
b = new B();
}
return b;
}
}
public class B {
private A a;
public B() {
}
public A getA() {
if (a == null) {
a = new A();
}
return a;
}
}
上述代码中,A类和B类之间存在循环依赖。如果使用延迟加载,Spring会在A类或B类真正需要使用依赖对象时才进行依赖注入。这样就不会出现循环依赖的问题
Spring Boot提供了@AutowiredAnnotationBeanPostProcessor类,可以用于解决循环依赖的问题。该类会在bean实例创建完成后再解析依赖关系,并将依赖对象注入到bean实例中。
示例
Java
@AutowiredAnnotationBeanPostProcessor
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().getName().equals("com.example.A")) {
A a = (A) bean;
a.setB(new B());
}
return bean;
}
}
上述代码中,A类和B类之间存在循环依赖。如果使用@AutowiredAnnotationBeanPostProcessor类,Spring会在A类或B类真正需要使用依赖对象时再进行依赖注入。这样就不会出现循环依赖的问题。
上面就是对循环依赖的解决方式分析,推荐使用构造方式注入。