Skip to content

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;
		}

	}

}

Spring 对于循环依赖的处理方式

Released under the MIT License.