一、ImportBeanDefinitionRegistrar是什么
Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary. Along with @Configuration and ImportSelector, classes of this type may be provided to the @Import annotation (or may also be returned from an ImportSelector). An ImportBeanDefinitionRegistrar may implement any of the following Aware interfaces, and their respective methods will be called prior to registerBeanDefinitions: EnvironmentAware BeanFactoryAware BeanClassLoaderAware ResourceLoaderAware
ImportBeanDefinitionRegistrar是spring3.1开始引入的一个接口,从官方说明中我们大致知道它是一个用来动态注册bean定义的接口,通过@Import方式引入,和ImportSelector用法类似,通常和EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware,ResourceLoaderAware接口一起使用,并且其执行优先级比这几个接口要低。
说白了就是spring框架留给开发者的一个扩展接口,用于自定义注册bean。
二、可以做什么
实现ImportBeanDefinitionRegistrar接口的类可以拥有动态注册bean的能力,然而万事皆有因果,既然spring把这个扩展能力暴露给了开发者,我们拿来做什么呢?接下来我们就分析一下能做什么。
首先提到动态注册bean,那么前提是我们缺少这个bean,并且我们应用中要用到这个bean,然后或者说我们拿到bean的抽象定义,框架没有办法帮我们自动注册到容器中,那么我们就可以来自定义扩展实现,根据自己的需求把相关的bean定义好之后注册到容器中。
1.动态注册数据源
我们一般开发中会属性文件中添加数据库连接配置,然后应用启动的时候会把数据库连接属性和相关DataSource结合起来生成数据源,当然我们也可以通过ImportBeanDefinitionRegistrar来做这件事,拿到连接属性后,然后定义DataSource类型的BeanDefinition然后注册到容器中,这样在应用启动完成后我们同样可以使用生成的DataSource。
2.动态生成DAO实现
在编码中和数据库交互时,我们一般会通过框架或者自己实现的方式抽象出数据访问层(DAO,Data Access Object),并且很多持久层框架的DAO层定义和实现是分离的,比如JPA、ibatis和mybatis等,我们只需要定义接口,或者再通过xml定义具体sql语句,在程序中我们就可以直接调用接口来执行数据交互了,很明显在应用启动时我们也可以通过ImportBeanDefinitionRegistrar来做这些接口实例化和sql绑定操作,根据定义的DAO接口生成代理实现,并且把数据交互操作填充到代理类中,这样在应用启动后上下文容器中就包含了完整的接口实现和数据交互了。
3.远程接口调用
我们暂时理解成企业维度的二方服务调用,比如电商业务中订单需要调商品服务获取价格,那么商品会暴露出api出来,然后具体实现本领域保留,而订单依赖的商品二方包中只有接口定义,为什么就能直接调用呢?
还是要提到ImportBeanDefinitionRegistrar这位老伙计,中间件会定义好微服务之间的交互协议,通过ImportBeanDefinitionRegistrar把依赖的二方包中的接口进行实例化填充,然后本地服务就能直接调用了,比如dubbo和openfeign等等。
总的来说ImportBeanDefinitionRegistrar的作用就是把模糊的概念明确化,把抽象的东西实例化,为本地服务提供更方便的使用或者服务调用。
三、原理分析
看一下ImportBeanDefinitionRegistrar定义:
代码语言:javascript复制public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
只有一个方法,根据需要把自定义bean注册到容器中。我们写一个简单的例子来看一下。
编写自定义bean
代码语言:javascript复制@Slf4j
public class CustomBean {
public void hello() {
log.info("CustomBean print hello");
}
}
编写ImportBeanDefinitionRegistrar
代码语言:javascript复制@Slf4j
public class CustomBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
log.info("开始手动注册CustomBean");
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(CustomBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, CustomBean.class.getSimpleName());
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
编写启用注解
代码语言:javascript复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CustomBeanDefinitionRegistrar.class)
public @interface EnableCustomBean {
}
启动执行
代码语言:javascript复制@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableCustomBean
@Slf4j
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
context.getBean(CustomBean.class).hello();
}
}
从启动日志看到我们已经实现了手动自定义注册bean,并且和容器维护的其他bean一样能够正常使用。
四、执行时机
前边我们分析了ImportBeanDefinitionRegistrar的原理,其实作为spring框架的扩展能力,更重要的是在于其执行时机,对于执行时机我们分两步来分析,分别是实例化和执行。
1.ImportBeanDefinitionRegistrar实例化
@Import注解被ConfigurationClassParser#doProcessConfigurationClass处理,ConfigurationClassParser被ConfigurationClassPostProcessor创建并调用,而ConfigurationClassPostProcessor被调用AbstractApplicationContext#refresh,我们直接看实现:
代码语言:javascript复制public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略....
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
//省略...
}
while (!candidates.isEmpty());
//省略...
}
ConfigurationClassPostProcessor创建了ConfigurationClassParser并执行parse,会调用到doProcessConfigurationClass方法:
代码语言:javascript复制protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//省略...
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
//省略...
return null;
}
其他的板块我们暂时不用关心,中间有处理@Import的调用processImports:
代码语言:javascript复制private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class ["
configClass.getMetadata().getClassName() "]", ex);
}
finally {
this.importStack.pop();
}
}
}
中间有处理ImportSelector或ImportBeanDefinitionRegistrar类的逻辑,先实例化ImportBeanDefinitionRegistrar类,然后调用相关aware方法(也就是我们前边所说register接口比aware执行优先级低),然后添加到配置类中备用。
2.ImportBeanDefinitionRegistrar执行
ConfigurationClassPostProcessor#processConfigBeanDefinitions方法执行完ConfigurationClassParser#parse之后有一行代码:
代码语言:javascript复制this.reader.loadBeanDefinitions(configClasses);
loadBeanDefinitions会执行loadBeanDefinitionsForConfigurationClass方法:
代码语言:javascript复制private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
最后一行我们看到执行loadBeanDefinitionsFromRegistrars方法,从Registrar加载BeanDefinition:
代码语言:javascript复制private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
这里拿到前边注册的ImportBeanDefinitionRegistrar实例并执行registerBeanDefinitions方法,到这里就完成了ImportBeanDefinitionRegistrar的执行。
我们从应用启动开始分析,ImportBeanDefinitionRegistrar实例化和执行的时序图大致如下(基于Springboot分析):
五、常用姿势
ImportBeanDefinitionRegistrar的大多用于框架或者应用基础能力扩充场景,目前市面上常见的有很多,我们简单列举几个:
1.mybatis数据访问接口实例化
@MapperScan注解由MapperScannerRegistrar驱动,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口重写registerBeanDefinitions方法,手动注入了MapperScannerConfigurer,而MapperScannerConfigurer又实现了BeanDefinitionRegistryPostProcessor接口,重写postProcessBeanDefinitionRegistry方法扫描DAO接口并注册BeanDefinition到容器中,详细内容可以参考《Mybatis原理分析》
2.Openfeign接口实例化
@EnableFeignClients注解由FeignClientsRegistrar驱动,其实现了ImportBeanDefinitionRegistrar接口重写registerBeanDefinitions方法,使用ClassPathScanningCandidateComponentProvider扫描basePackages路径下被@FeignClient标记的接口,然后注册成FeignClientFactoryBean类型的BeanDefinition到容器中,在使用的时候生成具体的接口代理实现服务调用,具体参考《一文看懂Openfeign服务调用原理》
3.dubbo接口实例化
我们使用dubbo搭建微服务时,拿到的其他领域的服务依赖都是接口,接口实例化由DubboComponentScanRegistrar完成,其实现了ImportBeanDefinitionRegistrar接口重写registerBeanDefinitions方法,注册ReferenceAnnotationBeanPostProcessor,然后扫描@Reference和@DubboReference生成接口代理。
总结
本文介绍了ImportBeanDefinitionRegistrar接口的原理和使用场景,具体什么时候和什么场景使用大致可以归纳为以下两点:
- 外部依赖类无法自动扫描和初始化,可以使用ImportBeanDefinitionRegistrar手动注入和初始化
- 外部依赖服务只有接口没有实现,重写ImportBeanDefinitionRegistrar接口拿到后根据协议和接口生成代理实现并实例化,供本地化调用