掀起Spring擴(kuò)展點(diǎn)的蓋頭來ImportBeanDefinitionRegistrar分析與實(shí)戰(zhàn)
本文開始,我們將系統(tǒng)地對(duì)Spring框架的擴(kuò)展點(diǎn)進(jìn)行學(xué)習(xí),通過案例分析與圖例結(jié)合,step by step地對(duì)Spring看似神秘的擴(kuò)展點(diǎn)的機(jī)理與應(yīng)用進(jìn)行研究。
首先通過一張圖對(duì)Spring框架各種擴(kuò)展點(diǎn)的調(diào)用順序(Bean生命周期)進(jìn)行先入為主的概覽。

可以看到圖片的一開始便是Spring對(duì)Bean定義(BeanDefinition)進(jìn)行解析和注冊(cè),Bean的注冊(cè)主要就是通過ImportBeanDefinitionRegistrar實(shí)現(xiàn)的。
Spring框架主要就是通過ImportBeanDefinitionRegistrar實(shí)現(xiàn)對(duì)bean的動(dòng)態(tài)注冊(cè)。源碼如下:
????public?interface?ImportBeanDefinitionRegistrar?{
????????//?通過解析給定的注解元信息,向Spring容器中注冊(cè)Bean定義
????????default?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry,
????????????????BeanNameGenerator?importBeanNameGenerator)?{
????????????registerBeanDefinitions(importingClassMetadata,?registry);
????????}
????????default?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry)?{
????????}
????}
實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類的都會(huì)被ConfigurationClassPostProcessor處理,ConfigurationClassPostProcessor實(shí)現(xiàn)了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中動(dòng)態(tài)注冊(cè)的bean是優(yōu)先于依賴其的bean初始化的,同時(shí)它也可以被aop、validator等機(jī)制處理。
編碼實(shí)現(xiàn)手動(dòng)Bean注入
?日常的業(yè)務(wù)開發(fā)中,我們很少會(huì)通過ImportBeanDefinitionRegistrar來對(duì)bean進(jìn)行注入。
?
?而是通過xml文件聲明或者注解如:@Component、@Service、@Bean等方式對(duì)bean進(jìn)行注入和聲明。
?
?那么什么場(chǎng)景下才需要通過ImportBeanDefinitionRegistrar注冊(cè)并注入bean呢?
?
在中間件開發(fā)場(chǎng)景下,就會(huì)用到手動(dòng)bean注入。原因在于中間件/框架的開發(fā)者并不知道調(diào)用方/框架使用者是通過什么方式對(duì)bean進(jìn)行注入的。
當(dāng)然我們也可以讓使用者們顯式的對(duì)框架中的bean進(jìn)行定義,但是這樣就顯著的增加了工作量和出錯(cuò)率,因此對(duì)于框架開發(fā)而言,常常通過ImportBeanDefinitionRegistrar實(shí)現(xiàn)bean的隱式注入和聲明,減少調(diào)用方整合框架的復(fù)雜度。
?我們通過一個(gè)模擬場(chǎng)景來介紹一下如何通過編碼實(shí)現(xiàn)bean的手動(dòng)隱式注入。、
?
1、定義ImportBeanDefinitionRegistrar實(shí)現(xiàn)類
首先定義一個(gè)實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類,并編寫bean注冊(cè)邏輯。
????public?class?MyBeanDefinationRegistry?implements?ImportBeanDefinitionRegistrar,?ResourceLoaderAware,?BeanFactoryAware?{
????????private?BeanFactory?beanFactory;
????????private?ResourceLoader?resourceLoader;
????????@Override
????????public?void?setBeanFactory(BeanFactory?beanFactory)?throws?BeansException?{
????????????this.beanFactory?=?beanFactory;
????????}
????????@Override
????????public?void?setResourceLoader(ResourceLoader?resourceLoader)?{
????????????this.resourceLoader?=?resourceLoader;
????????}
????????@Override
????????public?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry)?{
????????????MyClassPathBeanDefinitionScanner?scanner?=?new?MyClassPathBeanDefinitionScanner(registry,?false);
????????????scanner.setResourceLoader(resourceLoader);
????????????scanner.registerFilters();
????????????scanner.doScan("com.spring.framework");
????????????GenericBeanDefinition?genericBeanDefinition?=?new?GenericBeanDefinition();
????????????genericBeanDefinition.setBeanClass(TestBean.class);
????????????genericBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
????????????registry.registerBeanDefinition("testBean",?genericBeanDefinition);
????????}
????}
重點(diǎn)關(guān)注 「BeanDefinitionRegistry」 方法,這里提供了兩種bean掃描方式。
方式1:基于包路徑的掃描
????????MyClassPathBeanDefinitionScanner?scanner?=?
????????????new?MyClassPathBeanDefinitionScanner(registry,?false);
????????scanner.setResourceLoader(resourceLoader);
????????scanner.registerFilters();
????????scanner.doScan("com.spring.framework");
- 自定義一個(gè)ClassPathBeanDefinitionScanner實(shí)例,并將bean定義注冊(cè)器BeanDefinitionRegistry引用傳遞進(jìn)去,這是一種委托機(jī)制;
- 設(shè)置ResourceLoader,ResourceLoader的引用通過ResourceLoaderAware獲得,并指向當(dāng)前類的成員變量;
- 調(diào)用registerFilters方法(該方法為自定義方法,本質(zhì)是調(diào)用了addIncludeFilter),讓Spring去掃描帶有特定標(biāo)志的類進(jìn)行管理與加載;(具體的代碼稍后進(jìn)行分析);
- 調(diào)用doScan,傳遞需要掃描的包路徑,這個(gè)路徑就是框架開發(fā)者自定義的包路徑,該路徑下存放的就是框架本身的bean,「這個(gè)路徑是完全由框架的開發(fā)者決定的,而且我們一般可以認(rèn)為,該路徑一旦定義就不會(huì)更改。并且該路徑也不適合暴露給框架的調(diào)用者」。
方式2:直接注冊(cè)BeanDefinition
????????GenericBeanDefinition?genericBeanDefinition?=?new?GenericBeanDefinition();
????????genericBeanDefinition.setBeanClass(TestBean.class);
????????genericBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
????????registry.registerBeanDefinition("testBean",?genericBeanDefinition);
方式2比較簡(jiǎn)單,但是相對(duì)的也比方式1繁瑣。
?TestBean 是模擬的一個(gè)框架的內(nèi)部bean組件,實(shí)際開發(fā)中可以根據(jù)需要填充必要的屬性和方法,這里只是作為演示。
?
????public?class?TestBean?{
????}
通過聲明GenericBeanDefinition,并為其添加需要注冊(cè)的Bean的class,scope(單例or多例),beanName等屬性,具體的屬性可以看下圖:

最后通過 「registry.registerBeanDefinition」 將設(shè)置好屬性的GenericBeanDefinition注冊(cè),并設(shè)置beanName;
對(duì)比方式1方式2
通過代碼我們可以直觀的看到,方式1比方式2更加方便,可以實(shí)現(xiàn)批量bean的掃描與注入;
而方式2則需要逐個(gè)bean進(jìn)行注入,但是相對(duì)的,方式2也更加靈活,能夠?qū)崿F(xiàn) 「細(xì)粒度」 的beanDefinition聲明和定義。
2、定義ClassPathBeanDefinitionScanner實(shí)現(xiàn)類
通過定義ClassPathBeanDefinitionScanner的實(shí)現(xiàn)類,告訴Spring需要對(duì)哪些類進(jìn)行管理(addIncludeFilter)以及不需要關(guān)注哪些類(addExcludeFilter)。
????public?class?MyClassPathBeanDefinitionScanner?extends?ClassPathBeanDefinitionScanner?{
????????public?MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry?registry,?boolean?useDefaultFilters)?{
????????????super(registry,?useDefaultFilters);
????????}
????????/**
????????*?比較重要的一個(gè)點(diǎn)就是registerFilters()這個(gè)方法,
????????*?在里面我們可以定義讓Spring去掃描帶有特定標(biāo)志的類選擇進(jìn)行管理或者是選擇不管理;
????????*?通過addIncludeFilter()方法和通過addExcludeFilter()方法;
????????*/
????????protected?void?registerFilters()?{
????????????/**
????????????*??TODO?addIncludeFilter??滿足任意includeFilters會(huì)被加載
????????????*/
????????????addIncludeFilter(new?AnnotationTypeFilter(SnoWalkerAutoInject.class));
????????}
????????@Override
????????protected?Set<BeanDefinitionHolder>?doScan(String...?basePackages)?{
????????????return?super.doScan(basePackages);
????????}
????}
可以看到,掃描器ClassPathBeanDefinitionScanner掃描類路徑上的需要被管理的類,通過BeanFactory創(chuàng)建Bean給ApplicationComtext(Spring容器)管理;
registerFilters分析
registerFilters是自定義的方法,核心的邏輯就是通過addIncludeFilter添加了一個(gè)包掃描的規(guī)則:
這里是通過注解類型的Filter通知Spring容器對(duì)添加了SnoWalkerAutoInject自定義注解的bean進(jìn)行管理。
我們可以看到,自定義的MyClassPathBeanDefinitionScanner重寫了父類的doScan方法,本質(zhì)上調(diào)用了父類的doScan,以實(shí)現(xiàn)對(duì)指定路徑下的bean進(jìn)行掃描。
最終實(shí)際上是在ApplicationContext中調(diào)用了doScan,實(shí)現(xiàn)了對(duì)bean定義的掃描及實(shí)例化,我們可以看一下源碼實(shí)現(xiàn):
?/**
??*?Create?a?new?AnnotationConfigApplicationContext,?scanning?for?components
??*?in?the?given?packages,?registering?bean?definitions?for?those?components,
??*?and?automatically?refreshing?the?context.
??*?@param?basePackages?the?packages?to?scan?for?component?classes
??*/
?public?AnnotationConfigApplicationContext(String...?basePackages)?{
??this();
??scan(basePackages);
??refresh();
?}
AnnotationConfigApplicationContext構(gòu)造方法中,對(duì)package進(jìn)行了掃描,并調(diào)用refresh方法對(duì)bean進(jìn)行初始化和實(shí)例化。
3、自定義注解
自定義注解,并添加到需要裝載到Spring容器中的框架類上:
????@Documented
????@Inherited
????@Retention(RetentionPolicy.RUNTIME)
????@Target({ElementType.TYPE,?ElementType.FIELD,?ElementType.METHOD,?ElementType.PARAMETER})
????public?@interface?SnoWalkerAutoInject?{
????}
定義幾個(gè)模擬的框架類,用以模擬框架的邏輯。實(shí)際的開發(fā)中,我們可以按照需求的實(shí)際需要,開發(fā)框架代碼,并標(biāo)記自定義的注解。
????@SnoWalkerAutoInject
????public?class?FrameWorkConfigA?{
????????public?FrameWorkConfigA()?{
????????????System.out.println("自定義框架組件A-初始化邏輯");
????????}
????}
????@SnoWalkerAutoInject
????public?class?FrameWorkConfigB?{
????????public?FrameWorkConfigB()?{
????????????System.out.println("自定義框架組件B-初始化邏輯");
????????}
????}
????@SnoWalkerAutoInject
????public?class?FrameWorkConfigC?{
????????public?FrameWorkConfigC()?{
????????????System.out.println("自定義框架組件C-初始化邏輯");
????????}
????}
4、配置ImportBeanDefinitionRegistrar實(shí)現(xiàn)類
如何使用自定義的ImportBeanDefinitionRegistrar實(shí)現(xiàn)類對(duì)bean進(jìn)行裝載呢?
最終我們還是需要定義一個(gè)配置類,通過@Import注解配置ImportBeanDefinitionRegistrar實(shí)現(xiàn)。
????@Configuration
????@Import(MyBeanDefinationRegistry.class)
????@ComponentScan("com.spring.framework")
????public?class?MyConf?{
????}
- MyConf是自定義的配置類,標(biāo)注了 @Configuration 注解。
- 通過@Import將實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口的MyBeanDefinationRegistry包含進(jìn)來;
- 添加掃描包,以方便spring對(duì)該包下的類進(jìn)行掃描并進(jìn)行選擇性的裝載;
4、測(cè)試
編寫測(cè)試類:
????public?class?App?{
????????public?static?void?main(String[]?args)?{
????????????ApplicationContext?applicationContext?=?new?AnnotationConfigApplicationContext("com.spring");
????????????final?TestBean?testBean?=?(TestBean)?applicationContext.getBean("testBean");
????????????System.out.println(testBean.getClass());
????????}
????}
- 首先我們聲明并初始化一個(gè)AnnotationConfigApplicationContext容器;
- 接著從容器中通過BeanName獲取通過GenericBeanDefinition定義的TestBean實(shí)例,打印其Class類型;
- 觀察日志輸出,期望能夠看到框架代碼FrameWorkConfigA、FrameWorkConfigB、FrameWorkConfigC的構(gòu)造方法日志打印,并看到TestgBean的Class類型打印。
運(yùn)行測(cè)試類,觀察控制臺(tái)日志輸出:
????自定義框架組件A-初始化邏輯
????自定義框架組件B-初始化邏輯
????自定義框架組件C-初始化邏輯
????class?com.spring.TestBean
可以看到符合預(yù)期,這表明,通過ImportBeanDefinitionRegistrar自定義手動(dòng)bean注入符合預(yù)期。
總結(jié)
本文我們?nèi)獙?duì)ImportBeanDefinitionRegistrar在Spring容器裝載bean的過程進(jìn)行了綜述,并通過一個(gè)模擬框架開發(fā)的案例,對(duì)如何通過ImportBeanDefinitionRegistrar實(shí)現(xiàn)bean的自定義注入進(jìn)行了代碼級(jí)別的講解和分析。
如果在實(shí)際的開發(fā)案例中需要實(shí)現(xiàn)自定義的bean注入,減少調(diào)用方整合的復(fù)雜度,那么我們完全可以通過本文講解的方式,利用ImportBeanDefinitionRegistrar擴(kuò)展點(diǎn)實(shí)現(xiàn)。
下期預(yù)告:
下期我們將分析講解BeanPostProcessor擴(kuò)展點(diǎn)在Spring框架中的作用,并講解BeanPostProcessor在實(shí)戰(zhàn)開發(fā)中的使用。
