ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區(qū)別

概述

如果想實現(xiàn)自定義注冊bean到spring容器中,常見的做法有兩種

  • @Import+ImportBeanDefinitionRegistrar
  • BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor與ImportBeanDefinitionRegistrar都是接口占哟,通過實現(xiàn)任意一個就可以獲取到bean定義注冊器:BeanDefinitionRegistry,通過調(diào)用其方法

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

就可以給spring容器中新增自定義的bean

那么二者到底有啥區(qū)別东羹,spring為啥會提供兩種方式枫攀,我們?nèi)绾胃鶕?jù)需求進行選擇吶?

使用

首先使用上暇藏,二者的使用方式區(qū)別很大

ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar的用法是@Import+ImportBeanDefinitionRegistrar橄抹,比如現(xiàn)在要把一個spring掃描路徑之外的類加入bean容器靴迫,該類如下

package com.ext; // 不在application主類掃描包下
import lombok.Data;

@Data
public class Ext {
    private String name; // 只有一個name屬性
}

此時可以寫一個注解,并添加defaultName屬性

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ExtScannerRegistrar.class)
public @interface ExtScan {
    String defaultName(); //默認名稱
}

使用@Import注解引入ExtScannerRegistrar楼誓,它就是一個ImportBeanDefinitionRegistrar實現(xiàn)玉锌,代碼如下

public class ExtScannerRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 獲取到ExtScan注解的defaultName屬性
        AnnotationAttributes scanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ExtScan.class.getName()));
        String defaultName = scanAttrs.getString("defaultName");
        // 構(gòu)造Ext的bean定義
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Ext.class);
        // 給name屬性賦值defaultName
        builder.addPropertyValue("name", defaultName);
        // 加入到bean容器
        registry.registerBeanDefinition("ext", builder.getBeanDefinition());
    }
}

此時Ext包雖然不在spring的掃描路徑下,但通過getBean依然可以獲得疟羹,并且得到的bean的name屬性值就是自定注解@ExtScan的指定值主守,如下

@SpringBootApplication
@ExtScan(defaultName = "pq先生")
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        Ext ext = context.getBean(Ext.class);
        System.out.println(ext.getName()); // 輸出:pq先生
    }
    
}
BeanDefinitionRegistryPostProcessor

bean定義后置處理器,同樣是上面的例子榄融,我們使用BeanDefinitionRegistryPostProcessor把Ext類加入spring容器参淫,寫法如下

@Component // 本身首先是個bean
public class ExtRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 構(gòu)造Ext的bean定義
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Ext.class);
        // 加入到bean容器
        registry.registerBeanDefinition("ext", builder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 不用
    }
}

同樣get,去掉上一步的@ExtScan注解

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        Ext ext = context.getBean(Ext.class); 
        System.out.println(ext.getName()); // 輸出null
    }
    
}

同樣可以把Ext加入bean容器愧杯,因為沒有設(shè)置name涎才,所以name是null

原理

二者的執(zhí)行邏輯和時機可以參照一文通透spring的初始化,這里就簡單總結(jié)一下力九,不做贅述

  • @Import是spring啟動執(zhí)行BeanFactory后置處理器時被spring內(nèi)置后置處理器ConfigurationClassPostProcessor解析耍铜,如果發(fā)現(xiàn)@Import引入的是一個ImportBeanDefinitionRegistrar的實現(xiàn),則會立即調(diào)用其registerBeanDefinitions方法
  • spring啟動執(zhí)行BeanFactory后置處理器時跌前,BeanDefinitionRegistryPostProcessor的實現(xiàn)作為bean首先被ConfigurationClassPostProcessor掃描并加入spring容器中棕兼,后續(xù)會再去spring容器中查找所有的后置處理器并執(zhí)行

其實二者的執(zhí)行時機都是:spring啟動執(zhí)行BeanFactory后置處理器,其中ConfigurationClassPostProcessor作為spring內(nèi)置的后置處理器執(zhí)行優(yōu)先級較高抵乓,他的內(nèi)部會調(diào)用ImportBeanDefinitionRegistrar的實現(xiàn)伴挚,而BeanDefinitionRegistryPostProcessor與ConfigurationClassPostProcessor一樣都是后置處理器靶衍,屬于同級別的(其實是由ConfigurationClassPostProcessor衍生出來),會在ConfigurationClassPostProcessor執(zhí)行完畢后依次被執(zhí)行

從這里看茎芋,二者的定位不太一樣ImportBeanDefinitionRegistrar輸入后置處理器ConfigurationClassPostProcessor的一個自邏輯颅眶,BeanDefinitionRegistryPostProcessor本身就是一個后置處理器

當(dāng)然這是本質(zhì)上的區(qū)別,具體還要看使用區(qū)別

區(qū)別

ImportBeanDefinitionRegistrar的優(yōu)勢

從上面那個例子上其實可以看出败徊,ImportBeanDefinitionRegistrar的registerBeanDefinitions方法相較于BeanDefinitionRegistryPostProcessor多了個AnnotationMetadata參數(shù),利用這個參數(shù)可以獲取到含有@Import注解的類的一些屬性掏缎,比如上面的defaultName皱蹦,這樣用戶就可以通過注解的屬性定制化一些功能,例如我們常用mybaits眷蜈,可以通過

@MapperScan("com.pq.xxx")

指定掃描的包名沪哺,方便實現(xiàn)用的自定義配置,這一點是BeanDefinitionRegistryPostProcessor做不到的

且@Import自帶的把第三方pojo引入spring的特性酌儒,加上注解編程的優(yōu)雅辜妓,讓@Import+ImportBeanDefinitionRegistrar組合在很多第三方工具框架很常見

BeanDefinitionRegistryPostProcessor的優(yōu)勢

BeanDefinitionRegistryPostProcessor的實現(xiàn)首先是一個bean,如上例使用@Component注解才會生效忌怎,作為bean本身籍滴,自定義后置處理器可以依賴注入其它bean,也可以實現(xiàn)各種Aware以得到上下文環(huán)境榴啸,所有常規(guī)bean的生命周期和功能它都有孽惰,這一點是作為POJO的ImportBeanDefinitionRegistrar實現(xiàn)不具備的

實際上ImportBeanDefinitionRegistrar也可以實現(xiàn)幾個固定的Aware,但ImportBeanDefinitionRegistrar的實例化代碼是ConfigurationClassParser單獨實現(xiàn)的鸥印,并不是createBean那一套勋功,如下

ConfigurationClassParser

使用ParserStrategyUtils.instantiateClass方法來實例化ImportBeanDefinitionRegistrar
ParserStrategyUtils.instantiateClass

在創(chuàng)建實例后,使用ParserStrategyUtils.invokeAwareMethods執(zhí)行Aware库说,進去看一下
ParserStrategyUtils.invokeAwareMethods

就執(zhí)行這固定四個Aware:BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware

這相比于spring bean聲明周期中的Aware少太多

總結(jié)

今天大概梳理一下ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區(qū)別狂鞋,二者各有自己的優(yōu)勢,了解了區(qū)別潜的,至于使用哪個骚揍,就看使用場景就可以了

當(dāng)然也可以二者一起使用,即ImportBeanDefinitionRegistrar注冊的bean是一個BeanDefinitionRegistryPostProcessor的實現(xiàn)啰挪,這樣就形成了@Import+ImportBeanDefinitionRegistrar+BeanDefinitionRegistryPostProcessor

比如mybaits就是使用這種方式疏咐,整合了二者的優(yōu)勢(既可以使用@MapperScan+@Import+ImportBeanDefinitionRegistrar來定制掃描包,又可以通過BeanDefinitionRegistryPostProcessor在注冊bean前通過ApplicationContextAware獲得applicationContext對象)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脐供,一起剝皮案震驚了整個濱河市浑塞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌政己,老刑警劉巖酌壕,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掏愁,死亡現(xiàn)場離奇詭異,居然都是意外死亡卵牍,警方通過查閱死者的電腦和手機果港,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糊昙,“玉大人辛掠,你說我怎么就攤上這事∈臀” “怎么了萝衩?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長没咙。 經(jīng)常有香客問我猩谊,道長,這世上最難降的妖魔是什么祭刚? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任牌捷,我火速辦了婚禮,結(jié)果婚禮上涡驮,老公的妹妹穿的比我還像新娘暗甥。我一直安慰自己,他們只是感情好捉捅,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布淋袖。 她就那樣靜靜地躺著,像睡著了一般锯梁。 火紅的嫁衣襯著肌膚如雪即碗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天陌凳,我揣著相機與錄音剥懒,去河邊找鬼。 笑死合敦,一個胖子當(dāng)著我的面吹牛初橘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播充岛,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼保檐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了崔梗?” 一聲冷哼從身側(cè)響起夜只,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蒜魄,沒想到半個月后扔亥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體场躯,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年旅挤,在試婚紗的時候發(fā)現(xiàn)自己被綠了踢关。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡粘茄,死狀恐怖签舞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柒瓣,我是刑警寧澤儒搭,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站嘹朗,受9級特大地震影響师妙,放射性物質(zhì)發(fā)生泄漏诵肛。R本人自食惡果不足惜屹培,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怔檩。 院中可真熱鬧褪秀,春花似錦、人聲如沸薛训。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乙埃。三九已至闸英,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間介袜,已是汗流浹背甫何。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遇伞,地道東北人辙喂。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鸠珠,于是被迫代替她去往敵國和親巍耗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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