Solution 1: Spring way
The simplest answer is to follow how spring sub projects (boot,data...) implements this type of requirement. They usually define a custom composed annotation which enable the feature and define a set of packages to scan.
For example given this annotation :
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({MyInterfaceScanRegistrar.class})
public @interface MyInterfaceScan {
String[] value() default {};
}
Where value
defines the packages to scan and @Import
enables the MyInterfaceScan
detection.
Then create the ImportBeanDefinitionRegistrar
. This class will be able to create bean definition
Interface to be implemented by types that register additional bean
definitions when processing @Configuration classes. Useful when
operating at the bean definition level (as opposed to @Bean
method/instance level) is desired or necessary.
public class MyInterfaceScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Get the MyInterfaceScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyInterfaceScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0){
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
// using these packages, scan for interface annotated with MyCustomBean
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment){
// Override isCandidateComponent to only scan for interface
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(MyCustomBean.class));
// Scan all packages
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
// Do the stuff about the bean definition
// For example, redefine it as a bean factory with custom atribute...
// then register it
registry.registerBeanDefinition(generateAName() , beanDefinition);
System.out.println(beanDefinition);
}
}
}
}
}
This is the core of the logic. The bean definition can be manipulated and redefined as a bean factory with attributes or redefined using a generated class from an interface.
MyCustomBean
is a simple annotation:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomBean {
}
Which could annotate an interface:
@MyCustomBean
public interface Class1 {
}
Solution 2: extract component scan
The code which would extract packages define in @ComponentScan
will be more complicated.
You should create a BeanDefinitionRegistryPostProcessor and mimic the ConfigurationClassPostProcessor:
Iterate over the bean registry for bean definitions with a declared class having the ComponentScan
attribute eg (extracted from ConfigurationClassPostProcessor
.):
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// Extract component scan
}
}
}
Extract these attributes as Spring do
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
Then scan the packages and register the bean definition like the first solution