概述

如果想實現自定義註冊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那一套,如下

ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區別_spring

ConfigurationClassParser


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

ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區別_自定義_02

ParserStrategyUtils.instantiateClass


在創建實例後,使用ParserStrategyUtils.invokeAwareMethods執行Aware,進去看一下

ImportBeanDefinitionRegistrar與BeanDefinitionRegistryPostProcessor的區別_自定義_03

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對象)