概述
如果想實現自定義註冊bean到spring容器中,常見的做法有兩種
- @Import+ImportBeanDefinitionRegistrar
- BeanDefinitionRegistryPostProcessor
BeanDefinitionRegistryPostProcessor與ImportBeanDefinitionRegistrar都是接口,通過實現任意一個就可以獲取到bean定義註冊器:BeanDefinitionRegistry,通過調用其方法
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
就可以給spring容器中新增自定義的bean
那麼二者到底有啥區別,spring為啥會提供兩種方式,我們如何根據需求進行選擇吶?
使用
首先使用上,二者的使用方式區別很大
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar的用法是@Import+ImportBeanDefinitionRegistrar,比如現在要把一個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實現,代碼如下
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");
// 構造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 {
// 構造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容器,因為沒有設置name,所以name是null
原理
二者的執行邏輯和時機可以參照一文通透spring的初始化,這裏就簡單總結一下,不做贅述
- @Import是spring啓動執行BeanFactory後置處理器時被spring內置後置處理器
ConfigurationClassPostProcessor解析,如果發現@Import引入的是一個ImportBeanDefinitionRegistrar的實現,則會立即調用其registerBeanDefinitions方法 - 在spring啓動執行BeanFactory後置處理器時,BeanDefinitionRegistryPostProcessor的實現作為bean首先被
ConfigurationClassPostProcessor掃描並加入spring容器中,後續會再去spring容器中查找所有的後置處理器並執行
其實二者的執行時機都是:spring啓動執行BeanFactory後置處理器,其中ConfigurationClassPostProcessor作為spring內置的後置處理器執行優先級較高,他的內部會調用ImportBeanDefinitionRegistrar的實現,而BeanDefinitionRegistryPostProcessor與ConfigurationClassPostProcessor一樣都是後置處理器,屬於同級別的(其實是由ConfigurationClassPostProcessor衍生出來),會在ConfigurationClassPostProcessor執行完畢後依次被執行
從這裏看,二者的定位不太一樣ImportBeanDefinitionRegistrar輸入後置處理器ConfigurationClassPostProcessor的一個自邏輯,BeanDefinitionRegistryPostProcessor本身就是一個後置處理器
當然這是本質上的區別,具體還要看使用區別
區別
ImportBeanDefinitionRegistrar的優勢
從上面那個例子上其實可以看出,ImportBeanDefinitionRegistrar的registerBeanDefinitions方法相較於BeanDefinitionRegistryPostProcessor多了個AnnotationMetadata參數,利用這個參數可以獲取到含有@Import註解的類的一些屬性,比如上面的defaultName,這樣用户就可以通過註解的屬性定製化一些功能,例如我們常用mybaits,可以通過
@MapperScan("com.pq.xxx")
指定掃描的包名,方便實現用的自定義配置,這一點是BeanDefinitionRegistryPostProcessor做不到的
且@Import自帶的把第三方pojo引入spring的特性,加上註解編程的優雅,讓@Import+ImportBeanDefinitionRegistrar組合在很多第三方工具框架很常見
BeanDefinitionRegistryPostProcessor的優勢
BeanDefinitionRegistryPostProcessor的實現首先是一個bean,如上例使用@Component註解才會生效,作為bean本身,自定義後置處理器可以依賴注入其它bean,也可以實現各種Aware以得到上下文環境,所有常規bean的生命週期和功能它都有,這一點是作為POJO的ImportBeanDefinitionRegistrar實現不具備的
實際上ImportBeanDefinitionRegistrar也可以實現幾個固定的Aware,但ImportBeanDefinitionRegistrar的實例化代碼是ConfigurationClassParser單獨實現的,並不是createBean那一套,如下
ConfigurationClassParser
使用ParserStrategyUtils.instantiateClass方法來實例化ImportBeanDefinitionRegistrar
ParserStrategyUtils.instantiateClass
在創建實例後,使用ParserStrategyUtils.invokeAwareMethods執行Aware,進去看一下
ParserStrategyUtils.invokeAwareMethods
就執行這固定四個Aware:BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware
這相比於spring bean聲明週期中的Aware少太多
總結
今天大概梳理一下ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區別,二者各有自己的優勢,瞭解了區別,至於使用哪個,就看使用場景就可以了
當然也可以二者一起使用,即ImportBeanDefinitionRegistrar註冊的bean是一個BeanDefinitionRegistryPostProcessor的實現,這樣就形成了@Import+ImportBeanDefinitionRegistrar+BeanDefinitionRegistryPostProcessor
比如mybaits就是使用這種方式,整合了二者的優勢(既可以使用@MapperScan+@Import+ImportBeanDefinitionRegistrar來定製掃描包,又可以通過BeanDefinitionRegistryPostProcessor在註冊bean前通過ApplicationContextAware獲得applicationContext對象)