ImportBeanDefinitionRegistrar与BeanDefinitionRegistryPostProcessor的区别

概述

如果想实现自定义注册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对象)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容