概述
如果想实现自定义注册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那一套,如下
使用
ParserStrategyUtils.instantiateClass
方法来实例化ImportBeanDefinitionRegistrar在创建实例后,使用
ParserStrategyUtils.invokeAwareMethods
执行Aware,进去看一下就执行这固定四个Aware:
BeanClassLoaderAware
,BeanFactoryAware
,EnvironmentAware
,ResourceLoaderAware
这相比于spring bean声明周期中的Aware少太多
总结
今天大概梳理一下ImportBeanDefinitionRegistrar与BeanDefinitionRegistryPostProcessor的区别,二者各有自己的优势,了解了区别,至于使用哪个,就看使用场景就可以了
当然也可以二者一起使用,即ImportBeanDefinitionRegistrar注册的bean是一个BeanDefinitionRegistryPostProcessor的实现,这样就形成了@Import+ImportBeanDefinitionRegistrar+BeanDefinitionRegistryPostProcessor
比如mybaits就是使用这种方式,整合了二者的优势(既可以使用@MapperScan+@Import+ImportBeanDefinitionRegistrar来定制扫描包,又可以通过BeanDefinitionRegistryPostProcessor在注册bean前通过ApplicationContextAware获得applicationContext对象)