Spring 自定义 @Enable 注释元注释与 @ComponentScan

kni*_*ttl 5 java spring spring-annotations component-scan spring-autoconfiguration

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

package com.example.package.app;

@SpringBootApplication
@com.example.annotations.EnableCustom("com.example.package.custom")
public class MyApplication {}
Run Code Online (Sandbox Code Playgroud)

使用自定义注释遵循Component scan,但这带来了几个限制:

  1. 我不能使基本包属性动态化,即我不能传递"com.example.package.base",但需要在配置中预定义包。

    我看了看@AliasFor,但无法让它工作。

  2. 当我省略基础包时,扫描从注释的定义包开始,而不是从被注释的类的包开始。在上面的例子中,它只会为 中的类扫描和创建 bean com.example.annotations,而不是为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 {};
}
Run Code Online (Sandbox Code Playgroud)

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


AM(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");
    }
}
Run Code Online (Sandbox Code Playgroud)

kni*_*ttl 5

在Fabio Formosa 的答案、该答案中填补的缺失部分以及注释中的一些灵感的帮助下@EntityScan,我终于成功地使其发挥作用。可以在https://github.com/knittl/stackoverflow/tree/spring-enable-annotation-working找到可编译的工作示例。

\n\n

简而言之:在 Fabio 的答案的基础上,ClassPathScanningCandidateComponentProvider使用包含过滤器正确配置实例,然后针对所有提供的基类运行它非常重要。@AliasFor(annotation = Import.class, \xe2\x80\xa6)似乎不是必需的,并且可以别名为另一个属性,例如basePackages同一注释的属性。

\n\n

最低实现如下:

\n\n
@Configuration\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Import(EnableCustom.EnableCustomConfiguration.class)\npublic @interface EnableCustom {\n    @AliasFor(attribute = "basePackages")\n    String[] value() default {};\n\n    @AliasFor(attribute = "value")\n    String[] basePackages() default {};\n\n    class EnableCustomConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {\n        private static final BeanNameGenerator BEAN_NAME_GENERATOR = AnnotationBeanNameGenerator.INSTANCE;\n        private Environment environment;\n\n        @Override\n        public void setEnvironment(final Environment environment) {\n            this.environment = environment;\n        }\n\n        @Override\n        public void registerBeanDefinitions(\n                final AnnotationMetadata metadata,\n                final BeanDefinitionRegistry registry) {\n            final AnnotationAttributes annotationAttributes = new AnnotationAttributes(\n                    metadata.getAnnotationAttributes(EnableCustom.class.getCanonicalName()));\n\n            final ClassPathScanningCandidateComponentProvider provider\n                    = new ClassPathScanningCandidateComponentProvider(false, environment);\n            provider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotation.class, true));\n\n            final Set<String> basePackages\n                    = getBasePackages((StandardAnnotationMetadata) metadata, annotationAttributes);\n\n            for (final String basePackage : basePackages) {\n                for (final BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {\n                    final String beanClassName = BEAN_NAME_GENERATOR.generateBeanName(beanDefinition, registry);\n                    if (!registry.containsBeanDefinition(beanClassName)) {\n                        registry.registerBeanDefinition(beanClassName, beanDefinition);\n                    }\n                }\n            }\n        }\n\n        private static Set<String> getBasePackages(\n                final StandardAnnotationMetadata metadata,\n                final AnnotationAttributes attributes) {\n            final String[] basePackages = attributes.getStringArray("basePackages");\n            final Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));\n\n            if (packagesToScan.isEmpty()) {\n                // If value attribute is not set, fallback to the package of the annotated class\n                return Collections.singleton(metadata.getIntrospectedClass().getPackage().getName());\n            }\n\n            return packagesToScan;\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n