ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區(qū)別

概述

如果想實現(xiàn)自定義注冊bean到spring容器中,常見的做法有兩種

  • @Import+ImportBeanDefinitionRegistrar
  • BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor與ImportBeanDefinitionRegistrar都是接口,通過實現(xiàn)任意一個就可以獲取到bean定義注冊器:BeanDefinitionRegistry,通過調用其方法

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

就可以給spring容器中新增自定義的bean

那么二者到底有啥區(qū)別,spring為啥會提供兩種方式,我們如何根據(jù)需求進行選擇吶?

使用

首先使用上,二者的使用方式區(qū)別很大

ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar的用法是@Import+ImportBeanDefinitionRegistrar,比如現(xiàn)在要把一個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實現(xiàn),代碼如下

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

原理

二者的執(zhí)行邏輯和時機可以參照一文通透spring的初始化,這里就簡單總結一下,不做贅述

  • @Import是spring啟動執(zhí)行BeanFactory后置處理器時被spring內置后置處理器ConfigurationClassPostProcessor解析,如果發(fā)現(xiàn)@Import引入的是一個ImportBeanDefinitionRegistrar的實現(xiàn),則會立即調用其registerBeanDefinitions方法
  • spring啟動執(zhí)行BeanFactory后置處理器時,BeanDefinitionRegistryPostProcessor的實現(xiàn)作為bean首先被ConfigurationClassPostProcessor掃描并加入spring容器中,后續(xù)會再去spring容器中查找所有的后置處理器并執(zhí)行

其實二者的執(zhí)行時機都是:spring啟動執(zhí)行BeanFactory后置處理器,其中ConfigurationClassPostProcessor作為spring內置的后置處理器執(zhí)行優(yōu)先級較高,他的內部會調用ImportBeanDefinitionRegistrar的實現(xiàn),而BeanDefinitionRegistryPostProcessor與ConfigurationClassPostProcessor一樣都是后置處理器,屬于同級別的(其實是由ConfigurationClassPostProcessor衍生出來),會在ConfigurationClassPostProcessor執(zhí)行完畢后依次被執(zhí)行

從這里看,二者的定位不太一樣ImportBeanDefinitionRegistrar輸入后置處理器ConfigurationClassPostProcessor的一個自邏輯,BeanDefinitionRegistryPostProcessor本身就是一個后置處理器

當然這是本質上的區(qū)別,具體還要看使用區(qū)別

區(qū)別

ImportBeanDefinitionRegistrar的優(yōu)勢

從上面那個例子上其實可以看出,ImportBeanDefinitionRegistrar的registerBeanDefinitions方法相較于BeanDefinitionRegistryPostProcessor多了個AnnotationMetadata參數(shù),利用這個參數(shù)可以獲取到含有@Import注解的類的一些屬性,比如上面的defaultName,這樣用戶就可以通過注解的屬性定制化一些功能,例如我們常用mybaits,可以通過

@MapperScan("com.pq.xxx")

指定掃描的包名,方便實現(xiàn)用的自定義配置,這一點是BeanDefinitionRegistryPostProcessor做不到的

且@Import自帶的把第三方pojo引入spring的特性,加上注解編程的優(yōu)雅,讓@Import+ImportBeanDefinitionRegistrar組合在很多第三方工具框架很常見

BeanDefinitionRegistryPostProcessor的優(yōu)勢

BeanDefinitionRegistryPostProcessor的實現(xiàn)首先是一個bean,如上例使用@Component注解才會生效,作為bean本身,自定義后置處理器可以依賴注入其它bean,也可以實現(xiàn)各種Aware以得到上下文環(huán)境,所有常規(guī)bean的生命周期和功能它都有,這一點是作為POJO的ImportBeanDefinitionRegistrar實現(xiàn)不具備的

實際上ImportBeanDefinitionRegistrar也可以實現(xiàn)幾個固定的Aware,但ImportBeanDefinitionRegistrar的實例化代碼是ConfigurationClassParser單獨實現(xiàn)的,并不是createBean那一套,如下

ConfigurationClassParser

使用ParserStrategyUtils.instantiateClass方法來實例化ImportBeanDefinitionRegistrar
ParserStrategyUtils.instantiateClass

在創(chuàng)建實例后,使用ParserStrategyUtils.invokeAwareMethods執(zhí)行Aware,進去看一下
ParserStrategyUtils.invokeAwareMethods

就執(zhí)行這固定四個Aware:BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware

這相比于spring bean聲明周期中的Aware少太多

總結

今天大概梳理一下ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區(qū)別,二者各有自己的優(yōu)勢,了解了區(qū)別,至于使用哪個,就看使用場景就可以了

當然也可以二者一起使用,即ImportBeanDefinitionRegistrar注冊的bean是一個BeanDefinitionRegistryPostProcessor的實現(xiàn),這樣就形成了@Import+ImportBeanDefinitionRegistrar+BeanDefinitionRegistryPostProcessor

比如mybaits就是使用這種方式,整合了二者的優(yōu)勢(既可以使用@MapperScan+@Import+ImportBeanDefinitionRegistrar來定制掃描包,又可以通過BeanDefinitionRegistryPostProcessor在注冊bean前通過ApplicationContextAware獲得applicationContext對象)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容