Appearance
Spring 循环依赖
什么是循环依赖
当 A 依赖 B,而 B 又依赖 A 时,它们之间就发生了循环依赖。
A -> B -> A
出现循环依赖的两种情况
构造方法注入(Constructor Injection)
同是构造方法注入时发生的循环依赖是无法被解决的,因为它们的实例化需要依赖对方的注入。
java
static class A {
private B b;
public A(B b) {
super();
this.b = b;
}
}
static class B {
private A a;
public B(A a) {
super();
this.a = a;
}
}
设值方法注入(Setter Injection)
设值方法注入时发生的循环依赖是能够被解决的,因为它们的实例化不需要依赖对方的注入。
java
static class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
static class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
出现循环依赖的处理方式
我们都知道 Spring 等框架是通过反射的方式创建 Bean
的。而且为了保证性能,一般都会采用单例模式创建 Bean
。下面就来看看在使用反射创建 Bean
的时候是如何解决循环依赖的。
设值方法注入(Setter Injection)时循环依赖的处理方式
java
package study.helloworld.something4j._2022._01._14;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class CircularDependencyWithSetter {
public static void main(String[] args) {
Stream.of(A.class, B.class)
.forEach(CircularDependencyWithSetter::getSingleton);
System.out.println("A instance is a singleton: "
+ (getSingleton(A.class) == getSingleton(B.class).getA()));
System.out.println("B instance is a singleton: "
+ (getSingleton(B.class) == getSingleton(A.class).getB()));
}
private static final Map<String, Object> SINGLETON_OBJECT_MAP = new HashMap<String, Object>();
@SuppressWarnings("unchecked")
static <T> T getSingleton(Class<T> beanClass) {
String beanName = beanClass.getSimpleName();
Object object = SINGLETON_OBJECT_MAP.get(beanName);
if (object == null) {
try {
object = beanClass.getDeclaredConstructor().newInstance();
SINGLETON_OBJECT_MAP.put(beanName, object);
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
field.set(object, getSingleton(fieldClass));
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
return (T) object;
}
static class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
static class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
}
运行结果:
java
A instance is a singleton: true
B instance is a singleton: true
可以看到,我们只用了一个 Map
就解决了设值方法注入时循环依赖的问题。
构造方法注入(Constructor Injection)时循环依赖的处理方式
我们已经知道同是构造方法注入时发生的循环依赖是无法被解决的。为了验证,考虑下面代码:
java
package study.helloworld.something4j._2022._01._14;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class CircularDependencyWithConstructor {
public static void main(String[] args) {
Stream.of(A.class, B.class)
.forEach(CircularDependencyWithSetter::getSingleton);
System.out.println("A instance is a singleton: "
+ (getSingleton(A.class) == getSingleton(B.class).getA()));
System.out.println("B instance is a singleton: "
+ (getSingleton(B.class) == getSingleton(A.class).getB()));
}
private static final Map<String, Object> SINGLETON_OBJECT_MAP = new HashMap<String, Object>();
@SuppressWarnings("unchecked")
static <T> T getSingleton(Class<T> beanClass) {
String beanName = beanClass.getSimpleName();
Object object = SINGLETON_OBJECT_MAP.get(beanName);
if (object == null) {
try {
Constructor<?> constructor = beanClass.getConstructors()[0];
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] parameterObjects = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
parameterObjects[i] = getSingleton(parameterTypes[i]);
}
constructor.newInstance(parameterObjects);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return (T) object;
}
static class A {
private B b;
public A(B b) {
super();
this.b = b;
}
public B getB() {
return b;
}
}
static class B {
private A a;
public B(A a) {
super();
this.a = a;
}
public A getA() {
return a;
}
}
}
这会得到一个 java.lang.StackOverflowError
错误。
为了及时发现此类问题,可以使用一个 Set
来存放还在构造中的 Bean
的 beanName。如果 beanName 已经存在于 Set
,那么直接抛出 java.lang.RuntimeException
。
java
package study.helloworld.something4j._2022._01._14;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class CircularDependencyWithConstructor {
public static void main(String[] args) {
Stream.of(A.class, B.class)
.forEach(CircularDependencyWithSetter::getSingleton);
System.out.println("A instance is a singleton: "
+ (getSingleton(A.class) == getSingleton(B.class).getA()));
System.out.println("B instance is a singleton: "
+ (getSingleton(B.class) == getSingleton(A.class).getB()));
}
private static final Map<String, Object> SINGLETON_OBJECT_MAP = new HashMap<String, Object>();
private static final Set<String> SINGLETON_IN_CREATION_SET = new HashSet<String>();
@SuppressWarnings("unchecked")
static <T> T getSingleton(Class<T> beanClass) {
String beanName = beanClass.getSimpleName();
Object object = SINGLETON_OBJECT_MAP.get(beanName);
if (object == null) {
if (!SINGLETON_IN_CREATION_SET.add(beanName)) {
throw new RuntimeException("Error creating bean with name '"
+ beanName + "': "
+ "Requested bean is currently in creation: Is there an unresolvable circular reference?");
}
try {
Constructor<?> constructor = beanClass.getConstructors()[0];
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] parameterObjects = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
parameterObjects[i] = getSingleton(parameterTypes[i]);
}
constructor.newInstance(parameterObjects);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return (T) object;
}
static class A {
private B b;
public A(B b) {
super();
this.b = b;
}
public B getB() {
return b;
}
}
static class B {
private A a;
public B(A a) {
super();
this.a = a;
}
public A getA() {
return a;
}
}
}