女朋友都能看懂,Spring如何解決循環(huán)依賴?
本文轉(zhuǎn)載自微信公眾號「 Java識堂」,作者李立敏。轉(zhuǎn)載本文請聯(lián)系 Java識堂公眾號。
介紹
先說一下什么是循環(huán)依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成
Spring的循環(huán)依賴有兩種場景
- 構(gòu)造器的循環(huán)依賴
 - 屬性的循環(huán)依賴
 
構(gòu)造器的循環(huán)依賴,可以在構(gòu)造函數(shù)中使用@Lazy注解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再創(chuàng)建對象完成注入
屬性的循環(huán)依賴主要是通過3個map來解決的
構(gòu)造器的循環(huán)依賴
- @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,即上面這種形式的循環(huán)依賴Spring無法解決。
我們可以在ConstructorA或者ConstructorB構(gòu)造函數(shù)的參數(shù)上加上@Lazy注解就可以解決
- @Autowired
 - public ConstructorB(@Lazy ConstructorA constructorA) {
 - this.constructorA = constructorA;
 - }
 
因為我們主要關(guān)注屬性的循環(huán)依賴,構(gòu)造器的循環(huán)依賴就不做過多分析了
屬性的循環(huán)依賴
先演示一下什么是屬性的循環(huán)依賴
- @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個Bean
屬性的循環(huán)依賴在面試中還是經(jīng)常被問到的??傮w來說也不復(fù)雜,但是涉及到Spring Bean的初始化過程,所以感覺比較復(fù)雜,我寫個demo演示一下整個過程
Spring的Bean的初始化過程其實比較復(fù)雜,為了方便理解Demo,我就把Spring Bean的初始化過程分為2部分
- bean的實例化過程,即調(diào)用構(gòu)造函數(shù)將對象創(chuàng)建出來
 - bean的初始化過程,即填充bean的各種屬性
 
bean初始化過程完畢,則bean就能被正常創(chuàng)建出來了
下面開始寫Demo,ObjectFactory接口用來生產(chǎn)Bean,和Spring中定義的接口一樣
- public interface ObjectFactory<T> {
 - T getObject();
 - }
 - public class DependencyDemo {
 - // 初始化完畢的Bean
 - private final Map<String, Object> singletonObjects =
 - new ConcurrentHashMap<>(256);
 - // 正在初始化的Bean對應(yīng)的工廠,此時對象已經(jīng)被實例化
 - 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();
 - // 已經(jīng)初始化好了,或者正在初始化
 - 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參數(shù)的含義是Spring是否允許循環(huán)依賴,默認為true
 - * 所以當allowEarlyReference設(shè)置為false的時候,當項目存在循環(huán)依賴,會啟動失敗
 - */
 - 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的循環(huán)依賴
2個Map就能搞定循環(huán)依賴,那為什么Spring要用3個Map呢?
原因其實也很簡單,當我們從singletonFactories中根據(jù)BeanName獲取相應(yīng)的ObjectFactory,然后調(diào)用getObject()這個方法返回對應(yīng)的Bean。在我們的例子中 ObjectFactory的實現(xiàn)很簡單哈,就是將實例化好的對象直接返回,但是在Spring中就沒有這么簡單了,執(zhí)行過程比較復(fù)雜,為了避免每次拿到ObjectFactory然后調(diào)用getObject(),我們直接把ObjectFactory創(chuàng)建的對象緩存起來不就行了,這樣就能提高效率了
比如A依賴B和C,B和C又依賴A,如果不做緩存那么初始化B和C都會調(diào)用A對應(yīng)的ObjectFactory的getObject()方法。如果做緩存只需要B或者C調(diào)用一次即可。
知道了思路,我們把上面的代碼改一波,加個緩存。
- public class DependencyDemo {
 - // 初始化完畢的Bean
 - private final Map<String, Object> singletonObjects =
 - new ConcurrentHashMap<>(256);
 - // 正在初始化的Bean對應(yīng)的工廠,此時對象已經(jīng)被實例化
 - private final Map<String, ObjectFactory<?>> singletonFactories =
 - new HashMap<>(16);
 - // 緩存Bean對應(yīng)的工廠生產(chǎn)好的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();
 - // 已經(jīng)初始化好了,或者正在初始化
 - 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參數(shù)的含義是Spring是否允許循環(huán)依賴,默認為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的實現(xiàn)和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的實現(xiàn)一模一樣,這個方法幾乎所有分析Spring循環(huán)依賴的文章都會提到,這次你明白工作原理是什么了把
總結(jié)一波
拿bean的時候先從singletonObjects(一級緩存)中獲取
如果獲取不到,并且對象正在創(chuàng)建中,就從earlySingletonObjects(二級緩存)中獲取
如果還是獲取不到就從singletonFactories(三級緩存)中獲取,然后將獲取到的對象放到earlySingletonObjects(二級緩存)中,并且將bean對應(yīng)的singletonFactories(三級緩存)清除
bean初始化完畢,放到singletonObjects(一級緩存)中,將bean對應(yīng)的earlySingletonObjects(二級緩存)清除















 
 
 








 
 
 
 