
【扩展点】 自定义 bean 注册扩展机制 BeanDefinitionRegistryPostProcessor
接着上一篇容器刷新前的扩展点,我们继续往下走;接下来来到的就是 bean 的定义扩展处,它是在 Spring 容器刷新之后,应用的 bean 定义加载完成、实例化之前提供的切入点,主要是通过实现BeanDefinitionRegistryPostProcessor接口的两个方法,来实现自定义的 bean 定义,或者对已注册的 bean 进行修改 or 代理替换
本文将带来的知识点如下:
-
BeanDefinitionRegistryPostProcessor:基本使用姿势 -
postProcessBeanDefinitionRegistry方法 优先于postProcessBeanFactory方法执行 -
实现自定义的 bean 注册,实现对容器的 bean 定义进行修改
I. 项目准备
本文创建的实例工程采用SpringBoot 2.2.1.RELEASE + maven 3.5.3 + idea进行开发
具体的 SpringBoot 项目工程创建就不赘述了,核心的 pom 文件,无需额外的依赖;配置文件 application.yml, 也没有什么特殊的配置
说明
-
源码工程参考文末的源码 -
虽然本文是基于 2.2.1.RELEASE版本进行实测;实际上这些基础的扩展点,在更高的版本中表现也不会有太大的变动,基本上可以无修改复现
II. 自定义 bean 注册
有关注过博主一灰灰的朋友,应该在我之前的文章中可以翻到 bean 的动态注册的内容,其中其实也介绍到通过BeanDefinitionRegistryPostProcessor来实现 bean 的动态注册,有兴趣的小伙伴可以翻一下,链接如下
【基础系列】Bean 之动态注册 | 一灰灰 Blog
接下来我们开始进入正题
1. 自定义 bean 注册
现在我们定义一个普通的 bean 对象,也定义了几个常见的 bean 初始化之后的回调方法,顺带验证两个知识点
-
自定义注册的 bean 是否表现和普通的 bean 一致 -
初始化后的方法执行的顺序
public class DemoBean implements InitializingBean {
private int initCode;
public DemoBean() {
initCode = new Random().nextInt(100);
System.out.println("demo bean create! -> " + initCode);
}
@PostConstruct
public void init() {
System.out.println("PostConstruct" + initCode);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet" + initCode);
}
public int getInitCode() {
return initCode;
}
}
再定义一个 bean,构造方法依赖其他的 bean
public class DemoBeanWrapper extends DemoBean {
private DemoBean demoBean;
public DemoBeanWrapper(DemoBean demoBean) {
super();
this.demoBean = demoBean;
}
}
接下来我们再看一下这两个 bean 如何进行注册
@Configuration
public class AutoBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
System.out.println("========> postProcessBeanDefinitionRegistry --->");
// 这个接口主要是在读取项目中的 beanDefinition 之后执行,简单来说就是项目本身的bean定义读取完毕之后,如果我们还想补充一些自定义的bean注册信息,则可以用它
// 注意两个核心点:Spring上下文的注册Bean定义的逻辑都跑完后,但是所有的Bean都还没真正实例化之前
// 构建bean的定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DemoBean.class, () -> {
// 这个方法可以定义这个bean的实例创建方式,如构造函数之后还想调用其他的方法,也可以在这里做
DemoBean demoBean = new DemoBean();
return demoBean;
});
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//注册bean定义
beanDefinitionRegistry.registerBeanDefinition("demoBean", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("========> postProcessBeanFactory --->");
// 这个方法调用时再上面的方法执行之后,如加载自定义的bean注册依赖有其他的bean对象时,可以执行这个方法
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DemoBeanWrapper.class, () -> {
DemoBeanWrapper autoFacDIBean = new DemoBeanWrapper(configurableListableBeanFactory.getBean("demoBean", DemoBean.class));
return autoFacDIBean;
});
BeanDefinition beanDefinition = builder.getRawBeanDefinition();
((DefaultListableBeanFactory) configurableListableBeanFactory).registerBeanDefinition("demoBeanWrapper", beanDefinition);
}
}
bean 的注册从上面的代码来看比较简单,先看 DemoBean 的注册
方法:postProcessBeanDefinitionRegistry
在这个方法中进行简单的 bean 注册,除了上面这个稍显复杂的注册方式之外,也可以使用更简单的策略,如下,省略掉BeanDefinitionBuilder.genericBeanDefinition第二个参数
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DemoBean.class);
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//注册bean定义
beanDefinitionRegistry.registerBeanDefinition("demoBean", beanDefinition);
这个方法内的 bean 注册,更适用于简单的 bean 对象注册,如当其构造方法依赖其他的 bean 时,放在这个方法中好像没辙,此时则放在第二个方法中就更合适了
方法:postProcessBeanFactory
这个方法的参数是 BeanFactory,可以通过它获取其他的 bean 对象,因此适用于 DemoBeanWrapper 的注册了,当然除了上面的使用姿势之外,也可以如下
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DemoBeanWrapper.class);
// 用下面这种方式指定构造方法的传参也可以
builder.addConstructorArgValue(configurableListableBeanFactory.getBean("demoBean", DemoBean.class));
BeanDefinition beanDefinition = builder.getRawBeanDefinition();
((DefaultListableBeanFactory) configurableListableBeanFactory).registerBeanDefinition("demoBeanWrapper", beanDefinition);
}
2.bean 注册知识点
单独看上面的代码可能对知识点理解不够直观清晰,那么我们就进行知识点归纳一下
bean 注册方式
如何生成 Bean 的定义 BeanDefinition ?
// 1. bean定义构造器
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(bean.class, () -> {
// bean实例化 实现方式, 若bean存在无参构造方法,则可以省略这个参数
});
// 2. bean定义获取
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
// 拿到上面的bean定义之后,可以设置构造方法参数,作用域等
// 3. 注册
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
两个方法的选择
-
postProcessBeanDefinitionRegistry方法执行先于postProcessBeanFactory -
postProcessBeanDefinitionRegistry在 bean 实例化之前触发,可用于注册简单的自定义 bean 对象 -
postProcessBeanFactory: 若 bean 的定义中需要依赖其他的 bean 对象,则放在这个方法内实现,通过 BeanFactory 参数获取其他 bean
3. bean 定义扩展
文章开头介绍了除了自定义 bean 之外,还可以做一些其他的操作,如针对现有的 bean 定义进行修改,下面给一个基础的 demo,针对一个已有的 bean,设置它的 init 方法
新增一个普通的 bean 对象
@Component
public class NormalBean implements InitializingBean {
@Autowired
private DemoBean demoBean;
@Autowired
private DemoBeanWrapper demoBeanWrapper;
@PostConstruct
public void show() {
System.out.println("NormalBean: postConstruct");
System.out.println(demoBean.getInitCode());
System.out.println(demoBeanWrapper.getInitCode());
}
public void init() {
System.out.println("NormalBean: method init");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("NormalBean: afterPropertiesSet");
}
}
然后我们通过修改 bean 注册,来指定 bean 加载完之后,执行 init 方法,在前面的AutoBeanDefinitionRegistryPostProcessor中进行扩展
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
// ....
// 针对已有的bean定义进行调整
for (String beanName : configurableListableBeanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = configurableListableBeanFactory.getBeanDefinition(beanName);
if (definition.getBeanClassName() == null) {
continue;
}
if (definition.getBeanClassName().equalsIgnoreCase(NormalBean.class.getName())) {
// 手动指定一下初始化方法
definition.setInitMethodName("init");
}
}
}
然后我们将整个项目执行以下,看下会输出些啥
========> postProcessBeanDefinitionRegistry --->
========> postProcessBeanFactory --->
# 下面是DemoBean的相关输出
demo bean create! -> 58
afterPropertiesSet58
# 下面是DemoBeanWrapper的相关输出
demo bean create! -> 46
PostConstruct46
afterPropertiesSet46
# 下面是NormalBean的相关输出
NormalBean: postConstruct
58
46
NormalBean: afterPropertiesSet
NormalBean: method init
从上面的输出也可以看出,我们的几个自定义 bean 都被正常的加载、注入,依赖使用也没有什么问题;而且从日志输出还可以看出 bean 初始化后的触发方法,也有先后顺序
-
@PostConstruct>InitializingBean#afterPropertiesSet>init-method(这个可以理解为 xml 定义 bean 中的初始化方法, @Bean 注解中的 initMethod)
4. 小结
最后进入大家喜闻乐见的知识点汇总环节,本文中主要介绍的是 bean 定义加载之后、实例化之前的扩展点BeanDefinitionRegistryPostProcessor
4.1 知识点一:核心方法说明
通过它,我们可以实现自定义的 bean 注册,也可以实现对现有的 bean 定义进行扩展修改;有两个方法
postProcessBeanDefinitionRegistry
-
执行顺序在下面的方法之前,通常是在 bean 实例化之前被触发 -
适用于通用的 bean 注册定义
postProcessBeanFactory
-
其参数为 BeanFactory,因此可以通过它获取 Spring 容器中的其他 bean 对象
4.2 知识点二:bean 注册
bean 注册方式
如何生成 Bean 的定义 BeanDefinition ?
// 1. bean定义构造器
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(bean.class, () -> {
// bean实例化 实现方式, 若bean存在无参构造方法,则可以省略这个参数
});
// 2. bean定义获取
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
// 拿到上面的bean定义之后,可以设置构造方法参数,作用域等
// 3. 注册
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
4.3 知识点三:使用场景
看完本文之后,勤于思考的小伙伴可能就会想,这个东西到底有啥用,有真实的应用场景么?
自定义 bean 注册实例场景
这个应用场景就非常的典型了,用过 mybatis 的小伙伴都知道,我们会定义一个 Mapper 接口,用于与对应的 xml 文件进行映射,那么这些 mapper 接口是怎么注册到 Spring 容器的呢?
-
核心实现 org.mybatis.spring.mapper.MapperScannerConfigurer -
借助 BeanDefinitionRegistryPostProcessor与ClassPathBeanDefinitionScanner来实现扫描相关的类,并注册 bean
bean 定义修改实例场景
对于已有的 bean 定义进行修改,同样也有一个应用场景,在 SpringCloud 中,有个RefreshAutoConfiguration#RefreshScopeBeanDefinitionEnhancer
它会捞出HikariDataSource数据源 bean 对象,添加RefreshScope的能力增强,支持配置文件的动态加载
从而实现数据源配置的热加载更新(不发版,直接改数据库连接池,是不是很方便?)
4.4 知识点四:bean 初始化后执行方法先后顺序
我们知道在 bean 创建之后执行某些方法有多种策略,那么不同的方式先后顺序是怎样的呢?
bean 创建到销毁的先后执行顺序如下
-
构造方法 -
@PostConstruct 修饰的方法 -
InitializingBean 接口的实现方法 -
xml/@Bean 中定义的 initMethod -
@PreDestroy bean 销毁前的执行方法
其他
本文为 Spring 扩展点系列中的第二篇,接下来的扩展知识点同样是 bean 定义之后,实例化之前的BeanFactoryPostProcessor,那么这两个究竟又有什么区别呢?应用场景又有什么区别呢?我是一灰灰,欢迎关注我的 Spring 专栏,咱们下文见
-
一灰灰的 Spring 专栏: https://hhui.top/spring-extention/ -
【Spring 扩展点】 容器刷新前回调 ApplicationContextInitializer
III. 不能错过的源码和相关知识点
0. 项目
-
工程:https://github.com/liuyueyi/spring-boot-demo -
源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-extention/101-bean-definition
1. 微信公众号: 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
-
一灰灰 Blog 个人博客 https://blog.hhui.top -
一灰灰 Blog-Spring 专题博客 http://spring.hhui.top

本篇文章来源于微信公众号: 一灰灰blog
微信扫描下方的二维码阅读本文

Comments NOTHING