【问题标题】:Spring custom @Enable annotation meta-annotated with @ComponentScan使用 @ComponentScan 进行元注释的 Spring 自定义 @Enable 注释
【发布时间】:2020-09-10 06:24:03
【问题描述】:

我正在尝试为Spring框架编写自己的@Enable注解,应该使用如下:

package com.example.package.app;

@SpringBootApplication
@com.example.annotations.EnableCustom("com.example.package.custom")
public class MyApplication {}

我关注了Component scan using custom annotation,但这带来了一些限制:

  1. 我无法使基础包属性动态化,即无法传递"com.example.package.base",但需要在配置时预先定义包。

    我查看了@AliasFor,但无法正常工作。

  2. 当我省略基本包时,扫描从注释的定义包开始,而不是从注释类的包开始。在上面的示例中,它只会扫描和创建 com.example.annotations 中的类的 bean,而不是 com.example.package.*

    我查看了EntityScanPackages.Registrar.class,它是在@EntityScan 注释中导入的,但它是一个内部类,我的注释无法导入。

如果我将@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class)) 放在MyApplication 类上,一切正常,但当它移到@EnableCustom 的元注释时停止工作。如何告诉 Spring Framework 将 @EnableCustom 视为使用一些默认值指定 @ComponentScan 的不同方式。我尝试使用 @Configuration@Component 和其他人对我的注释进行元注释,但无济于事:

@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ComponentScan(
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                value = ApplicationService.class))
public @interface EnableApplicationServices {
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] value() default {};
}

我在哪里可以找到这方面的文档,或者您会推荐什么起点?我的长期目标是拥有一个可供众多项目使用的 Spring Boot 启动器。


可以在以下存储库中找到一个 M(N)WE:https://github.com/knittl/stackoverflow/tree/spring-enable-annotation

以下是包结构的概要:

// com.example.annotations.EnableCustom.java
@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// this annotation is never honored:
@ComponentScan(
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                value = MyAnnotation.class))
//@Import(EnableCustom.EnableCustomConfiguration.class)
public @interface EnableCustom {
    // this annotation works in combination with @Import, but scans the wrong packages.
    @ComponentScan(
            includeFilters = @ComponentScan.Filter(
                    type = FilterType.ANNOTATION,
                    value = MyAnnotation.class))
    class EnableCustomConfiguration {}
}

// file:com.example.app.Application.java
@SpringBootApplication
@EnableCustom("com.example.app.custom.services")
// @ComponentScan(
//         includeFilters = @ComponentScan.Filter(
//                 type = FilterType.ANNOTATION,
//                 value = MyAnnotation.class)) // <- this would work, but I want to move it to a custom annotation
public class Application {
}

// file:com.example.app.custom.services.MyService
@MyAnnotation
public class MyService {
    public MyService() {
        System.out.println("Look, I'm a bean now!");
    }
}

// file:com.example.annotations.services.WrongService.java
@MyAnnotation
public class WrongService {
    public WrongService() {
        System.out.println("I'm in the wrong package, I must not be instantiated");
    }
}

【问题讨论】:

  • 如果我是对的,你想制作自己的@ComponentScan 注释吗?
  • @AnishB。是的,这基本上就是我想要做的。我想创建一个自定义注释,它可以使用预定义的设置进行组件扫描(即为具有另一个自定义注释的类创建 bean),但足够灵活,以便注释的用户仍然可以配置基本包。如果自定义注释不是解决此问题的正确方法,但存在另一种解决方案,那也没关系。但看起来大多数其他库或框架都依赖于某种 ​​@Enable… 注释。
  • 嗨,我已经在 myannotation 上注册了组件,它可以与普通组件一起使用。在 EnableCustom 上工作是否需要过滤器。

标签: java spring spring-annotations component-scan spring-autoconfiguration


【解决方案1】:

Fabio Formosa's answer的帮助下,this answer中的缺失位,以及@EntityScan注释的一些启发,我终于设法让它工作了。在https://github.com/knittl/stackoverflow/tree/spring-enable-annotation-working 可以找到一个可编译的工作示例。

简而言之:基于 Fabio 的回答,重要的是正确配置带有包含过滤器的 ClassPathScanningCandidateComponentProvider 实例,然后针对所有提供的基类运行它。 @AliasFor(annotation = Import.class, …) 似乎不是必需的,可以别名为另一个属性,例如basePackages 的同一个注解。

最低实现如下:

@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableCustom.EnableCustomConfiguration.class)
public @interface EnableCustom {
    @AliasFor(attribute = "basePackages")
    String[] value() default {};

    @AliasFor(attribute = "value")
    String[] basePackages() default {};

    class EnableCustomConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {
        private static final BeanNameGenerator BEAN_NAME_GENERATOR = AnnotationBeanNameGenerator.INSTANCE;
        private Environment environment;

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

        @Override
        public void registerBeanDefinitions(
                final AnnotationMetadata metadata,
                final BeanDefinitionRegistry registry) {
            final AnnotationAttributes annotationAttributes = new AnnotationAttributes(
                    metadata.getAnnotationAttributes(EnableCustom.class.getCanonicalName()));

            final ClassPathScanningCandidateComponentProvider provider
                    = new ClassPathScanningCandidateComponentProvider(false, environment);
            provider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotation.class, true));

            final Set<String> basePackages
                    = getBasePackages((StandardAnnotationMetadata) metadata, annotationAttributes);

            for (final String basePackage : basePackages) {
                for (final BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
                    final String beanClassName = BEAN_NAME_GENERATOR.generateBeanName(beanDefinition, registry);
                    if (!registry.containsBeanDefinition(beanClassName)) {
                        registry.registerBeanDefinition(beanClassName, beanDefinition);
                    }
                }
            }
        }

        private static Set<String> getBasePackages(
                final StandardAnnotationMetadata metadata,
                final AnnotationAttributes attributes) {
            final String[] basePackages = attributes.getStringArray("basePackages");
            final Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));

            if (packagesToScan.isEmpty()) {
                // If value attribute is not set, fallback to the package of the annotated class
                return Collections.singleton(metadata.getIntrospectedClass().getPackage().getName());
            }

            return packagesToScan;
        }
    }
}

【讨论】:

    【解决方案2】:

    使用带有 basePackages 属性的自定义注解 @EnableAnnotation

    @EnableAnnotation(basePackages =  "write-here-a-base-package")
    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class SampleSimpleApplication implements CommandLineRunner {
    
      public static void main(String[] args) throws Exception {
       SpringApplication.run(SampleSimpleApplication.class, args);
      }
    }
    

    @EnableAnnotation 是这样定义的:

    @Retention(RUNTIME)
    @Target(TYPE)
    @Import(EnableAnnotationConfigRegistrar.class)
    public @interface EnableAnnotation {
    
      String[] basePackages() default "*";
    
      @AliasFor(annotation = Import.class, attribute = "value")
      Class<?>[] value() default { EnableAnnotationConfigRegistrar.class };
    
    }
    

    最后,EnableAnnotationConfigRegistrar.class 以编程方式扫描:

    public class EnableAnnotationConfigRegistrar implements ImportBeanDefinitionRegistrar {
    
     @Override
     public void registerBeanDefinitions(AnnotationMetadata enableAnnotationMetadata,
      BeanDefinitionRegistry registry) {
       AnnotationAttributes enableAnnotationAttributes = new AnnotationAttributes(
       enableAnnotationMetadata.getAnnotationAttributes(EnableAnnotation.class.getName()));
    
       String[] basePackages = enableAnnotationAttributes.getStringArray("basePackages");
       AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(basePackages);
       }
    
    }
    

    【讨论】:

    • 感谢您的回答!我应该将组件扫描的过滤器放在哪里,只创建带有@MyAnnotation 注释的bean? registerBeanDefinitions 方法中究竟必须实现什么?换句话说:如何使用正确的过滤器进行扫描(类似于@ComponentScan)以及如何将bean 添加到应用程序上下文中?我注意到的另一个问题:扫描时,注册器被递归调用,一遍又一遍地扫描同一个基础包(注册器在基础包之外,但@Enable注释类包含在基础包中)。
    • 我想通了。明天我将提供详细信息,以便未来的开发人员可以在此基础上进行开发。
    猜你喜欢
    • 1970-01-01
    • 2016-06-02
    • 2011-10-08
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    相关资源
    最近更新 更多