前兩天我們說明了 Spring 底層是怎麼建立代理物件、以及代理物件的擴充方法是怎麼作用在目標物件上,今天我們試著把所有的東西揉合在一起,完整兜出一個切面代理物件的從 0 到 1。具體來說,Spring 的 AOP 自動化機制,主要依賴於一個 Spring AOP 的 BPP 組件 - Abstract-Auto-Proxy-Creator(後續簡稱 Proxy Creator)。顧名思義,它是一個抽象類別,且主要功能是進行代理物件的自動創建。
因為依賴鏈上實作的是最原始的 BPP,所以 Proxy Creator 的主要作用時機在初始化前後的位置,準確來說,大部分的邏輯都會集中在 post-Process-After-Initialization(也就是最後最後的 after 方法)上,原因也很單純:如果不把代理的邏輯擺在所有步驟的最後,那其他的步驟都要額外處理「是否為代理物件」的邏輯判斷,對其餘的 BPP 加工(或依賴注入賦值 ...等)都會是不小的負擔。
Proxy Creator 的 after 方法很單純,可以簡單分成兩個步驟,首先會從快取檢查代理物件是不是已經在循環依賴的時候就創建過了,如果是,就會提早返回,不重複執行代理的創建邏輯,但如果原先沒有提早創建,邏輯就會走到創建和新的 wrapIfNecessary 方法裡。wrapIfNecessary 裡面主要會做 3 個判斷:
首先會做一次初步的過濾跟檢查判定,如果 Bean 本身有做一些特殊的標記(例如被標為不需要代理、或者是 Bean 本身就是 AOP 框架的相關 Bean ...等),這裡就會中斷代理的創建,返回原始的 Bean;第二步是檢查 Advisors,就像昨天說的,「Advisor 在被建立完成之後會被存在一個 BPP 的快取裡面。」這裡的 BPP 指的就是 ProxyCreator。ProxyCreator 會在這時候把所有的 Advisors 都拿出來,逐一比對類別資訊(和方法資訊)是否符合切面的約束;如果有找到匹配 Advisors,就會進到 build-Proxy 的創建邏輯。
build-Proxy 首先會創建一個乾淨的代理工廠,並把 ProxyCreator 的全域設定都複製到新工廠上,接著會決定這次的代理物件是要用 CGLIB 代理完成、還是 JDK 動態代理。決定好之後,代理工廠就會組裝 interceptor chain 和目標物件,最後取得 CGLIB 代理(或 JDK 動態代理)的 ClassLoader,並呼叫它們完成代理物件的建立。至此,一個完整的 AOP 代理物件便在 Spring 容器中正式誕生。
後續我們如果在系統當中呼叫代理物件的相關方法,就會走回 2 天前有提到的核心方法上:當代理物件被呼叫到,如果走的是 JDK 動態代理,底層就會調用 invoke 方法,完成以責任鏈為設計結構的代理邏輯;而如果是 CGLIB 代理,則會調用到裡面的 intercept 方法,同樣去執行 AOP 的切面邏輯與目標業務方法。
再來,我們可以補充一個小東西:Proxy Creator 除了改寫初始化後的方法外,其實也改寫了實例化前的 post-Process-Before-Instantiation 方法。但這要配合著實體類的 AspectJ-Aware-Advisor-Auto-Proxy-Creator 一併閱讀:當第一個 Bean 執行到實例化前的階段,會觸發到 ProxyCreator 的實例化前方法,這時候 ProxyCreator 就會執行一次實例化前的相關邏輯。
其中,實體類的 shouldSkip 方法會完整的掃描一次所有的 Advisors,具體來說,方法內部會一口氣走訪容器中的所有 BD,並把所有標註 @Aspect 註解的類別與裡面的切面方法都掃描出來,再轉化成昨天說到的 Advisor 規格物件,後續在初始化後,原始的 Bean 才能完整取得 Advisor 清單。這也是為什麼「帶有切面註解的類別,需要在類別的地方掛上 @Aspect 註解,才能被正常啟用」的核心原因。
最後,我們回收一個在依賴循環那篇提過的小伏筆:提早生成代理物件的方式有很多,但最最常見的產生情形,是在循環依賴時,A bean 先對 B 進行 getBean,但 B 又回頭 get A 的情況,這時候,為了解決兩者這個迴圈,Spring 會觸發第三層快取中存放的 ObjectFactory。並取出剛實例化完成、但屬性尚未填入的『半成品 A』,提早執行 getEarlyBeanReference 進行代理判斷。如果 A 需要被代理,就在此處提早生成 Proxy 並放回第二層快取。
提前代理的原因也很單純,因為我們如果回傳原始的 A 回去,後續從 B 呼叫的 A 方法就都不會經過正確的代理邏輯(因為注入進去的是原始物件),因此,在這裡進行提早代理就是一個正確且必要的行為。當然了,這裡也不用擔心整個系統會存在 2 的原始的 A,因為原始 A 在完成後續的屬性注入與初始化後,Spring 會在最後一步自動從第二層快取取出已建好的 Proxy 物件替換回傳,確保注入給 B 與最終註冊進單例池的,都是同一個具備完整切面能力的代理實例。