Spring中的循環依賴解決詳解

  • 2019 年 10 月 3 日
  • 筆記

前言

        說起Spring中循環依賴的解決辦法,相信很多園友們都或多或少的知道一些,但當真的要詳細說明的時候,可能又沒法一下將它講清楚。本文就試著盡自己所能,對此做出一個較詳細的解讀。另,需注意一點,下文中會出現類的實例化跟類的初始化兩個短語,為怕園友迷惑,事先聲明一下,本文的實例化是指剛執行完構造器將一個對象new出來,但還未填充屬性值的狀態,而初始化是指完成了屬性的依賴注入。

一、先說說Spring解決的循環依賴是什麼

        Java中的循環依賴分兩種,一種是構造器的循環依賴,另一種是屬性的循環依賴。

        構造器的循環依賴就是在構造器中有屬性循環依賴,如下所示的兩個類就屬於構造器循環依賴:

 1 @Service   2 public class Student {   3     @Autowired   4     private Teacher teacher;   5   6     public Student (Teacher teacher) {   7         System.out.println("Student init1:" + teacher);   8     }   9  10     public void learn () {  11         System.out.println("Student learn");  12     }  13 }

 1 @Service   2 public class Teacher {   3     @Autowired   4     private Student student;   5   6     public Teacher (Student student) {   7         System.out.println("Teacher init1:" + student);   8   9     }  10  11     public void teach () {  12         System.out.println("teach:");  13         student.learn();  14     }  15 }

        這種循環依賴沒有什麼解決辦法,因為JVM虛擬機在對類進行實例化的時候,需先實例化構造器的參數,而由於循環引用這個參數無法提前實例化,故只能拋出錯誤。

        Spring解決的循環依賴就是指屬性的循環依賴,如下所示:

 1 @Service   2 public class Teacher {   3     @Autowired   4     private Student student;   5   6     public Teacher () {   7         System.out.println("Teacher init1:" + student);   8   9     }  10  11     public void teach () {  12         System.out.println("teach:");  13         student.learn();  14     }  15  16 }

 1 @Service   2 public class Student {   3     @Autowired   4     private Teacher teacher;   5   6     public Student () {   7         System.out.println("Student init:" + teacher);   8     }   9  10     public void learn () {  11         System.out.println("Student learn");  12     }  13 }

測試掃描類:

1 @ComponentScan(value = "myPackage")  2 public class ScanConfig {  3  4 }

測試啟動類:

1 public class SpringTest {  2  3     public static void main(String[] args) {  4         AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);  5         applicationContext.getBean(Teacher.class).teach();  6  7     }  8 }

測試類執行結果:

1 Student init:null  2 Teacher init:null  3 teach:  4 Student learn

        可以看到,在構造器執行的時候未完成屬性的注入,而在調用方法的時候已經完成了注入。下面就一起看看Spring內部是在何時完成的屬性注入,又是如何解決的循環依賴。

二、循環依賴與屬性注入

1、對於非懶載入的類,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包掃描以及bean的初始化,下面就一起追蹤下去。

1 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {  2         // 其他程式碼  3  4         // Instantiate all remaining (non-lazy-init) singletons.  5         beanFactory.preInstantiateSingletons();  6     }

可以看到調用了beanFactory的一個方法,此處的beanFactory就是指我們最常見的那個DefaultListableBeanFactory,下面看它裡面的這個方法。

2、DefaultListableBeanFactory的preInstantiateSingletons方法

 1 public void preInstantiateSingletons() throws BeansException {   2   3         List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);   4   5         // Trigger initialization of all non-lazy singleton beans...   6         for (String beanName : beanNames) {   7             RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);   8             if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 判斷為非抽象類、是單例、非懶載入 才給初始化   9                 if (isFactoryBean(beanName)) {  10                     // 無關程式碼(針對FactoryBean的處理)  11                 }  12                 else {  13                     // 重要!!!普通bean就是在這裡初始化的  14                     getBean(beanName);  15                 }  16             }  17         }  18  19         // 其他無關程式碼  20     }

可以看到,就是在此方法中循環Spring容器中所有的bean,依次對其進行初始化,初始化的入口就是getBean方法

3、AbstractBeanFactory的getBean跟doGetBean方法

追蹤getBean方法:

1 public Object getBean(String name) throws BeansException {  2         return doGetBean(name, null, null, false);  3     }

可見引用了重載的doGetBean方法,繼續追蹤之:

 1 protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,   2             @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {   3   4         final String beanName = transformedBeanName(name);   5         Object bean;   6   7          // 方法1)從三個map中獲取單例類   8         Object sharedInstance = getSingleton(beanName);   9         // 省略無關程式碼  10         }  11         else {  12             // 如果是多例的循環引用,則直接報錯  13             if (isPrototypeCurrentlyInCreation(beanName)) {  14                 throw new BeanCurrentlyInCreationException(beanName);  15             }  16             // 省略若干無關程式碼  17             try {  18                 // Create bean instance.  19                 if (mbd.isSingleton()) {  20                     // 方法2) 獲取單例對象  21                     sharedInstance = getSingleton(beanName, () -> {  22                         try { //方法3) 創建ObjectFactory中getObject方法的返回值  23                             return createBean(beanName, mbd, args);  24                         }  25                         catch (BeansException ex) {  26                             // Explicitly remove instance from singleton cache: It might have been put there  27                             // eagerly by the creation process, to allow for circular reference resolution.  28                             // Also remove any beans that received a temporary reference to the bean.  29                             destroySingleton(beanName);  30                             throw ex;  31                         }  32                     });  33                     bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);  34                 }  35          }  36         // 省略若干無關程式碼  37         return (T) bean;  38     }

該方法比較長,對於解決循環引用來說,上面標出來的3個方法起到了至關重要的作用,下面我們挨個攻克。

3.1) getSingleton(beanName)方法注意該方法跟方法2)是重載方法,名字一樣內部邏輯卻大相徑庭。

 1 protected Object getSingleton(String beanName, boolean allowEarlyReference) {   2         Object singletonObject = this.singletonObjects.get(beanName);// 步驟A   3         if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {   4             synchronized (this.singletonObjects) {   5                 singletonObject = this.earlySingletonObjects.get(beanName);// 步驟B   6                 if (singletonObject == null && allowEarlyReference) {   7                     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 步驟C   8                     if (singletonFactory != null) {   9                         singletonObject = singletonFactory.getObject();  10                         this.earlySingletonObjects.put(beanName, singletonObject);  11                         this.singletonFactories.remove(beanName);  12                     }  13                 }  14             }  15         }  16         return singletonObject;  17     }

        通過上面的步驟可以看出這三個map的優先順序。其中singletonObjects裡面存放的是初始化之後的單例對象;earlySingletonObjects中存放的是一個已完成實例化未完成初始化的早期單例對象;而singletonFactories中存放的是ObjectFactory對象,此對象的getObject方法返回值即剛完成實例化還未開始初始化的單例對象。所以先後順序是,單例對象先存在於singletonFactories中,後存在於earlySingletonObjects中,最後初始化完成後放入singletonObjects中

        當debug到此處時,以上述Teacher和Student兩個循環引用的類為例,如果第一個走到這一步的是Teacher,則從此處這三個map中get到的值都是空,因為還未添加進去。這個方法主要是給循環依賴中後來過來的對象用。

3.2)getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

 1 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {   2         Assert.notNull(beanName, "Bean name must not be null");   3         synchronized (this.singletonObjects) {   4             Object singletonObject = this.singletonObjects.get(beanName);   5             if (singletonObject == null) {   6                 // 省略無關程式碼   7                 beforeSingletonCreation(beanName); // 步驟A   8                 boolean newSingleton = false;   9                 // 省略無關程式碼  10                 try {  11                     singletonObject = singletonFactory.getObject();// 步驟B  12                     newSingleton = true;  13                 }  14                 // 省略無關程式碼  15                 finally {  16                     if (recordSuppressedExceptions) {  17                         this.suppressedExceptions = null;  18                     }  19                     afterSingletonCreation(beanName);// 步驟C  20                 }  21                 if (newSingleton) {  22                     addSingleton(beanName, singletonObject);// 步驟D  23                 }  24             }  25             return singletonObject;  26         }  27     }

獲取單例對象的主要邏輯就是此方法實現的,主要分為上面四個步驟,繼續挨個看:

步驟A:

1 protected void beforeSingletonCreation(String beanName) {  2         // 判斷,並首次將beanName即teacher放入singletonsCurrentlyInCreation中  3         if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {  4             throw new BeanCurrentlyInCreationException(beanName);  5         }  6     }

步驟C:

1 protected void afterSingletonCreation(String beanName) {  2         // 得到單例對象後,再講beanName從singletonsCurrentlyInCreation中移除  3         if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {  4             throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");  5         }  6     }

步驟D:

1 protected void addSingleton(String beanName, Object singletonObject) {  2         synchronized (this.singletonObjects) {  3             this.singletonObjects.put(beanName, singletonObject);//添加單例對象到map中  4             this.singletonFactories.remove(beanName);//從早期暴露的工廠中移除,此map在解決循環依賴中發揮了關鍵的作用  5             this.earlySingletonObjects.remove(beanName);//從早期暴露的對象map中移除  6             this.registeredSingletons.add(beanName);//添加到已註冊的單例名字集合中  7         }  8     }

步驟B:

        此處調用了ObjectFactory的getObject方法,此方法是在哪裡實現的呢?返回的又是什麼?且往回翻,找到3中的方法3,對java8函數式編程有過了解的園友應該能看出來,方法3 【createBean(beanName, mbd, args)】的返回值就是getObject方法的返回值,即方法3返回的就是我們需要的單例對象,下面且追蹤方法3而去。

3.3)AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]) 方法

 1 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)   2             throws BeanCreationException {   3   4         // 省略無關程式碼   5         try {   6             Object beanInstance = doCreateBean(beanName, mbdToUse, args);   7             return beanInstance;   8         }   9         // 省略無關程式碼  10     }

去掉無關程式碼之後,關鍵方法只有doCreateBean方法,追蹤之:

 1 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)   2             throws BeanCreationException {   3   4         BeanWrapper instanceWrapper = null;   5         // 省略程式碼   6         if (instanceWrapper == null) {   7             // 實例化bean   8             instanceWrapper = createBeanInstance(beanName, mbd, args);   9         }  10         boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&  11                 isSingletonCurrentlyInCreation(beanName));  12         if (earlySingletonExposure) {  13             // 重點!!!將實例化的對象添加到singletonFactories中   14             addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));  15         }  16         // 初始化bean  17         Object exposedObject = bean;  18         try {  19             populateBean(beanName, mbd, instanceWrapper);//也很重要  20             exposedObject = initializeBean(beanName, exposedObject, mbd);  21         }  22         // 省略無關程式碼  23         return exposedObject;  24 }

        上面注釋中標出的重點是此方法的關鍵。在addSingletonFactory方法中,將第二個參數ObjectFactory存入了singletonFactories供其他對象依賴時調用。然後下面的populateBean方法對剛實例化的bean進行屬性注入(該方法關聯較多,本文暫時不展開追蹤了,有興趣的園友自行查看即可),如果遇到Spring中的對象屬性,則再通過getBean方法獲取該對象。至此,循環依賴在Spring中的處理過程已經追溯完畢,下面我們總結一下。

小結

        屬性注入主要是在populateBean方法中進行的。對於循環依賴,以我們上文中的Teacher中注入了Student、Student中注入了Teacher為例來說明,假定Spring的載入順序為先載入Teacher,再載入Student。

getBean方法觸發Teacher的初始化後:

    a. 首先走到3中的方法1),此時map中都為空,獲取不到實例;

    b. 然後走到方法2)中,步驟A、步驟C、步驟D為控制map中數據的方法,實現簡單,可暫不關注。其中步驟B的getObject方法觸發對方法3)的調用;

    c. 在方法3)中,先通過createBeanInstance實例化Teacher對象,又將該實例化的對象通過addSingletonFactory方法放入singletonFactories中,完成Teacher對象早期的暴露;

    d. 然後在方法3)中通過populateBean方法對Teacher對象進行屬性的注入,發現它有一個Student屬性,則觸發getBean方法對Student進行初始化

    e. 重複a、b、c步驟,只是此時要初始化的是Student對象

    f. 走到d的時候,調用populateBean對Student對象進行屬性注入,發現它有一個Teacher屬性,則觸發getBean方法對Teacher進行初始化;

    g. 對Teacher進行初始化,又來到a,但此時map已經不為空了,因為之前在c步驟中已經將Teacher實例放入了singletonFactories中,a中得到Teacher實例後返回;

    h.完成f中對Student的初始化,繼而依次往上回溯完成Teacher的初始化;

完成Teacher的初始化後,Student的初始化就簡單了,因為map中已經存了這個單例。

至此,Spring循環依賴的總結分析結束,一句話來概括一下:Spring通過將實例化後的對象提前暴露給Spring容器中的singletonFactories,解決了循環依賴的問題