springboot自定义注解,项目启动时扫描注解类并注入容器

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot自定义注解,项目启动时扫描注解类并注入容器相关的知识,希望对你有一定的参考价值。

以下是核心流程的实现示例,如果需要更完整的实现,可参考:
注意:需要切换到simple-rpc-like-feign分支

一、需求说明

  • 有两个自定义注解@EnableSimpleRpcClients@SimpleRpcClient

    • @EnableSimpleRpcClients注解标注在启动类上,并且可以指定要扫描的包(basePackages)

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      @Documented
      public @interface EnableSimpleRpcClients 
      
          String[] value() default ;
      
          String[] basePackages() default ;
      
      
    • @SimpleRpcClient注解标注在需要注入到容器的类或接口上

      @Documented
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface SimpleRpcClient 
      
          @AliasFor("name")
          String value() default "";
      
          @AliasFor("value")
          String name() default "";
      
      
  • 要求项目启动时自动扫描@EnableSimpleRpcClients注解的basePackages属性指定的包路径下的所有标注了@SimpleRpcClient注解的类或接口,并将这些类或接口都注入到容器中

二、实现过程分析

主要是借助spring留给我们的扩展点ImportBeanDefinitionRegistrar以及一些工具类BeanDefinitionReaderUtils

  • 利用ImportBeanDefinitionRegistrar我们就可以在容器启动前期,扫描指定basePackages下所有标注了@SimpleRpcClient注解的类或接口信息
  • 然后借助spring提供的工具类BeanDefinitionReaderUtils,将扫描到的类或接口按自定义逻辑注入容器即可。(比如我这里是扫描到basePackages包下,所有标注了@SimpleRpcClient注解的接口,然后借助factoryBean为这些接口创建【动态代理类】,然后将动态代理类注入容器中)

三、代码实现

①、先定义两个自定义注解

EnableSimpleRpcClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface EnableSimpleRpcClients 

    String[] value() default ;

    String[] basePackages() default ;

SimpleRpcClient

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleRpcClient 

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

②、自定义ImportBeanDefinitionRegistrar

@Component
public class ExampleRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware 
  /**
     * 资源加载器
     */
    private ResourceLoader resourceLoader;
    /**
     * 环境
     */
    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) 
        // 创建scanner
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(resourceLoader);

        // 设置扫描器scanner扫描的过滤条件
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(SimpleRpcClient.class);
        scanner.addIncludeFilter(annotationTypeFilter);

        // 获取指定要扫描的basePackages
        Set<String> basePackages = getBasePackages(metadata);

        // 遍历每一个basePackages
        for (String basePackage : basePackages) 
            // 通过scanner获取basePackage下的候选类(有标@SimpleRpcClient注解的类)
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            // 遍历每一个候选类,如果符合条件就把他们注册到容器
            for (BeanDefinition candidateComponent : candidateComponents) 
                if (candidateComponent instanceof AnnotatedBeanDefinition) 
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(), "@SimpleRpcClient can only be specified on an interface");
                    // 获取@SimpleRpcClient注解的属性
                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(SimpleRpcClient.class.getCanonicalName());
                    // 注册到容器
                    registerSimpleRpcClient(registry, annotationMetadata, attributes);
                
            
        
    
  
  /**
     * 利用factoryBean创建代理对象,并注册到容器
     */
    private static void registerSimpleRpcClient(BeanDefinitionRegistry registry,
                                                AnnotationMetadata annotationMetadata,
                                                Map<String, Object> attributes) 
        // 类名(接口全限定名)
        String className = annotationMetadata.getClassName();
        // 创建SimpleRpcClientFactoryBean的BeanDefinition
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(SimpleRpcClientFactoryBean.class);
        // 解析出@SimpleRpcClient注解的name
        String name = getName(attributes);
        if (!StringUtils.hasText(name)) 
            throw new ProviderNameNullException(String.format("class [%s] , @SimpleRpcClient name or value can not be null, please check.", className));
        

        // 给factoryBean添加属性值
        definition.addPropertyValue("name", name);
        definition.addPropertyValue("type", className);
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        String alias = name + "SimpleRpcClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

        // 注册bean定义信息到容器
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]alias);
      	// 使用BeanDefinitionReaderUtils工具类将BeanDefinition注册到容器
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    

    /**
     * 创建扫描器
     */
    protected ClassPathScanningCandidateComponentProvider getScanner() 
        return new ClassPathScanningCandidateComponentProvider(false, environment) 
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) 
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) 
                    if (!beanDefinition.getMetadata().isAnnotation()) 
                        isCandidate = true;
                    
                
                return isCandidate;
            
        ;
    

    /**
     * 获取base packages
     */
    protected static Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) 
        // 获取到@EnableSimpleRpcClients注解所有属性
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableSimpleRpcClients.class.getCanonicalName());
        Set<String> basePackages = new HashSet<>();
        assert attributes != null;
        // value 属性是否有配置值,如果有则添加
        for (String pkg : (String[]) attributes.get("value")) 
            if (StringUtils.hasText(pkg)) 
                basePackages.add(pkg);
            
        

        // basePackages 属性是否有配置值,如果有则添加
        for (String pkg : (String[]) attributes.get("basePackages")) 
            if (StringUtils.hasText(pkg)) 
                basePackages.add(pkg);
            
        

        // 如果上面两步都没有获取到basePackages,那么这里就默认使用当前项目启动类所在的包为basePackages
        if (basePackages.isEmpty()) 
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        

        return basePackages;
    

    /**
     * 获取name
     */
    protected static String getName(Map<String, Object> attributes) 
        String name = (String) attributes.get("name");
        if (!StringUtils.hasText(name)) 
            name = (String) attributes.get("value");
        
        return name;
    
  
  @Override
    public void setEnvironment(Environment environment) 
        this.environment = environment;
    

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) 
        this.resourceLoader = resourceLoader;
    

③、具体使用

// 启动类上标注@EnableSimpleRpcClients注解,如果不指定value和basePackages那么默认扫描classpath下
@EnableSimpleRpcClients
@SpringBootApplication
public class SimpleRpcConsumerApplication 

    public static void main(String[] args) 
        SpringApplication.run(SimpleRpcConsumerApplication.class, args);
    


以上是关于springboot自定义注解,项目启动时扫描注解类并注入容器的主要内容,如果未能解决你的问题,请参考以下文章

ComponentScan注解的扫描范围及源码解析

JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库

JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库

JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库

SpringBoot注解介绍

java新加的借口扫描不到