最近因為需要去講spring源碼就又讀了一遍spring源碼, 感嘆一下spring源碼的龐大和強大,這次再讀源碼收獲很多;
隨筆記錄一點關于spring當中的循環(huán)引用的的源碼分析
文字篇幅較多如果看到錯別字一定提醒筆者修改
眾所周知spring在默認單例的情況下是支持循環(huán)引用的,比如有如下代碼:
IndexService和UserService是相互依賴的、循環(huán)依賴、循環(huán)引用
@Service
public class IndexService {
//注入userService
@Autowired
UserService userService;
//構(gòu)造方法
public IndexService(){
System.out.println("index init");
}
}
@Service
public class UserService {
//注入 indexService
@Autowired
IndexService indexService;
//構(gòu)造方法
public UserService(){
System.out.println("user init");
}
}
這兩個類非常簡單,就是相互引用了對方,也就是我們常常的說的循環(huán)依賴,spring是允許這樣的循環(huán)依賴(前提是單例的情況下的)
運行這段代碼的結(jié)果下圖

spring默認是支持循環(huán)的依賴的?
可能在一些非常高級的面試當中,(比如阿里的面試)面試官會問你為什么spring當中默認支持循環(huán)依賴?或者問spring在哪里體現(xiàn)了默認支持循環(huán)依賴。從上面的結(jié)果我們可以推導出來spring是默認支持循環(huán)依賴的,但這僅僅是個結(jié)果,你不可能告訴面試官說你寫了個簡單的demo從而說明了他默認支持,高級面試中最好是能通過源碼設計來說明這個問題。比如spring的循環(huán)依賴其實是可以關閉的,spring提供了api來關閉循環(huán)依賴的功能。當然你也可以修改spring源碼來關閉這個功能,這里筆者為了逼格很高,就修改一下spring的源碼來關閉這個功能,關于如何直接調(diào)用spring提供的api來直接關閉循環(huán)引用功能可以和私下問筆者
下圖是我修改后的spring源碼
我加了一行setAllowCircularReferences(false);

當我修改spring源碼關閉循環(huán)引用功能之后代碼便會出異常

那么為什么setAllowCircularReferences(false);會關閉循環(huán)依賴呢?首要明白spring的循環(huán)依賴是怎么做到的呢?spring源碼當中是如何處理循環(huán)依賴的?
我們來分析一下spring循環(huán)依賴這塊的源碼 其實所謂的循環(huán)依賴無非就是屬性注入,或者就是大家常常說的自動注入,
故而我們需要去研究spring自動注入的源碼
下面筆者介紹一下spring創(chuàng)建一個bean的流程,通過源碼結(jié)合在idea當中debug截圖來說明
1、main方法,初始化spring容器,在初始化容器之后默認的單例bean已經(jīng)實例化完成了,也就是說spring的默認單例bean創(chuàng)建流程、和所謂自動注入的功能都在容器初始化的過程中。故而我們需要研究這個容器初始化過程、在哪里體現(xiàn)了自動注入;
進入AnnotationConfigApplicationContext類的構(gòu)造方法

2、在構(gòu)造方法中調(diào)用了refresh方法,查看refresh方法

3、refresh方法當中調(diào)用finishBeanFactoryInitialization方法來實例化所有掃描出來的類

4、finishBeanFactoryInitialization方法當中調(diào)用preInstantiateSingletons初始化掃描出來的類

5、preInstantiateSingletons方法經(jīng)過一系列判斷之后會調(diào)用getBean方法去實例化掃描出來的類

6、getBean方法就是個空殼方法,調(diào)用了doGetBean方法

doGetBean方法內(nèi)容有點多,這個方法非常重要,不僅僅針對循環(huán)依賴,
甚至整個spring bean生命周期中這個方法也有著舉足輕重的地位,讀者可以認真看看筆者的分析。需要說明的是我為了更好的說清楚這個方法,我把代碼放到文章里面進行分析;但是刪除了一些無用的代碼;比如日志的記錄這些無關緊要的代碼。下面重點說這個doGetBean方法
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//這個方法非常重要,但是和筆者今天要分析的循環(huán)依賴沒什么很大的關系
//讀者可以簡單的認為就是對beanName做一個校驗特殊字符串的功能
//我會在下次更新博客的時候重點討論這個方法
//transformedBeanName(name)這里的name就是bean的名字
final String beanName = transformedBeanName(name);
//定義了一個對象,用來存將來返回出來的bean
Object bean;
// Eagerly check singleton cache for manu ally registered singletons.
/**
* 注意這是第一次調(diào)用getSingleton方法,下面spring還會調(diào)用一次
* 但是兩次調(diào)用的不是同一個方法;屬于方法重載
* 第一次 getSingleton(beanName) 也是循環(huán)依賴最重要的方法
* 關于getSingleton(beanName)具體代碼分析可以參考筆者后面的分析
* 這里給出這個方法的總結(jié)
* 首先spring會去單例池去根據(jù)名字獲取這個bean,單例池就是一個map
* 如果對象被創(chuàng)建了則直接從map中拿出來并且返回
* 但是問題來了,為什么spring在創(chuàng)建一個bean的時候會去獲取一次呢?
* 因為作為代碼的書寫者肯定知道這個bean這個時候沒有創(chuàng)建?為什么需要get一次呢?
* 關于這個問題其實原因比較復雜,需要讀者對spring源碼設計比較精通
* 筆者不準備來針對這個原因大書特書,稍微解釋一下吧
* 我們可以分析doGetBean這個方法,顧名思義其實是用來獲取bean的
* 為什么創(chuàng)建bean會調(diào)用這個doGetBean方法呢?難道是因為spring作者疏忽,獲取亂起名字
* 顯然不是,spring的源碼以其書寫優(yōu)秀、命名優(yōu)秀而著名,那么怎么解釋這個方法呢?
* 其實很簡單doGetBean這個方法不僅僅在創(chuàng)建bean的時候會被調(diào)用,在getBean的時候也會調(diào)用
* 他是創(chuàng)建bean和getBean通用的方法。但是這只是解釋了這個方法的名字意義
* 并么有解釋這個方法為什么會在創(chuàng)建bean的時候調(diào)用
* 筆者前面已經(jīng)說過原因很復雜,和本文有關的就是因為循環(huán)引用
* 由于循環(huán)引用需要在創(chuàng)建bean的過程中去獲取被引用的那個類
* 而被引用的這個類如果沒有創(chuàng)建,則會調(diào)用createBean來創(chuàng)建這個bean
* 在創(chuàng)建這個被引用的bean的過程中會判斷這個bean的對象有沒有實例化
* bean的對象?什么意思呢?
* 為了方便閱讀,請讀者一定記住兩個概念;什么是bean,什么是對象
* 筆者以為一個對象和bean是有區(qū)別的
* 對象:只要類被實例化就可以稱為對象
* bean:首先得是一個對象,然后這個對象需要經(jīng)歷一系列的bean生命周期
* 最后把這個對象put到單例池才能算一個bean
* 讀者千萬注意,筆者下文中如果寫到bean和寫到對象不是隨意寫的
* 是和這里的解釋有關系的;重點一定要注意;一定;一定;
* 簡而言之就是spring先new一個對象,繼而對這個對象進行生命周期回調(diào)
* 接著對這個對象進行屬性填充,也是大家說的自動注入
* 然后在進行AOP判斷等等;這一些操作簡稱----spring生命周期
* 所以一個bean是一個經(jīng)歷了spring周期的對象,和一個對象有區(qū)別
* 再回到前面說的循環(huán)引用,首先spring掃描到一個需要被實例化的類A
* 于是spring就去創(chuàng)建A;A=new A-a;new A的過程會調(diào)用getBean("a"));
* 所謂的getBean方法--核心也就是筆者現(xiàn)在寫注釋的這個getSingleton(beanName)
* 這個時候get出來肯定為空?為什么是空呢?我寫這么多注釋就是為了解釋這個問題?
* 可能有的讀者會認為getBean就是去容器中獲取,所以肯定為空,其實不然,接著往下看
* 如果getA等于空;spring就會實例化A;也就是上面的new A
* 但是在實例化A的時候會再次調(diào)用一下
* getSingleton(String beanName, ObjectFactory<?> singletonFactory)
* 筆者上面說過現(xiàn)在寫的注釋給getSingleton(beanName)
* 也即是第一次調(diào)用getSingleton(beanName)
* 實例化一共會調(diào)用兩次getSingleton方法;但是是重載
* 第二次調(diào)用getSingleton方法的時候spring會在一個set集合當中記錄一下這個類正在被創(chuàng)建
* 這個一定要記住,在調(diào)用完成第一次getSingleton完成之后
* spring判讀這個類沒有創(chuàng)建,然后調(diào)用第二次getSingleton
* 在第二次getSingleton里面記錄了一下自己已經(jīng)開始實例化這個類
* 這是循環(huán)依賴做的最牛逼的地方,兩次getSingleton的調(diào)用
* 也是回答面試時候關于循環(huán)依賴必須要回答道的地方;
* 需要說明的spring實例化一個對象底層用的是反射;
* spring實例化一個對象的過程非常復雜,需要推斷構(gòu)造方法等等;
* 這里筆者先不討論這個過程,以后有機會更新一下
* 讀者可以理解spring直接通過new關鍵字來實例化一個對象
* 但是這個時候?qū)ο骯僅僅是一個對象,還不是一個完整的bean
* 接著讓這個對象去完成spring的bean的生命周期
* 過程中spring會判斷容器是否允許循環(huán)引用,判斷循環(huán)引用的代碼筆者下面會分析
* 前面說過spring默認是支持循環(huán)引用的,筆者后面解析這個判斷的源碼也是spring默認支持循環(huán)引用的證據(jù)
* 如果允許循環(huán)依賴,spring會把這個對象(還不是bean)臨時存起來,放到一個map當中
* 注意這個map和單例池是兩個map,在spring源碼中單例池的map叫做 singletonObjects
* 而這個存放臨時對象的map叫做singletonFactories
* 當然spring還有一個存放臨時對象的map叫做earlySingletonObjects
* 所以一共是三個map,有些博客或者書籍人也叫做三級緩存
* 為什么需要三個map呢?先來了解這三個map到底都緩存了什么
* 第一個map singletonObjects 存放的單例的bean
* 第二個map singletonFactories 存放的臨時對象(沒有完整springBean生命周期的對象)
* 第三個map earlySingletonObjects 存放的臨時對象(沒有完整springBean生命周期的對象)
* 筆者為了讓大家不懵逼這里吧第二個和第三個map功能寫成了一模一樣
* 其實第二個和第三個map會有不一樣的地方,但這里不方便展開講,下文會分析
* 讀者姑且認為這兩個map是一樣的
* 第一個map主要為了直接緩存創(chuàng)建好的bean;方便程序員去getBean;很好理解
* 第二個和第三個主要為了循環(huán)引用;為什么為了方便循環(huán)引用,接著往下看
* 把對象a緩存到第二個map之后,會接著完善生命周期;
* 當然spring bean的生命周期很有很多步驟;本文先不詳細討論;
* 后面的博客筆者再更新;
* 當進行到對象a的屬性填充的這一周期的時候,發(fā)覺a依賴了一個B類
* 所以spring就會去判斷這個B類到底有沒有bean在容器當中
* 這里的判斷就是從第一個map即單例池當中去拿一個bean
* 后面我會通過源碼來證明是從第一個map中拿一個bean的
* 假設沒有,那么spring會先去調(diào)用createBean創(chuàng)建這個bean
* 于是又回到和創(chuàng)建A一樣的流程,在創(chuàng)建B的時候同樣也會去getBean("B");
* getBean核心也就是筆者現(xiàn)在寫注釋的這個getSingleton(beanName)方法
* 下面我重申一下:因為是重點
* 這個時候get出來肯定為空?為什么是空呢?我寫這么多注釋就是為了解釋這個問題?
* 可能有的讀者會認為getBean就是去容器中獲??;
* 所以肯定為空,其實不然,接著往下看;
* 第一次調(diào)用完getSingleton完成之后會調(diào)用第二次getSingleton
* 第二次調(diào)用getSingleton同樣會在set集合當中去記錄B正在被創(chuàng)建
* 請筆者記住這個時候 set集合至少有兩個記錄了 A和B;
* 如果為空就 b=new B();創(chuàng)建一個b對象;
* 再次說明一下關于實例化一個對象,spring做的很復雜,下次討論
* 創(chuàng)建完B的對象之后,接著完善B的生命周期
* 同樣也會判斷是否允許循環(huán)依賴,如果允許則把對象b存到第二個map當中;
* 提醒一下筆者這個時候第二個map當中至少有兩個對象了,a和b
* 接著繼續(xù)生命周期;當進行到b對象的屬性填充的時候發(fā)覺b需要依賴A
* 于是就去容器看看A有沒有創(chuàng)建,說白了就是從第一個map當中去找a
* 有人會說不上A在前面創(chuàng)建了a嘛?注意那只是個對象,不是bean;
* 還不在第一個map當中 對所以b判定A沒有創(chuàng)建,于是就是去創(chuàng)建A;
* 那么又再次回到了原點了,創(chuàng)建A的過程中;首先調(diào)用getBean("a")
* 上文說到getBean("a")的核心就是 getSingleton(beanName)
* 上文也說了get出來a==null;但是這次卻不等于空了
* 這次能拿出一個a對象;注意是對象不是bean
* 為什么兩次不同?原因在于getSingleton(beanName)的源碼
* getSingleton(beanName)首先從第一個map當中獲取bean
* 這里就是獲取a;但是獲取不到;然后判斷a是不是等于空
* 如果等于空則在判斷a是不是正在創(chuàng)建?什么叫做正在創(chuàng)建?
* 就是判斷a那個set集合當中有沒有記錄A;
* 如果這個集合當中包含了A則直接把a對象從map當中get出來并且返回
* 所以這一次就不等于空了,于是B就可以自動注入這個a對象了
* 這個時候a還只是對象,a這個對象里面依賴的B還沒有注入
* 當b對象注入完成a之后,把B的周期走完,存到容器當中
* 存完之后繼續(xù)返回,返回到a注入b哪里?
* 因為b的創(chuàng)建時因為a需要注入b;于是去get b
* 當b創(chuàng)建完成一個bean之后,返回b(b已經(jīng)是一個bean了)
* 需要說明的b是一個bean意味著b已經(jīng)注入完成了a;這點上面已經(jīng)說明了
* 由于返回了一個b,故而a也能注入b了;
* 接著a對象繼續(xù)完成生命周期,當走完之后a也在容器中了
* 至此循環(huán)依賴搞定
* 需要說明一下上文提到的正在創(chuàng)建這種說法并沒有官方支持
* 是筆者自己的認為;各位讀者可以自行給他取名字
* 筆者是因為存放那些記錄的set集合的名字叫做singletonsCurrentlyInCreation
* 顧名思義,當前正在創(chuàng)建的單例對象。。。。。
* 還有上文提到的對象和bean的概念;也沒有官方支持
* 也是筆者為了讓讀者更好的理解spring源碼而提出的個人概念
* 但是如果你覺得這種方法確實能讓你更好的理解spring源碼
* 那么請姑且相信筆者對spring源碼的理解,假設10個人相信就會有100個人相信
* 繼而會有更多人相信,就會成為官方說法,哈哈。
* 以上是循環(huán)依賴的整個過程,其中getSingleton(beanName)
* 這個方法的存在至關重要
* 最后說明一下getSingleton(beanName)的源碼分析,下文會分析
**/
Object sharedInstance = getSingleton(beanName);
/**
* 如果sharedInstance不等于空直接返回
* 當然這里沒有直接返回而是調(diào)用了getObjectForBeanInstance
* 關于這方法以后解釋,讀者可以認為這里可以理解為
* bean =sharedInstance; 然后方法最下面會返回bean
* 什么時候不等于空?
* 再容器初始化完成之后
* 程序員直接調(diào)用getbean的時候不等于空
* 什么時候等于空?
* 上文已經(jīng)解釋過了,創(chuàng)建對象的時候調(diào)用就會等于空
*/
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
/**
* 判斷這個類是不是在創(chuàng)建過程中
* 上文說了,一個類是否在創(chuàng)建的過程中是第二次調(diào)用getSingleton中決定的
* 這里還沒有執(zhí)行到,如果就在創(chuàng)建過程中則出異常
*
**/
//prototypesCurrentlyInCreation 需要聯(lián)系 getSingleton方法
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}else{
/**
* 需要說明的筆者刪了很多和本文無用的代碼
* 意思就是源碼中執(zhí)行到這個if的時候有很多其他代碼
* 但是都是一些判斷,很本文需要討論的問題關聯(lián)不大
* 這個if就是判斷當前需要實例化的類是不是單例的
* spring默認都是單例的,故而一般都成立的
* 接下來便是調(diào)用第二次 getSingleton
* 第二次會把當前正在創(chuàng)建的類記錄到set集合
* 然后反射創(chuàng)建這個實例,并且走完生命周期
* 第二次調(diào)用getSingleton的源碼分析會在下文
**/
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
//完成了目標對象的創(chuàng)建
//如果需要代理,還完成了代理
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;
}
第一次調(diào)用getSingleton的源碼分析 Object sharedInstance =
getSingleton(beanName);
//空殼方法
public Object getSingleton(String beanName) {
//重點,一定要記住這里傳的是一個true,面試會考
return getSingleton(beanName, true);
}
/**
上面說的true對應這里的第二個參數(shù)boolean allowEarlyReference
顧名思義 叫做允許循環(huán)引用,而spring在內(nèi)部調(diào)用這個方法的時候傳的true
這也能說明spring默認是支持循環(huán)引用的,這也是需要講過面試官的
但是你不能只講這一點,后面我會總結(jié),這里先記著這個true
這個allowEarlyReference也是支持spring默認支持循環(huán)引用的其中一個原因
**/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
/**
首先spring會去第一個map當中去獲取一個bean;說白了就是從容器中獲取
說明我們?nèi)绻谌萜鞒跏蓟笳{(diào)用getBean其實就從map中去獲取一個bean
假設是初始化A的時候那么這個時候肯定等于空,前文分析過這個map的意義
**/
Object singletonObject = this.singletonObjects.get(beanName);
/**
我們這里的場景是初始化對象A第一次調(diào)用這個方法
這段代碼非常重要,首先從容器中拿,如果拿不到,再判斷這個對象是不是在set集合
這里的set集合前文已經(jīng)解釋過了,就是判斷a是不是正在創(chuàng)建
假設現(xiàn)在a不在創(chuàng)建過程,那么直接返回一個空,第一次getSingleton返回
**/
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;
}
第二次調(diào)用getSingleton sharedInstance = getSingleton(beanName, () ->
代碼我做了刪減,刪了一些本本文無關的代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
//首先也是從第一個map即容器中獲取
//再次證明如果我們在容器初始化后調(diào)用getBean其實就是從map當中獲取一個bean
//我們這里的場景是初始化對象A第一次調(diào)用這個方法
//那么肯定為空
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
/**注意這行代碼,就是A的名字添加到set集合當中
也就是筆者說的標識A正在創(chuàng)建過程當中
這個方法比較簡單我就不單獨分析了,直接在這里給出
singletonsCurrentlyInCreation.add就是放到set集合當中
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName)
&& !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
**/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
//這里便是創(chuàng)建一個bean的入口了
//spring會首先實例化一個對象,然后走生命周期
//走生命周期的時候前面說過會判斷是否允許循環(huán)依賴
//如果允許則會把創(chuàng)建出來的這個對象放到第二個map當中
//然后接著走生命周期當他走到屬性填充的時候
//會去get一下B,因為需要填充B,也就是大家認為的自動注入
//這些代碼下文分析,如果走完了生命周期
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
}
return singletonObject;
}
}
如果允許則會把創(chuàng)建出來的這個對象放到第二個map當中
AbstractAutowireCapableBeanFactory#doCreateBean()方法部分代碼
由于這個方法內(nèi)容過去多,我刪減了一些無用代碼 上面說的 singletonObject =
singletonFactory.getObject();
會開始創(chuàng)建bean調(diào)用AbstractAutowireCapableBeanFactory#doCreateBean() 在創(chuàng)建bean;
下面分析這個方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
//如果你bean指定需要通過factoryMethod來創(chuàng)建則會在這里被創(chuàng)建
//如果讀者不知道上面factoryMethod那你就忽略這行代碼
//你可以認為你的A是一個普通類,不會再這里創(chuàng)建
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//這里就通過反射創(chuàng)建一個對象,注意是對象不是bean
//這個createBeanInstance的方法過于復雜,本文不做分析
//以后如果有更新再來分析這個代碼
//讀者可以理解這里就是new了一個A對象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//得到new出來的A,為什么需要得到呢?因為Anew出來之后存到一個對象的屬性當中
final Object bean = instanceWrapper.getWrappedInstance();
//重點:面試會考
//這里就是判斷是不是支持循環(huán)引用和是否單例以及bean是否在創(chuàng)建過程中
//判斷循環(huán)引用的是&& this.allowCircularReferences
//allowCircularReferences在spring源碼當中默認就是true
// private boolean allowCircularReferences = true; 這是spring源碼中的定義
//并且這個屬性上面spring寫了一行非常重要的注釋
// Whether to automatically try to resolve circular references between beans
// 讀者自行翻譯,這是支持spring默認循環(huán)引用最核心的證據(jù)
//讀者一定要講給面試官,關于怎么講,我后面會總結(jié)
boolean earlySingletonExposure = (mbd.isSingleton()
&& this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//如果是單例,并且正在創(chuàng)建,并且是沒有關閉循環(huán)引用則執(zhí)行
//所以spring原形是不支持循環(huán)引用的這是證據(jù),但是其實可以解決
//怎么解決原形的循環(huán)依賴,筆者下次更新吧
if (earlySingletonExposure) {
//這里就是這個創(chuàng)建出來的A 對象a 放到第二個map當中
//注意這里addSingletonFactory就是往map當中put
//需要說明的是他的value并不是一個a對象
//而是一段表達式,但是包含了這個對象的
//所以上文說的第二個map和第三個map的有點不同
//第三個map是直接放的a對象(下文會講到第三個map的),
//第二個放的是一個表達式包含了a對象
//為什么需要放一個表達式?下文分析吧
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充屬性,也就是所謂的自動注入
//這個代碼我同一張圖來說明
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
return exposedObject;
}
populateBean(beanName, mbd, instanceWrapper)截圖圖說明

當A執(zhí)行到屬性填充的時候會調(diào)用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法來完成填充或者叫做自動注入b
下圖有很多文字注釋,可以放大圖上的注釋

填充B的時候先從容器中獲取B,這個時候b沒有創(chuàng)建則等于空,然后看B是不是正在創(chuàng)建,這個時候B只是執(zhí)行了第一次getSingleton故而不在第二個map當中,所以返回空,返回空之后會執(zhí)行創(chuàng)建B的流程;執(zhí)行第二遍調(diào)用getSingleton的時候會把b標識正在創(chuàng)建的過程中,也就是添加到那個set集合當中;下圖做說明

創(chuàng)建B的流程和創(chuàng)建A差不多,把B放到set集合,標識B正在創(chuàng)建,繼而實例化b對象,然后執(zhí)行生命周期流程,把創(chuàng)建的這個b對象放到第二個map當中,這個時候map當中已經(jīng)有了a,b兩個對象。
然后執(zhí)行b對象的屬性填充或者叫自動注入時候發(fā)覺需要依賴a,于是重復上面的getbean步驟,調(diào)用getSingleton方法;只不過現(xiàn)在a對象已經(jīng)可以獲取了故而把獲取出來的a對象、臨時對象注入給b對象,然后走完b的生命周期流程后返回b所表示bean,跟著把這個b所表示的bean注入給a對象,最后走完a對象的其他生命周期流程;循環(huán)依賴流程全部走完;但是好像沒有說到第三個map,第三個map到底充當了什么角色呢?
這個知識點非常的重要,關于這個知識不少書籍和博客都說錯了,這也是寫這篇文章的意義;筆者說每次讀spring源碼都不一樣的收獲,這次最大的收獲便是這里了;我們先來看一下代碼;
場景是這樣的,spring創(chuàng)建A,記住第一次創(chuàng)建A,過程中發(fā)覺需要依賴B,于是創(chuàng)建B,創(chuàng)建B的時候發(fā)覺需要依賴A,于是再一次創(chuàng)建–第二次創(chuàng)建A,下面代碼就是基于第二次創(chuàng)建的A來分析;第二次創(chuàng)建A的時候依然會調(diào)用getSingleton,先獲取一下a
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先從第一個map獲取a這個bean,也就是單例池獲取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//然后從第三個map當中獲取a這個對象
singletonObject = this.earlySingletonObjects.get(beanName);
//如果第三個map獲取不到a對象,再看是否允許了循環(huán)引用
//而這里的allowEarlyReference是true
//為什么是true,上文說了這個方法是spring自己調(diào)用的,他默認傳了true
if (singletonObject == null && allowEarlyReference) {
//然后從第二個map中獲取一個表達式
//這里要非常注意第二個map當中存的不是一個單純的對象
//前面說了第二個map當中存的是一個表達式,你可以理解為存了一個工廠
//或者理解存了一個方法,方法里面有個參數(shù)就是這個對象
//安裝spring的命名來分析應該理解為一個工廠singletonFactory
//一個能夠生成a對象的工廠
//那么他為什么需要這么一個工廠
//這里我先大概說一下,是為了通過工廠來改變這個對象
//至于為什么要改變對象,下文我會分析
//當然大部分情況下是不需要改變這個對象的
//讀者先可以考慮不需要改變這個對象,
//那么這個map里面存的工廠就生產(chǎn)就是這個原對象,那么和第三個map功能一樣
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//調(diào)用表達式,說白了就是調(diào)用工廠的方法,然后改變對象
//我們假設對象不需要改變的情況那么返回了原對象就是a
//需要改變的情況我們下文再分享
singletonObject = singletonFactory.getObject();
//然后把這個對象放到第三個map當中
this.earlySingletonObjects.put(beanName, singletonObject);
//把這個對象、或者表達式、或者工廠從第二個map中移除
this.singletonFactories.remove(beanName);
//重點:面試會考---為什么要放到第三個?為什么要移除第二個?
首先我們通過分析做一個總結(jié):
spring首先從第一個map中拿a這個bean
拿不到,從第三個map當中拿a這個對象
拿不到,從第二個map拿a這個對象或者工廠
拿到之后放到第三個map,移除第二個map里面的表達式、或者工廠
如果對象需要改變,當改變完成之后就把他放到第三個里面
這里的情況是b需要a而進行的步驟,試想一下以后如果還有C需要依賴a
就不需要重復第二個map的工作了,也就是改變對象的工作了。
因為改變完成之后的a對象已經(jīng)在第三個map中了。不知道讀者能不能懂筆者的意思
如果對象不需要改變道理是一樣的,也同樣在第三個map取就是了;
至于為什么需要移除第二個map里面的工廠、或者表達式就更好理解了
他已經(jīng)對a做完了改變,改變之后的對象已經(jīng)在第三個map了,為了方便gc啊
下面對為什么需要改變對象做分析
}
}
}
}
return singletonObject;
}
為什么需要改變對象?那個表達式、或者說工廠主要干什么事呢? 那個工廠、或者表達式主要是調(diào)用了下面這個方法
//這個方法內(nèi)容比較少,但是很復雜,因為是對后置處理器的調(diào)用
//關于后置處理器筆者其實要說話很多很多
//現(xiàn)在市面上可見的資料或者書籍對后置處理器的說法筆者一般都不茍同
//我在B站上傳過一個4個小時的視頻,其中對spring后置處理器做了詳細的分析
//也提出了一些自己的理解和主流理解不同的地方,有興趣同學可以去看看
//其實簡單說--這個方法作用主要是為了來處理aop的;
//當然還有其他功能,但是一般的讀者最熟悉的就是aop
//這里我說明一下,aop的原理或者流程有很多書籍說到過
//但是筆者今天親測了,現(xiàn)在市面可見的資料和書籍對aop的說法都不全
//很多資料提到aop是在spring bean的生命周期里面填充屬性之后的代理周期完成的
//而這個代理周期甚至是在執(zhí)行生命周期回調(diào)方法之后的一個周期
//那么問題來了?什么叫spring生命周期回調(diào)方法周期呢?
// 首先spring bean生命周期和spring生命周期回調(diào)方法周期是兩個概念
//spring生命周期回調(diào)方法是spring bean生命周期的一部分、或者說一個周期
//簡單理解就是spring bean的生命的某個過程會去執(zhí)行spring的生命周期的回調(diào)方法
//比如你在某個bean的方法上面寫一個加@PostConstruct的方法(一般稱bean初始化方法)
//那么這個方法會在spring實例化一個對象之后,填充屬性之后執(zhí)行這個加注解的方法
//我這里叫做spring 生命周期回調(diào)方法的生命周期,不是我胡說,有官方文檔可以參考的
//在執(zhí)行完spring生命周期回調(diào)方法的生命周期之后才會執(zhí)行代理生命周期
//在代理這個生命周期當中如果有需要會完成aop的功能
//以上是現(xiàn)在主流的說法,也是一般書籍或者“某些大師”的說法
//但是在循環(huán)引用的時候就不一樣了,循環(huán)引用的情況下這個周期這里就完成了aop的代理
//這個周期嚴格意義上是在填充屬性之前(填充屬性也是一個生命周期階段)
//填充屬性的周期甚至在生命周期回調(diào)方法之前,更在代理這個周期之前了
//簡單來說主流說法代理的生命周期比如在第8個周期或者第八步吧
//但是筆者這里得出的結(jié)論,如果一個bean是循環(huán)引用的則代理的周期可能在第3步就完成了
//那么為什么需要在第三步就完成呢?
//試想一下A、B兩個類,現(xiàn)在對A類做aop處理,也就是需要對A代理
不考慮循環(huán)引用 spring 先實例化A,然后走生命周期確實在第8個周期完成的代理
關于這個結(jié)論可以去看b站我講的spring aop源碼分析
但是如果是循環(huán)依賴就會有問題
比如spring 實例化A 然后發(fā)現(xiàn)需要注入B這個時候A還沒有走到8步
還沒有完成代理,發(fā)覺需要注入B,便去創(chuàng)建B,創(chuàng)建B的時候
發(fā)覺需要注入A,于是創(chuàng)建A,創(chuàng)建的過程中通過getSingleton
得到了a對象,注意是對象,一個沒有完成代理的對象
然后把這個a注入給B?這樣做合適嗎?注入的a根本沒有aop功能;顯然不合適
因為b中注入的a需要是一個代理對象
而這個時候a存在第二個map中;不是一個代理對象;
于是我在第二個map中就不能單純的存一個對象,需要存一個工廠
這個工廠在特殊的時候需要對a對象做改變,比如這里說的代理(需要aop功能的情況)
這也是三個map存在的必要性,不知道讀者能不能get到點
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
總結(jié)關于循環(huán)引用,如何回答面試:
首先spring在單例的情況下是默認支持循環(huán)引用的(當然原形也有辦法,今天先不討論);在不做任何配置的情況下,兩個bean相互依賴是能初始化成功的;spring源碼中在創(chuàng)建bean的時候先創(chuàng)建這個bean的對象,創(chuàng)建對象完成之后通過判斷容器對象的allowCircularReferences屬性決定是否允許緩存這個臨時對象,如果能被緩存成功則通過緩存提前暴露這個臨時對象來完成循環(huán)依賴;而這個屬性默認為true,所以說spring默認支持循環(huán)依賴的,但是這個屬性spring提供了api讓程序員來修改,所以spring也提供了關閉循環(huán)引用的功能;再就是spring完成這個臨時對象的生命周期的過程中當執(zhí)行到注入屬性或者自動裝配的周期時候會通過getSingleton方法去得到需要注入的b對象;而b對象這個時候肯定不存在故而會創(chuàng)建b對象創(chuàng)建b對象成功后繼續(xù)b對象的生命周期,當執(zhí)行到b對象的自動注入周期時候會要求注入a對象;調(diào)用getSingleton;從map緩存中得到a的臨時對象(因為這個時候a在set集合中;這里可以展開講),而且獲取的時候也會判斷是否允許循環(huán)引用,但是判斷的這個值是通過參數(shù)傳進來的,也就是spring內(nèi)部調(diào)用的,spring源碼當中寫死了為true,故而如果需要擴展spring、或者對spring二次開發(fā)的的時候程序員可以自定義這個值來實現(xiàn)自己的功能;不管放到緩存還是從緩存中取出這個臨時都需要判斷;而這兩次判斷spring源碼當中都是默認為true;這里也能再次說明spring默認是支持循環(huán)引用的;
然后面試中可以在說說兩次調(diào)用getSingleton的意義,正在創(chuàng)建的那個set集合有什么用;最后在說說你在看spring循環(huán)引用的時候得出的aop實例化過程的新發(fā)現(xiàn);就比較完美了
如果文字看不懂可以關注這個,我會通過視頻來講解
https://ke.qq.com/course/447566?from=800004101#term_id=100535028
魯班-子路
|