簡單了解Spring循環(huán)依賴解決過程
這篇文章主要介紹了簡單了解Spring循環(huán)依賴解決過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
前言
說起Spring中循環(huán)依賴的解決辦法,相信很多園友們都或多或少的知道一些,但當(dāng)真的要詳細(xì)說明的時候,可能又沒法一下將它講清楚。本文就試著盡自己所能,對此做出一個較詳細(xì)的解讀。另,需注意一點(diǎn),下文中會出現(xiàn)類的實(shí)例化跟類的初始化兩個短語,為怕園友迷惑,事先聲明一下,本文的實(shí)例化是指剛執(zhí)行完構(gòu)造器將一個對象new出來,但還未填充屬性值的狀態(tài),而初始化是指完成了屬性的依賴注入。
一、先說說Spring解決的循環(huán)依賴是什么
Java中的循環(huán)依賴分兩種,一種是構(gòu)造器的循環(huán)依賴,另一種是屬性的循環(huán)依賴。
構(gòu)造器的循環(huán)依賴就是在構(gòu)造器中有屬性循環(huán)依賴,如下所示的兩個類就屬于構(gòu)造器循環(huán)依賴:
@Service
public class Student {
@Autowired
private Teacher teacher;
public Student (Teacher teacher) {
System.out.println("Student init1:" + teacher);
}
public void learn () {
System.out.println("Student learn");
}
}
@Service
public 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();
}
}
這種循環(huán)依賴沒有什么解決辦法,因?yàn)镴VM虛擬機(jī)在對類進(jìn)行實(shí)例化的時候,需先實(shí)例化構(gòu)造器的參數(shù),而由于循環(huán)引用這個參數(shù)無法提前實(shí)例化,故只能拋出錯誤。
Spring解決的循環(huán)依賴就是指屬性的循環(huán)依賴,如下所示:
@Service
public class Teacher {
@Autowired
private Student student;
public Teacher () {
System.out.println("Teacher init1:" + student);
}
public void teach () {
System.out.println("teach:");
student.learn();
}
}
@Service
public 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();
}
}
測試類執(zhí)行結(jié)果:
Student init:null Teacher init:null teach: Student learn
可以看到,在構(gòu)造器執(zhí)行的時候未完成屬性的注入,而在調(diào)用方法的時候已經(jīng)完成了注入。下面就一起看看Spring內(nèi)部是在何時完成的屬性注入,又是如何解決的循環(huán)依賴。
二、循環(huán)依賴與屬性注入
1、對于非懶加載的類,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包掃描以及bean的初始化,下面就一起追蹤下去。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 其他代碼
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
可以看到調(diào)用了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)) {
// 無關(guān)代碼(針對FactoryBean的處理)
}
else {
// 重要?。?!普通bean就是在這里初始化的
getBean(beanName);
}
}
}
// 其他無關(guān)代碼
}
可以看到,就是在此方法中循環(huán)Spring容器中所有的bean,依次對其進(jìn)行初始化,初始化的入口就是getBean方法
3、AbstractBeanFactory的getBean跟doGetBean方法
追蹤getBean方法:
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
可見引用了重載的doGetBean方法,繼續(xù)追蹤之:
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);
// 省略無關(guān)代碼
}
else {
// 如果是多例的循環(huán)引用,則直接報(bào)錯
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 省略若干無關(guān)代碼
try {
// Create bean instance.
if (mbd.isSingleton()) {
// 方法2) 獲取單例對象
sharedInstance = getSingleton(beanName, () -> {
try { //方法3) 創(chuàng)建ObjectFactory中g(shù)etObject方法的返回值
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);
}
}
// 省略若干無關(guān)代碼
return (T) bean;
}
該方法比較長,對于解決循環(huán)引用來說,上面標(biāo)出來的3個方法起到了至關(guān)重要的作用,下面我們挨個攻克。
3.1) getSingleton(beanName)方法: 注意該方法跟方法2)是重載方法,名字一樣內(nèi)部邏輯卻大相徑庭。
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的優(yōu)先級。其中singletonObjects里面存放的是初始化之后的單例對象;earlySingletonObjects中存放的是一個已完成實(shí)例化未完成初始化的早期單例對象;而singletonFactories中存放的是ObjectFactory對象,此對象的getObject方法返回值即剛完成實(shí)例化還未開始初始化的單例對象。所以先后順序是,單例對象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。
當(dāng)debug到此處時,以上述Teacher和Student兩個循環(huán)引用的類為例,如果第一個走到這一步的是Teacher,則從此處這三個map中g(shù)et到的值都是空,因?yàn)檫€未添加進(jìn)去。這個方法主要是給循環(huán)依賴中后來過來的對象用。
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) {
// 省略無關(guān)代碼
beforeSingletonCreation(beanName); // 步驟A
boolean newSingleton = false;
// 省略無關(guān)代碼
try {
singletonObject = singletonFactory.getObject();// 步驟B
newSingleton = true;
}
// 省略無關(guān)代碼
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);// 步驟C
}
if (newSingleton) {
addSingleton(beanName, singletonObject);// 步驟D
}
}
return singletonObject;
}
}
獲取單例對象的主要邏輯就是此方法實(shí)現(xiàn)的,主要分為上面四個步驟,繼續(xù)挨個看:
步驟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在解決循環(huán)依賴中發(fā)揮了關(guān)鍵的作用
this.earlySingletonObjects.remove(beanName);//從早期暴露的對象map中移除
this.registeredSingletons.add(beanName);//添加到已注冊的單例名字集合中
}
}
步驟B:
此處調(diào)用了ObjectFactory的getObject方法,此方法是在哪里實(shí)現(xiàn)的呢?返回的又是什么?且往回翻,找到3中的方法3,對java8函數(shù)式編程有過了解的園友應(yīng)該能看出來,方法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 {
// 省略無關(guān)代碼
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
// 省略無關(guān)代碼
}
去掉無關(guān)代碼之后,關(guān)鍵方法只有doCreateBean方法,追蹤之:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
// 省略代碼
if (instanceWrapper == null) {
// 實(shí)例化bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 重點(diǎn)!??!將實(shí)例化的對象添加到singletonFactories中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 初始化bean
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);//也很重要
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 省略無關(guān)代碼
return exposedObject;
}
上面注釋中標(biāo)出的重點(diǎn)是此方法的關(guān)鍵。在addSingletonFactory方法中,將第二個參數(shù)ObjectFactory存入了singletonFactories供其他對象依賴時調(diào)用。然后下面的populateBean方法對剛實(shí)例化的bean進(jìn)行屬性注入(該方法關(guān)聯(lián)較多,本文暫時不展開追蹤了,有興趣的園友自行查看即可),如果遇到Spring中的對象屬性,則再通過getBean方法獲取該對象。至此,循環(huán)依賴在Spring中的處理過程已經(jīng)追溯完畢,下面我們總結(jié)一下。
小結(jié)
屬性注入主要是在populateBean方法中進(jìn)行的。對于循環(huán)依賴,以我們上文中的Teacher中注入了Student、Student中注入了Teacher為例來說明,假定Spring的加載順序?yàn)橄燃虞dTeacher,再加載Student。
getBean方法觸發(fā)Teacher的初始化后:
a. 首先走到3中的方法1),此時map中都為空,獲取不到實(shí)例;
b. 然后走到方法2)中,步驟A、步驟C、步驟D為控制map中數(shù)據(jù)的方法,實(shí)現(xiàn)簡單,可暫不關(guān)注。其中步驟B的getObject方法觸發(fā)對方法3)的調(diào)用;
c. 在方法3)中,先通過createBeanInstance實(shí)例化Teacher對象,又將該實(shí)例化的對象通過addSingletonFactory方法放入singletonFactories中,完成Teacher對象早期的暴露;
d. 然后在方法3)中通過populateBean方法對Teacher對象進(jìn)行屬性的注入,發(fā)現(xiàn)它有一個Student屬性,則觸發(fā)getBean方法對Student進(jìn)行初始化
e. 重復(fù)a、b、c步驟,只是此時要初始化的是Student對象
f. 走到d的時候,調(diào)用populateBean對Student對象進(jìn)行屬性注入,發(fā)現(xiàn)它有一個Teacher屬性,則觸發(fā)getBean方法對Teacher進(jìn)行初始化;
g. 對Teacher進(jìn)行初始化,又來到a,但此時map已經(jīng)不為空了,因?yàn)橹霸赾步驟中已經(jīng)將Teacher實(shí)例放入了singletonFactories中,a中得到Teacher實(shí)例后返回;
h.完成f中對Student的初始化,繼而依次往上回溯完成Teacher的初始化;
完成Teacher的初始化后,Student的初始化就簡單了,因?yàn)閙ap中已經(jīng)存了這個單例。
至此,Spring循環(huán)依賴的總結(jié)分析結(jié)束,一句話來概括一下:Spring通過將實(shí)例化后的對象提前暴露給Spring容器中的singletonFactories,解決了循環(huán)依賴的問題。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot項(xiàng)目找不到j(luò)avax.servlet.Filter的問題及解決
這篇文章主要介紹了SpringBoot項(xiàng)目找不到j(luò)avax.servlet.Filter的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
springboot 配置文件配置項(xiàng)前綴為0的數(shù)字特殊處理方式
這篇文章主要介紹了springboot 配置文件配置項(xiàng)前綴為0的數(shù)字特殊處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
Java實(shí)現(xiàn)SHA1加密代碼實(shí)例
這篇文章給大家分享了Java實(shí)現(xiàn)SHA1加密的相關(guān)實(shí)例代碼,有興趣的朋友可以測試參考下。2018-07-07
springboot之端口設(shè)置和contextpath的配置方式
這篇文章主要介紹了springboot之端口設(shè)置和contextpath的配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Spring Security基于自定義的認(rèn)證提供器實(shí)現(xiàn)圖形驗(yàn)證碼流程解析
這篇文章主要介紹了Spring Security基于自定義的認(rèn)證提供器實(shí)現(xiàn)圖形驗(yàn)證碼,通過本文學(xué)習(xí)下AuthenticationProvider接口的類關(guān)系圖,感興趣的朋友跟隨小編一起看看吧2021-09-09
SpringBoot整合WebService服務(wù)的實(shí)現(xiàn)代碼
WebService是一個SOA(面向服務(wù)的編程)的架構(gòu),它是不依賴于語言,不依賴于平臺,可以實(shí)現(xiàn)不同的語言間的相互調(diào)用,通過Internet進(jìn)行基于Http協(xié)議的網(wǎng)絡(luò)應(yīng)用間的交互,這篇文章主要介紹了SpringBoot整合WebService服務(wù)的實(shí)例代碼,需要的朋友可以參考下2022-02-02

