Spring学习 - 自定义Spring 的 bean扫描器

作者: 疯狂小兵 | 2019-10-30 | 阅读
「编辑」 「本文源码」

自定义扫描器

创建自定义的注解类型

如创建注解MyAnno

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyAnno {

    @AliasFor(annotation = Component.class)
    String value() default "empty name";
}

此处注意@MyAnno必须被@Component标记,因为自定义的 scanner 是继承自ClassPathBeanDefinitionScanner,而其检测的目标注解就是@Component

如果想使用自定义的 value,必须使用@AliasFor指定注解为@Component,原因还是因为ClassPathBeanDefinitionScanner只会取@Component的 value 值。

创建自定义的扫描器

public class CustomScanner extends ClassPathBeanDefinitionScanner {

    private Class<? extends Annotation> annoType;

    public CustomScanner(BeanDefinitionRegistry registry, Class<? extends Annotation> annoType) {
        super(registry);
        this.annoType = annoType;

        super.addIncludeFilter(new AnnotationTypeFilter(annoType));
    }

    @Override
    public int scan(String... basePackages) {
        return super.scan(basePackages);
    }
}

通过在构造方法中指定解析的注解来使得自定义的注解生效。通过super.addIncludeFilter方法将自定义注解添加进过滤器中。

引用自定义的注解

package cn.followtry.boot.java.service;

@MyAnno(value = "my_first_test_scanner_service")
public class MyService {
}

创建一个测试类,并在该类上打上注解@MyAnno并为其 value 赋值。

创建测试类

package cn.followtry.boot.java.test;

public class ScannerTest {

    /**  */
    private static final String BASE_PACKAGE = "cn.followtry.boot.java.service";

    /**
     * main.
     */
    public static void main(String[] args) {

        GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();

        CustomScanner scanner = new CustomScanner(applicationContext, MyAnno.class);
        int scanCount = scanner.scan(BASE_PACKAGE);
        System.out.println("1扫描的数量"+scanCount);
        applicationContext.refresh();
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("bean name : " + beanDefinitionName);
        }

    }
}
  1. 先创建Spring 的注解方式的上下文 AnnotationConfigApplicationContext
  2. 然后创建自定义扫描器实例 CustomScanner,将注解@MyAnno作为构造方法的参数传入,使其在该扫描器中生效。
  3. 调用scan方法扫描 base 包,并生成注册 Bean。
  4. 调用applicationContext.refresh();完成 Spring 的初始化。
  5. 通过打印applicationContext.getBeanDefinitionNames();,判断当前自定义注解@MyAnno标记的 bean 是否成功注册进 Spring。

打印的日志

bean name : org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean name : org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean name : org.springframework.context.annotation.internalRequiredAnnotationProcessor
bean name : org.springframework.context.annotation.internalCommonAnnotationProcessor
bean name : org.springframework.context.event.internalEventListenerProcessor
bean name : org.springframework.context.event.internalEventListenerFactory
bean name : my_first_test_scanner_service

可以看到,有 bean name 为my_first_test_scanner_service被打印出,说明自定义的注解已经生效了。

创建自定义的 starter

依赖自定义的扫描器 CustomScanner

创建扫描器配置类

配置类ScannerConfiguration实现接口 BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

代码如下:

public class ScannerConfiguration implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

    private static final Logger log = LoggerFactory.getLogger(ScannerConfiguration.class);

    private String basePackage;

    /**  */
    private String beanName;

    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("======初始化自定义的配置:{},basePackage : {}", this.getClass().getCanonicalName(), this.basePackage);
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("=====开始启动自定义的扫描器扫描自定义的注解====");
        CustomScanner customScanner = new CustomScanner(registry, MyAnno.class);
        customScanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
        log.info("=====完成启动自定义的扫描器扫描自定义的注解====");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public String getBeanName() {
        return beanName;
    }
}

创建扫描器的注册处理器

处理器ScannerPostProcessor,实现接口 BeanDefinitionRegistryPostProcessor, EnvironmentAware, BeanFactoryAware

代码如下:

public class ScannerPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, BeanFactoryAware {

    private static final Logger log = LoggerFactory.getLogger(ScannerPostProcessor.class);
    private ConfigurableEnvironment environment;
    private ConfigurableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableBeanFactory)beanFactory;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //将配置的 kv 绑定到指定的对象上
        MyBootScanProperties scanProperties = Binder.get(this.environment).bind("my-scanner", Bindable.of(MyBootScanProperties.class)).orElse(new MyBootScanProperties());
        BeanDefinition beanDefinition = this.buildScannerConfigurerBeanDefinition(scanProperties);
        //注册对象
        registry.registerBeanDefinition("scannerConfiguration",beanDefinition);

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment)environment;
    }

    private BeanDefinition buildScannerConfigurerBeanDefinition(MyBootScanProperties myBootScanProperties) {
        BeanDefinitionBuilder scannerConfigurerBuilder = BeanDefinitionBuilder.genericBeanDefinition(ScannerConfiguration.class);
        scannerConfigurerBuilder.addPropertyValue("basePackage", myBootScanProperties.getBasePackage());
        return scannerConfigurerBuilder.getBeanDefinition();
    }
}

其中的BeanDefinitionBuilder scannerConfigurerBuilder = BeanDefinitionBuilder.genericBeanDefinition(ScannerConfiguration.class);就是在将ScannerConfiguration转为 BeanDefinition,并将MyBootScanProperties的值赋值给ScannerConfiguration相应的属性。

MyBootScanProperties为配置属性类,在下面会介绍。

starter 的启动类

启动类为ScanAutoConfiguration,主要为了设置启动开关和实例化扫描器的注册处理器ScannerPostProcessor,一遍让ScannerPostProcessor在实例化的时候顺便把`ScannerConfiguration也实例化。

@Configuration
@ConditionalOnProperty(
        name = {"mdp.zebra.enabled"},
        matchIfMissing = true
)
public class ScanAutoConfiguration {

    @Bean
    public ScannerPostProcessor myScannerPostProcessor(){
        return new ScannerPostProcessor();
    }
}

配置属性类

MyBootScanProperties配置属性类设置前缀为my-scanner,仅设置了basePackage一个属性用于配置自定义扫描器的 base 包路径。

@ConfigurationProperties(
        prefix = "my-scanner"
)
public class MyBootScanProperties {
    public static final String MY_BOOT_PREFIX = "my-scanner";

    /**  */
    private String basePackage = ".";

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }
}

配置启动类

src/main/resources下建立文件META-INF/spring.factories,该文件出配置启动类。SpringBoot在启动时会扫描所有包下的该文件,收集启动类。

# 配置可以被自动化配置开启的类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.followtry.ScanAutoConfiguration

此处将ScanAutoConfiguration设置为自动启动类。

建立文件META-INF/spring-autoconfigure-metadata.properties,用来配置条件类

#Mon Jul 15 16:03:44 JST 2019
# 指定需要可以出发自动化配置条件的类
cn.followtry.UserAutoConfiguration.ConditionalOnClass=cn.followtry.MyBootScanProperties

建立文件META-INF/spring-configuration-metadata.json,用来设置属性配置项

json 格式,内容如下

{
  /** 指定属性标签的名称,类型和属性值及类型 **/
  "groups": [
    {
      "name": "my-scanner",
      "type": "cn.followtry.MyBootScanProperties",
      "sourceType": "cn.followtry.MyBootScanProperties"
    }
  ],
  "properties": [
    {
      "name": "my-scanner.base-package",
      "type": "java.lang.String",
      "description": "base package path",
      "sourceType": "cn.followtry.MyBootScanProperties",
      "defaultValue": ""
    }
  ],
  "hints": []
}

完成以上步骤后,在 Springboot 项目中,配置扫描 basePackage,并使用@MyAnno注解标记类,则项目启动后,该类就被注册进 Spring 的注册器了。

总结

总体流程如下:

  1. SpringBoot项目配置 basePackage 属性,启动项目
  2. springboot 会自动获取所有 jar 包下的spring.factories文件,该文件中定义了每个 starter 的启动器
  3. 触发启动器ScanAutoConfiguration,该启动器会实例化扫描器的注册处理器ScannerPostProcessor
  4. ScannerPostProcessor会在实例化时通过postProcessBeanDefinitionRegistry方法将ScannerConfiguration给实例化,并会将配置的属性,设置给改该类。
  5. ScannerConfiguration中在postProcessBeanDefinitionRegistry中配置了自定义的扫描器。会 new 一个新的扫描器,然后通过给定的basePackage,扫描并注册所有的 bean 的信息,包括@MyAnno标记的 Bean 的信息。
  6. 最后完成 spring 的整个初始化,项目完成启动。

版权声明:本文由 在 2019年10月30日发表。本文采用CC BY-NC-SA 4.0许可协议,非商业转载请注明出处,不得用于商业目的。
文章题目及链接:《Spring学习 - 自定义Spring 的 bean扫描器》




  相关文章:

「游客及非Github用户留言」:

「Github登录用户留言」:

TOP