【扩展点】 自定义 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
  • 借助BeanDefinitionRegistryPostProcessorClassPathBeanDefinitionScanner来实现扫描相关的类,并注册 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 专栏,咱们下文见

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


本篇文章来源于微信公众号: 一灰灰blog



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2023-05-28