概述
如果想實現(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那一套勋功,如下
使用
ParserStrategyUtils.instantiateClass
方法來實例化ImportBeanDefinitionRegistrar在創(chuàng)建實例后,使用
ParserStrategyUtils.invokeAwareMethods
執(zhí)行Aware库说,進去看一下就執(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對象)