Spring如何解決循環依賴?

在這裏插入圖片描述

介紹

先說一下什麼是循環依賴,Spring在初始化A的時候須要注入B,而初始化B的時候須要注入A,在Spring啓動後這2個Bean都要被初始化完成java

Spring的循環依賴有兩種場景面試

  1. 構造器的循環依賴
  2. 屬性的循環依賴

構造器的循環依賴,能夠在構造函數中使用@Lazy註解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再建立對象完成注入spring

屬性的循環依賴主要是經過3個map來解決的緩存

構造器的循環依賴

@Component
public class ConstructorA {

	private ConstructorB constructorB;

	@Autowired
	public ConstructorA(ConstructorB constructorB) {
		this.constructorB = constructorB;
	}
}
@Component
public class ConstructorB {

	private ConstructorA constructorA;

	@Autowired
	public ConstructorB(ConstructorA constructorA) {
		this.constructorA = constructorA;
	}
}
@Configuration
@ComponentScan("com.javashitang.dependency.constructor")
public class ConstructorConfig {
}
public class ConstructorMain {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context =
				new AnnotationConfigApplicationContext(ConstructorConfig.class);
		System.out.println(context.getBean(ConstructorA.class));
		System.out.println(context.getBean(ConstructorB.class));
	}
}

運行ConstructorMain的main方法的時候會在第一行就報異常,說明Spring沒辦法初始化全部的Bean,即上面這種形式的循環依賴Spring沒法解決。bash

咱們能夠在ConstructorA或者ConstructorB構造函數的參數上加上@Lazy註解就能夠解決函數

@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {
	this.constructorA = constructorA;
}

由於咱們主要關注屬性的循環依賴,構造器的循環依賴就不作過多分析了post

屬性的循環依賴

先演示一下什麼是屬性的循環依賴測試

@Component
public class FieldA {

	@Autowired
	private FieldB fieldB;
}
@Component
public class FieldB {

	@Autowired
	private FieldA fieldA;
}
@Configuration
@ComponentScan("com.javashitang.dependency.field")
public class FieldConfig {
}
public class FieldMain {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context =
				new AnnotationConfigApplicationContext(FieldConfig.class);
		// com.javashitang.dependency.field.FieldA@3aa9e816
		System.out.println(context.getBean(FieldA.class));
		// com.javashitang.dependency.field.FieldB@17d99928
		System.out.println(context.getBean(FieldB.class));
	}
}

Spring容器正常啓動,能獲取到FieldA和FieldB這2個Beanthis

屬性的循環依賴在面試中仍是常常被問到的。整體來講也不復雜,可是涉及到Spring Bean的初始化過程,因此感受比較複雜,我寫個demo演示一下整個過程代理

Spring的Bean的初始化過程其實比較複雜,爲了方便理解Demo,我就把Spring Bean的初始化過程分爲2部分

  1. bean的實例化過程,即調用構造函數將對象建立出來
  2. bean的初始化過程,即填充bean的各類屬性

bean初始化過程完畢,則bean就能被正常建立出來了

下面開始寫Demo,ObjectFactory接口用來生產Bean,和Spring中定義的接口同樣

public interface ObjectFactory<T> {
	T getObject();
}
public class DependencyDemo {

	// 初始化完畢的Bean
	private final Map<String, Object> singletonObjects =
			new ConcurrentHashMap<>(256);

	// 正在初始化的Bean對應的工廠,此時對象已經被實例化
	private final Map<String, ObjectFactory<?>> singletonFactories =
			new HashMap<>(16);

	// 存放正在初始化的Bean,對象尚未被實例化以前就放進來了
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

	public  <T> T getBean(Class<T> beanClass) throws Exception {
		// 類名爲Bean的名字
		String beanName = beanClass.getSimpleName();
		// 已經初始化好了,或者正在初始化
		Object initObj = getSingleton(beanName, true);
		if (initObj != null) {
			return (T) initObj;
		}
		// bean正在被初始化
		singletonsCurrentlyInCreation.add(beanName);
		// 實例化bean
		Object object = beanClass.getDeclaredConstructor().newInstance();
		singletonFactories.put(beanName, () -> {
			return object;
		});
		// 開始初始化bean,即填充屬性
		Field[] fields = object.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			// 獲取須要注入字段的class
			Class<?> fieldClass = field.getType();
			field.set(object, getBean(fieldClass));
		}
		// 初始化完畢
		singletonObjects.put(beanName, object);
		singletonsCurrentlyInCreation.remove(beanName);
		return (T) object;
	}

	/**
	 * allowEarlyReference參數的含義是Spring是否容許循環依賴,默認爲true
	 * 因此當allowEarlyReference設置爲false的時候,當項目存在循環依賴,會啓動失敗
	 */
	public Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null 
				&& isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory =
							this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
					}
				}
			}
		}
		return singletonObject;
	}

	/**
	 * 判斷bean是否正在被初始化
	 */
	public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

}

測試一波

public static void main(String[] args) throws Exception {
	DependencyDemo dependencyDemo = new DependencyDemo();
	// 僞裝掃描出來的對象
	Class[] classes = {A.class, B.class};
	// 僞裝項目初始化全部bean
	for (Class aClass : classes) {
		dependencyDemo.getBean(aClass);
	}
	// true
	System.out.println(
			dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class));
	// true
	System.out.println(
			dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class));
}

是否是很簡單?咱們只用了2個map就搞定了Spring的循環依賴

2個Map就能搞定循環依賴,那爲何Spring要用3個Map呢?

緣由其實也很簡單,當咱們從singletonFactories中根據BeanName獲取相應的ObjectFactory,而後調用getObject()這個方法返回對應的Bean。在咱們的例子中
ObjectFactory的實現很簡單哈,就是將實例化好的對象直接返回,可是在Spring中就沒有這麼簡單了,執行過程比較複雜,爲了不每次拿到ObjectFactory而後調用getObject(),咱們直接把ObjectFactory建立的對象緩存起來不就好了,這樣就能提升效率了

好比A依賴B和C,B和C又依賴A,若是不作緩存那麼初始化B和C都會調用A對應的ObjectFactory的getObject()方法。若是作緩存只須要B或者C調用一次便可。

知道了思路,咱們把上面的代碼改一波,加個緩存。

public class DependencyDemo {

	// 初始化完畢的Bean
	private final Map<String, Object> singletonObjects =
			new ConcurrentHashMap<>(256);

	// 正在初始化的Bean對應的工廠,此時對象已經被實例化
	private final Map<String, ObjectFactory<?>> singletonFactories =
			new HashMap<>(16);

	// 緩存Bean對應的工廠生產好的Bean
	private final Map<String, Object> earlySingletonObjects =
			new HashMap<>(16);

	// 存放正在初始化的Bean,對象尚未被實例化以前就放進來了
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

	public  <T> T getBean(Class<T> beanClass) throws Exception {
		// 類名爲Bean的名字
		String beanName = beanClass.getSimpleName();
		// 已經初始化好了,或者正在初始化
		Object initObj = getSingleton(beanName, true);
		if (initObj != null) {
			return (T) initObj;
		}
		// bean正在被初始化
		singletonsCurrentlyInCreation.add(beanName);
		// 實例化bean
		Object object = beanClass.getDeclaredConstructor().newInstance();
		singletonFactories.put(beanName, () -> {
			return object;
		});
		// 開始初始化bean,即填充屬性
		Field[] fields = object.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			// 獲取須要注入字段的class
			Class<?> fieldClass = field.getType();
			field.set(object, getBean(fieldClass));
		}
		singletonObjects.put(beanName, object);
		singletonsCurrentlyInCreation.remove(beanName);
		earlySingletonObjects.remove(beanName);
		return (T) object;
	}

	/**
	 * allowEarlyReference參數的含義是Spring是否容許循環依賴,默認爲true
	 */
	public Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null
				&& isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory =
							this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
}

咱們寫的getSingleton的實現和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的實現如出一轍,這個方法幾乎全部分析Spring循環依賴的文章都會提到,此次你明白工做原理是什麼了把

總結一波

  1. 拿bean的時候先從singletonObjects(一級緩存)中獲取
  2. 若是獲取不到,而且對象正在建立中,就從earlySingletonObjects(二級緩存)中獲取
  3. 若是仍是獲取不到就從singletonFactories(三級緩存)中獲取,而後將獲取到的對象放到earlySingletonObjects(二級緩存)中,而且將bean對應的singletonFactories(三級緩存)清除
  4. bean初始化完畢,放到singletonObjects(一級緩存)中,將bean對應的earlySingletonObjects(二級緩存)清除

歡迎關注

在這裏插入圖片描述

參考博客

[1]https://mp.weixin.qq.com/s/gBr3UfC1HRcw4U-ZMmtRaQ
[2]https://mp.weixin.qq.com/s/5mwkgJB7GyLdKDgzijyvXw
比較詳細
[1]https://zhuanlan.zhihu.com/p/84267654
[2]http://www.javashuo.com/article/p-vpyquwup-ck.html

相關文章
相關標籤/搜索