I would like to implement something similar to Spring Data.
Developer can define some interfaces, add a custom annotation to the interfaces to mark them, (my code will create Proxy instances for the interfaces) and use them by @Autowire to necessary services.
During spring initializing I need to get list of all the interfaces (properly annotated)< create dynamic Proxy for the interfaces and inject them where they are necessary.
Proxy creation, created beans injecting is fine. Now the problem:
How to find the list of all the interfaces?
They could be placed in any package (or even in a separate jar) and have any name. Scanning all the classes existing on the classpath requires too much time.
I found the question but it requires base package to start.
Tried a Reflections based solution but again it requires base package or in case of starting from root requires really a lot of time to scan all classes available.
Reflections reflections = new Reflections("...");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(<annotation>);
So I need a full list of base packages Spring scans to find my Interfaces in the packages (must be much much faster).
The info is definitely available in SpringContext. I tried to debug and see how basePackages[] is initialized but there are a lot of private classes/methods used to initialize and I just don't see how to access the basePackages properly from ApplicationContext.
@Component and @ComponentScan are for different purposes. @Component indicates that a class might be a candidate for creating a bean. It's like putting a hand up. @ComponentScan is searching packages for Components.
Annotating an interface with @Component is common for Spring classes, particularly for some Spring stereotype annotations : package org.
With Spring, we use the @ComponentScan annotation along with the @Configuration annotation to specify the packages that we want to be scanned. @ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages.
@Configuration is meta annotated with @Component , which marks it eligible for classpath scanning. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig. class); @Bean doesn't need @ComponentScan as all these beans are created explicitly when spring encounters this annotation.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With