講解思路:
- BBP怎麼用 —— 先學會怎麼用,再去看原理
- BBP的觸發時機 —— 在整個Spring Bean初始化流程中的位置
- BBP自己又是什麼時候被建立的?
- BBP是如何連線IOC和AOP的?
怎麼用
BeanPostProcessor,直譯過來,就是“物件後處理器”, 那麼這個“後”,是指什麼之後呢?
試試便知。
我們先寫一個物件,Bean4BBP( 本文的所有程式碼,可到 Github 上下載 ):
@Componentpublic class Bean4BBP { private static final Logger log = LoggerFactory.getLogger(Bean4BBP.class); public Bean4BBP(){ log.info("construct Bean4BBP"); }}複製程式碼
然後再寫一個BeanPostProcessor,這時發現它是一個介面,沒關係,那就寫一個類實現它,CustomBeanPostProcessor:
@Componentpublic class CustomBeanPostProcessor implements BeanPostProcessor { private static final Logger log = LoggerFactory.getLogger(CustomBeanPostProcessor.class); public CustomBeanPostProcessor() { log.info("construct CustomBeanPostProcessor"); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Bean4BBP) { log.info("process bean before initialization"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Bean4BBP) { log.info("process bean after initialization"); } return bean; }}複製程式碼
然後啟動我們的Spring Boot專案(直接執行Application類),看這幾條日誌列印的順序:
construct CustomBeanPostProcessorconstruct Bean4BBPprocess bean before initializationprocess bean after initialization複製程式碼
BBP物件首先被建立,然後建立Bean4BBP物件,接著再先後執行BBP物件的postProcessBeforeInitialization和postProcessAfterInitialization方法。
結論:“物件後處理器”,指的是“ 物件建立後處理器 ”。
我們可以利用它,在物件建立之後,對物件進行修改(有什麼場合需要用到?思考題,文末回答。)
那麼,為什麼要分postProcessBeforeInitialization和postProcessAfterInitialization呢?這裡的Initialization是什麼意思?
觸發時機
我們只需要在CustomBeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法裡,打上兩個斷點,一切自然明瞭。
斷點進來,跟著呼叫棧這點蛛絲馬跡往回走,真相大白:
在initializeBean方法裡面,先後呼叫了applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization方法,這兩個方法內部,則分別去遍歷系統裡所有的BBP,然後逐個執行這些BBP物件的postProcessBeforeInitialization和postProcessAfterInitialization方法,去處理物件,以applyBeanPostProcessorsBeforeInitialization為例:
那麼夾在applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization方法中間的invokeInitMethods方法是做什麼的呢?
其實這個方法就是Spring提供的,用於物件建立完之後,針對物件的一些初始化操作。這就好比你建立了一個英雄之後,你需要給他進行一些能力屬性的初始化、服裝初始化一樣。
要驗證這一點,很簡單,只需讓Bean4BBP實現InitializingBean介面:
@Componentpublic class Bean4BBP implements InitializingBean { private static final Logger log = LoggerFactory.getLogger(Bean4BBP.class); public Bean4BBP(){ log.info("construct Bean4BBP"); } @Override public void afterPropertiesSet() throws Exception { log.info("init Bean4BBP"); }}複製程式碼
然後重新啟動工程,列印順序如下:
construct CustomBeanPostProcessorconstruct Bean4BBPprocess bean before initializationinit Bean4BBPprocess bean after initialization複製程式碼
BBP是什麼時候被初始化的
從上面的程式碼片段,我們已經知道,在物件建立之後,需要遍歷BBP列表,對物件進行處理。
這也就意味著, BBP物件,必須在普通物件建立之前被建立 。
那麼BBP都是在什麼時候被建立的呢?
要回答這個問題,非常簡單, 我們只需要在CustomBeanPostProcessor的建構函式裡打個斷點 (這下看到先學會用,再瞭解原理的好處了吧)
斷點進來,繼續利用呼叫棧,我們找尋到了AbstractApplicationContext的refresh()方法,這個方法裡面呼叫了registerBeanPostProcessors方法,裡頭就已經把BBP列表建立好了,而普通物件的建立,是在之後的finishBeanFactoryInitialization方法裡執行的:
網上有個圖畫的特別好,很好的展示了BBP在Spring物件初始化流程的位置:
(看到BBP在哪了嗎?)
BBP的典型使用 - AOP
不知道大家在使用Spring AOP時,有沒有發現,帶有切面邏輯的物件,注入進來之後,都不是原來的物件了,比如下圖:
除錯資訊顯示,aspectService是一個…$$EnhanceBySpringCGlib的物件,這其實和Spring AOP用到的動態代理有關。
關於Spring AOP的原理,可以參考我之前的回答: 什麼是面向切面程式設計AOP? - Javdroider Hong的回答 - 知乎
這也就意味著, 最終放進Spring容器的,必須是代理物件,而不是原先的物件 ,這樣別的物件在注入時,才能獲得帶有切面邏輯的代理物件。
那麼Spring是怎麼做到這一點的呢?正是利用了這篇文章講到的BBP。
顯然,我只需要寫一個BBP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,對物件進行判斷,看他需不需要織入切面邏輯,如果需要,那我就根據這個物件,生成一個代理物件,然後返回這個代理物件,那麼最終注入容器的,自然就是代理物件了。
這個服務於Spring AOP的BBP,叫做 AnnotationAwareAspectJAutoProxyCreator .
利用idea的diagram功能,可以看出它和BBP的關係:
具體的建立代理物件並返回的邏輯,在postProcessAfterInitialization方法中,大家自行欣賞。
可以說,如果沒有BBP,那麼Spring AOP就只能叫AOP。
BBP是連線IOC和AOP的橋樑。
總結
這篇文章,主要通過對BBP的講解,串聯起之前講到的關於Spring的知識,希望能夠加深大家對Spring的理解。
回到開頭提出的四個問題:
- BBP怎麼用 —— 先學會怎麼用,再去看原理
- BBP的觸發時機 —— 在整個Spring Bean初始化流程中的位置
- BBP自己又是什麼時候被建立的?
- BBP是如何連線IOC和AOP的?
最後:
給大家分享資深架構師錄製的視訊:(有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化、分散式架構)等這些成為架構師必備的內容,還有阿里大牛直播講解技術!群號:685167672(無Java開發經驗勿加,你聽不懂)