BeanPostProcessor —— 連接Spring IOC和AOP的橋梁

轉(zhuǎn)自:http://www.reibang.com/p/f607ecc99c00
之前都是從大Boss的視角仇哆,來介紹Spring,比如IOC夫植、AOP。
今天換個(gè)視角,從一個(gè)小嘍啰出發(fā)详民,來加深對(duì)Spring的理解延欠。
這個(gè)小嘍啰就是,BeanPostProcessor(下面簡稱BPP)沈跨。

講解思路:

  • BPP怎么用 —— 先學(xué)會(huì)怎么用由捎,再去看原理
  • BPP的觸發(fā)時(shí)機(jī) —— 在整個(gè)Spring Bean初始化流程中的位置
  • BPP自己又是什么時(shí)候被創(chuàng)建的?
  • BPP是如何連接IOC和AOP的饿凛?

怎么用

BeanPostProcessor狞玛,直譯過來,就是“對(duì)象后處理器”涧窒,那么這個(gè)“后”心肪,是指什么之后呢?

試試便知纠吴。

我們先寫一個(gè)對(duì)象硬鞍,Bean4BBP(本文的所有代碼,可到Github上下載):

@Component
public class Bean4BBP {

    private static final Logger log = LoggerFactory.getLogger(Bean4BBP.class);

    public Bean4BBP(){
        log.info("construct Bean4BBP");
    }
}

然后再寫一個(gè)BeanPostProcessor戴已,這時(shí)發(fā)現(xiàn)它是一個(gè)接口固该,沒關(guān)系,那就寫一個(gè)類實(shí)現(xiàn)它糖儡,CustomBeanPostProcessor:

@Component
public 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;
    }
}

然后啟動(dòng)我們的Spring Boot項(xiàng)目(直接運(yùn)行Application類)伐坏,看這幾條日志打印的順序:

construct CustomBeanPostProcessor
construct Bean4BBP
process bean before initialization
process bean after initialization

BBP對(duì)象首先被創(chuàng)建,然后創(chuàng)建Bean4BBP對(duì)象握联,接著再先后執(zhí)行BBP對(duì)象的postProcessBeforeInitialization和postProcessAfterInitialization方法桦沉。

結(jié)論:“對(duì)象后處理器”,指的是“對(duì)象創(chuàng)建后處理器”拴疤。

我們可以利用它永部,在對(duì)象創(chuàng)建之后,對(duì)對(duì)象進(jìn)行修改(有什么場合需要用到呐矾?思考題苔埋,文末回答。)

那么蜒犯,為什么要分postProcessBeforeInitialization和postProcessAfterInitialization呢组橄?這里的Initialization是什么意思?

觸發(fā)時(shí)機(jī)

我們只需要在CustomBeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法里罚随,打上兩個(gè)斷點(diǎn)玉工,一切自然明了。

斷點(diǎn)進(jìn)來淘菩,跟著調(diào)用棧這點(diǎn)蛛絲馬跡往回走遵班,真相大白:

在initializeBean方法里面屠升,先后調(diào)用了applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization方法,這兩個(gè)方法內(nèi)部狭郑,則分別去遍歷系統(tǒng)里所有的BBP腹暖,然后逐個(gè)執(zhí)行這些BBP對(duì)象的postProcessBeforeInitialization和postProcessAfterInitialization方法,去處理對(duì)象翰萨,以applyBeanPostProcessorsBeforeInitialization為例:

那么夾在applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization方法中間的invokeInitMethods方法是做什么的呢脏答?

其實(shí)這個(gè)方法就是Spring提供的,用于對(duì)象創(chuàng)建完之后亩鬼,針對(duì)對(duì)象的一些初始化操作殖告。這就好比你創(chuàng)建了一個(gè)英雄之后,你需要給他進(jìn)行一些能力屬性的初始化雳锋、服裝初始化一樣黄绩。

要驗(yàn)證這一點(diǎn),很簡單魄缚,只需讓Bean4BBP實(shí)現(xiàn)InitializingBean接口:

@Component
public 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");
    }
}

然后重新啟動(dòng)工程宝与,打印順序如下:

construct CustomBeanPostProcessor
construct Bean4BBP
process bean before initialization
init Bean4BBP
process bean after initialization

BBP是什么時(shí)候被初始化的
從上面的代碼片段,我們已經(jīng)知道冶匹,在對(duì)象創(chuàng)建之后习劫,需要遍歷BBP列表,對(duì)對(duì)象進(jìn)行處理嚼隘。

這也就意味著诽里,BBP對(duì)象,必須在普通對(duì)象創(chuàng)建之前被創(chuàng)建飞蛹。

那么BBP都是在什么時(shí)候被創(chuàng)建的呢谤狡?

要回答這個(gè)問題,非常簡單卧檐,我們只需要在CustomBeanPostProcessor的構(gòu)造函數(shù)里打個(gè)斷點(diǎn)(這下看到先學(xué)會(huì)用墓懂,再了解原理的好處了吧)

斷點(diǎn)進(jìn)來,繼續(xù)利用調(diào)用棧霉囚,我們找尋到了AbstractApplicationContext的refresh()方法捕仔,這個(gè)方法里面調(diào)用了registerBeanPostProcessors方法,里頭就已經(jīng)把BBP列表創(chuàng)建好了盈罐,而普通對(duì)象的創(chuàng)建榜跌,是在之后的finishBeanFactoryInitialization方法里執(zhí)行的:


網(wǎng)上有個(gè)圖畫的特別好,很好的展示了BBP在Spring對(duì)象初始化流程的位置:


看到BBP在哪了嗎盅粪?)

BBP的典型使用 - AOP

不知道大家在使用Spring AOP時(shí)钓葫,有沒有發(fā)現(xiàn),帶有切面邏輯的對(duì)象票顾,注入進(jìn)來之后础浮,都不是原來的對(duì)象了帆调,比如下圖:


調(diào)試信息顯示,aspectService是一個(gè)...$$EnhanceBySpringCGlib的對(duì)象豆同,這其實(shí)和Spring AOP用到的動(dòng)態(tài)代理有關(guān)贷帮。

這也就意味著,最終放進(jìn)Spring容器的诱告,必須是代理對(duì)象,而不是原先的對(duì)象民晒,這樣別的對(duì)象在注入時(shí)精居,才能獲得帶有切面邏輯的代理對(duì)象。

那么Spring是怎么做到這一點(diǎn)的呢潜必?正是利用了這篇文章講到的BBP靴姿。

顯然,我只需要寫一個(gè)BBP磁滚,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中佛吓,對(duì)對(duì)象進(jìn)行判斷,看他需不需要織入切面邏輯垂攘,如果需要维雇,那我就根據(jù)這個(gè)對(duì)象,生成一個(gè)代理對(duì)象晒他,然后返回這個(gè)代理對(duì)象吱型,那么最終注入容器的,自然就是代理對(duì)象了陨仅。

這個(gè)服務(wù)于Spring AOP的BBP津滞,叫做AnnotationAwareAspectJAutoProxyCreator.

利用idea的diagram功能,可以看出它和BBP的關(guān)系:

具體的創(chuàng)建代理對(duì)象并返回的邏輯灼伤,在postProcessAfterInitialization方法中触徐,大家自行欣賞。

可以說狐赡,如果沒有BBP撞鹉,那么Spring AOP就只能叫AOP。

BBP是連接IOC和AOP的橋梁猾警。

總結(jié)

這篇文章孔祸,主要通過對(duì)BBP的講解,串聯(lián)起之前講到的關(guān)于Spring的知識(shí)发皿,希望能夠加深大家對(duì)Spring的理解崔慧。

最后,回到開頭提出的四個(gè)問題:

BBP怎么用 —— 先學(xué)會(huì)怎么用穴墅,再去看原理
BBP的觸發(fā)時(shí)機(jī) —— 在整個(gè)Spring Bean初始化流程中的位置
BBP自己又是什么時(shí)候被創(chuàng)建的惶室?
BBP是如何連接IOC和AOP的温自?
也許你弄懂了,也許沒懂皇钞,沒關(guān)系悼泌,這篇文章還是跟以前的文章風(fēng)格不太一樣的,比較嚴(yán)肅夹界,有些門檻馆里。

參考

《Spring揭秘》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市可柿,隨后出現(xiàn)的幾起案子鸠踪,更是在濱河造成了極大的恐慌,老刑警劉巖复斥,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件营密,死亡現(xiàn)場離奇詭異,居然都是意外死亡目锭,警方通過查閱死者的電腦和手機(jī)评汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痢虹,“玉大人被去,你說我怎么就攤上這事∈婪郑” “怎么了编振?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長臭埋。 經(jīng)常有香客問我踪央,道長,這世上最難降的妖魔是什么瓢阴? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任畅蹂,我火速辦了婚禮,結(jié)果婚禮上荣恐,老公的妹妹穿的比我還像新娘液斜。我一直安慰自己,他們只是感情好叠穆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布少漆。 她就那樣靜靜地躺著,像睡著了一般硼被。 火紅的嫁衣襯著肌膚如雪示损。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天嚷硫,我揣著相機(jī)與錄音检访,去河邊找鬼始鱼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脆贵,可吹牛的內(nèi)容都是我干的医清。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼卖氨,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼会烙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筒捺,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤持搜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后焙矛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡残腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年村斟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抛猫。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蟆盹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闺金,到底是詐尸還是另有隱情逾滥,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布败匹,位于F島的核電站寨昙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掀亩。R本人自食惡果不足惜舔哪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望槽棍。 院中可真熱鬧捉蚤,春花似錦、人聲如沸炼七。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豌拙。三九已至陕悬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姆蘸,已是汗流浹背墩莫。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工芙委, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狂秦。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓灌侣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親裂问。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侧啼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 之前都是從大Boss的視角,來介紹Spring堪簿,比如IOC痊乾、AOP。 今天換個(gè)視角椭更,從一個(gè)小嘍啰出發(fā)哪审,來加深對(duì)Sp...
    柳樹之閱讀 2,987評(píng)論 5 58
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn)虑瀑,也是為了防止忘記湿滓,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦舌狗!如果你也喜歡叽奥,那...
    波波波先森閱讀 12,284評(píng)論 6 86
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)痛侍,斷路器朝氓,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • 跟著風(fēng)走 把孤獨(dú)當(dāng)做自由
    墨雨不語閱讀 177評(píng)論 1 1
  • 上有蔚藍(lán)天,垂光抱長街谈截。 兩道家常菜筷屡,果腹暖心田。 蟬噪夏風(fēng)吹簸喂,蛙兒緊相隨毙死。 下樓散步去,歸來靜天黑喻鳄。
    奔跑吧沐夏閱讀 179評(píng)論 0 1