您好,登錄后才能下訂單哦!
這篇文章主要介紹“Spring中循環依賴的解決方法”,在日常操作中,相信很多人在Spring中循環依賴的解決方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Spring中循環依賴的解決方法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一、先說說Spring解決的循環依賴是什么
Java中的循環依賴分兩種,一種是構造器的循環依賴,另一種是屬性的循環依賴。
構造器的循環依賴就是在構造器中有屬性循環依賴,如下所示的兩個類就屬于構造器循環依賴:
@Servicepublic class Student { @Autowired private Teacher teacher; public Student (Teacher teacher) { System.out.println("Student init1:" + teacher); } public void learn () { System.out.println("Student learn"); }}
@Servicepublic class Teacher { @Autowired private Student student; public Teacher (Student student) { System.out.println("Teacher init1:" + student); } public void teach () { System.out.println("teach:"); student.learn(); }}
這種循環依賴沒有什么解決辦法,因為JVM虛擬機在對類進行實例化的時候,需先實例化構造器的參數,而由于循環引用這個參數無法提前實例化,故只能拋出錯誤。
Spring解決的循環依賴就是指屬性的循環依賴,如下所示:
@Servicepublic class Teacher { @Autowired private Student student; public Teacher () { System.out.println("Teacher init1:" + student); } public void teach () { System.out.println("teach:"); student.learn(); } }
@Servicepublic class Student { @Autowired private Teacher teacher; public Student () { System.out.println("Student init:" + teacher); } public void learn () { System.out.println("Student learn"); }}
測試掃描類:
@ComponentScan(value = "myPackage") public class ScanConfig { }
測試啟動類:
public class SpringTest { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class); applicationContext.getBean(Teacher.class).teach(); }}
測試類執行結果:
Student init:null Teacher init:null teach: Student learn
可以看到,在構造器執行的時候未完成屬性的注入,而在調用方法的時候已經完成了注入。下面就一起看看Spring內部是在何時完成的屬性注入,又是如何解決的循環依賴。
二、循環依賴與屬性注入
1、對于非懶加載的類,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包掃描以及bean的初始化,下面就一起追蹤下去。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // 其他代碼 // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); }
可以看到調用了beanFactory的一個方法,此處的beanFactory就是指我們最常見的那個DefaultListableBeanFactory,下面看它里面的這個方法。
2、DefaultListableBeanFactory的preInstantiateSingletons方法
public void preInstantiateSingletons() throws BeansException { List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 判斷為非抽象類、是單例、非懶加載 才給初始化 if (isFactoryBean(beanName)) { // 無關代碼(針對FactoryBean的處理) } else { // 重要!!!普通bean就是在這里初始化的 getBean(beanName); } } } // 其他無關代碼 }
可以看到,就是在此方法中循環Spring容器中所有的bean,依次對其進行初始化,初始化的入口就是getBean方法
3、AbstractBeanFactory的getBean跟doGetBean方法
追蹤getBean方法:
public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); }
可見引用了重載的doGetBean方法,繼續追蹤之:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // 方法1)從三個map中獲取單例類 Object sharedInstance = getSingleton(beanName); // 省略無關代碼 } else { // 如果是多例的循環引用,則直接報錯 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // 省略若干無關代碼 try { // Create bean instance. if (mbd.isSingleton()) { // 方法2) 獲取單例對象 sharedInstance = getSingleton(beanName, () -> { try { //方法3) 創建ObjectFactory中getObject方法的返回值 return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } // 省略若干無關代碼 return (T) bean; }
該方法比較長,對于解決循環引用來說,上面標出來的3個方法起到了至關重要的作用,下面我們挨個攻克。
3.1) getSingleton(beanName)方法: 注意該方法跟方法2)是重載方法,名字一樣內部邏輯卻大相徑庭。
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName);// 步驟A if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName);// 步驟B if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 步驟C if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
通過上面的步驟可以看出這三個map的優先級。其中singletonObjects里面存放的是初始化之后的單例對象;earlySingletonObjects中存放的是一個已完成實例化未完成初始化的早期單例對象;而singletonFactories中存放的是ObjectFactory對象,此對象的getObject方法返回值即剛完成實例化還未開始初始化的單例對象。所以先后順序是,單例對象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。
當debug到此處時,以上述Teacher和Student兩個循環引用的類為例,如果第一個走到這一步的是Teacher,則從此處這三個map中get到的值都是空,因為還未添加進去。這個方法主要是給循環依賴中后來過來的對象用。
3.2)getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 省略無關代碼 beforeSingletonCreation(beanName); // 步驟A boolean newSingleton = false; // 省略無關代碼 try { singletonObject = singletonFactory.getObject();// 步驟B newSingleton = true; } // 省略無關代碼 finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName);// 步驟C } if (newSingleton) { addSingleton(beanName, singletonObject);// 步驟D } } return singletonObject; } }
獲取單例對象的主要邏輯就是此方法實現的,主要分為上面四個步驟,繼續挨個看:
步驟A:
protected void beforeSingletonCreation(String beanName) { // 判斷,并首次將beanName即teacher放入singletonsCurrentlyInCreation中 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
步驟C:
protected void afterSingletonCreation(String beanName) { // 得到單例對象后,再講beanName從singletonsCurrentlyInCreation中移除 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } }
步驟D:
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject);//添加單例對象到map中 this.singletonFactories.remove(beanName);//從早期暴露的工廠中移除,此map在解決循環依賴中發揮了關鍵的作用 this.earlySingletonObjects.remove(beanName);//從早期暴露的對象map中移除 this.registeredSingletons.add(beanName);//添加到已注冊的單例名字集合中 } }
步驟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[]) 方法
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // 省略無關代碼 try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); return beanInstance; } // 省略無關代碼 }
去掉無關代碼之后,關鍵方法只有doCreateBean方法,追蹤之:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // 省略代碼 if (instanceWrapper == null) { // 實例化bean instanceWrapper = createBeanInstance(beanName, mbd, args); } boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 重點!!!將實例化的對象添加到singletonFactories中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 初始化bean Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper);//也很重要 exposedObject = initializeBean(beanName, exposedObject, mbd); } // 省略無關代碼 return exposedObject;}
上面注釋中標出的重點是此方法的關鍵。在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,解決了循環依賴的問題。
到此,關于“Spring中循環依賴的解決方法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。