Reputation: 91
I noticed a problem with Spring's @ComponentScan
annotation when used as a meta-annotation.
In the following example project structure, both the FirstHandler
and SecondService
classes should be scanned as components and registered as beans:
org/example/
|_ ExampleContext.java
|___ api/
| |___ ExampleCommand.java
|___ application/
|___ FirstHandler.java
|___ SecondService.java
// --- ExampleContext.java ---
@ContextConfiguration
public class ExampleContext { }
// --- api/ExampleCommand.java ---
public class ExampleCommand extends Command {
// -snip-
}
// --- application/FirstHandler.java ---
public class FirstHandler implements CommandHandler<ExampleCommand> {
// -snip-
}
// --- application/SecondService.java ---
@CommandService
public class SecondService {
@CommandMethod(ExampleCommand.class)
public void handle(ExampleCommand command) {
// -snip-
}
}
The Command
and related classes are custom, and not relevant to the question at hand. For the purpose of this question, they function as markers and reside in a module which does not depend on Spring, ergo can not be meta-annotated themselves.
The custom annotation ContextConfiguration
is supposed to scan all classes either implementing CommandHandler<C>
or annotated with CommandService
:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScans({
// Scan for CommandHandler implementations
@ComponentScan(includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
CommandHandler.class,
})
}),
// Scan for @CommandService annotated classes
@ComponentScan(includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {
CommandService.class,
})
})
})
public @interface ContextConfiguration { }
But what actually happens is that only the first @ComponentScan
annotation is used, and the second annotation is simply ignored.
By changing the order of annotations or removing one, I can change which one is ignored/active, but only one of the classes is scanned by Spring.
Is this a known issue? Are there any solutions/workarounds?
Thank you and have a nice day,
Alex.
Upvotes: 2
Views: 301
Reputation: 91
I found a workaround, which basically re-implements what the repeated @ComponentScan
annotations should be doing.
It was inspired by this answer, with a slightly simplified implementation.
To use multiple different component-scans, a nested configuration implementing ImportBeanDefinitionRegistrar
can be imported on the annotation.
Example:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@Import(ContextConfiguration.ScanContextComponents.class)
public @interface ContextConfiguration {
class ScanContextComponents implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingMetadata, BeanDefinitionRegistry registry) {
String basePackage = ClassUtils.getPackageName(importingMetadata.getClassName());
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// Add include-filters for different types here
provider.addIncludeFilter(new AssignableTypeFilter(CommandHandler.class));
provider.addIncludeFilter(new AnnotationTypeFilter(CommandService.class, true));
// Register bean-definition candidates
provider.setEnvironment(environment);
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
String beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
}
Upvotes: 0