先說一下什麼是循環依賴,Spring在初始化A的時候須要注入B,而初始化B的時候須要注入A,在Spring啓動後這2個Bean都要被初始化完成java
Spring的循環依賴有兩種場景面試
構造器的循環依賴,能夠在構造函數中使用@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部分
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]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