Spring 使用@ComponentScan 元注释的自定义@Enable 注释
Spring custom @Enable annotation meta-annotated with @ComponentScan
我正在尝试为 Spring 框架编写自己的 @Enable
注释,应该按如下方式使用:
package com.example.package.app;
@SpringBootApplication
@com.example.annotations.EnableCustom("com.example.package.custom")
public class MyApplication {}
我关注了 Component scan using custom annotation,但这有几个限制:
我不能使基础包属性动态,即我不能通过"com.example.package.base"
,但需要在配置中预先定义包。
我查看了 @AliasFor
,但无法正常工作。
当我省略基本包时,扫描从注释的定义包开始,而不是从被注释的包class开始。在上面的示例中,它只会为 com.example.annotations
中的 classes 扫描并创建 bean,但不会为 com.example.package.*
.
扫描和创建 bean
我查看了 EntityScanPackages.Registrar.class
是在 @EntityScan
注释中导入的,但它是一个内部 class 并且我的注释无法导入。
如果我将 @ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class))
放在 MyApplication
class 上,一切正常,但当它移至 @EnableCustom
的元注释时停止工作。如何告诉 Spring 框架将 @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 可用于多个项目的启动器。
M(N)WE 可以在以下存储库中找到:https://github.com/knittl/Whosebug/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");
}
}
使用带有 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);
}
}
在, the missing bits filled in from , and some inspiration from @EntityScan
annotation, I finally managed to get this to work. A compilable, working example can be found at https://github.com/knittl/Whosebug/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;
}
}
}
我正在尝试为 Spring 框架编写自己的 @Enable
注释,应该按如下方式使用:
package com.example.package.app;
@SpringBootApplication
@com.example.annotations.EnableCustom("com.example.package.custom")
public class MyApplication {}
我关注了 Component scan using custom annotation,但这有几个限制:
我不能使基础包属性动态,即我不能通过
"com.example.package.base"
,但需要在配置中预先定义包。我查看了
@AliasFor
,但无法正常工作。当我省略基本包时,扫描从注释的定义包开始,而不是从被注释的包class开始。在上面的示例中,它只会为
扫描和创建 beancom.example.annotations
中的 classes 扫描并创建 bean,但不会为com.example.package.*
.我查看了
EntityScanPackages.Registrar.class
是在@EntityScan
注释中导入的,但它是一个内部 class 并且我的注释无法导入。
如果我将 @ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class))
放在 MyApplication
class 上,一切正常,但当它移至 @EnableCustom
的元注释时停止工作。如何告诉 Spring 框架将 @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 可用于多个项目的启动器。
M(N)WE 可以在以下存储库中找到:https://github.com/knittl/Whosebug/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");
}
}
使用带有 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);
}
}
在@EntityScan
annotation, I finally managed to get this to work. A compilable, working example can be found at https://github.com/knittl/Whosebug/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;
}
}
}