How to get a list of interfaces from @ComponentScan packages

I would like to implement something similar to Spring Data.

The developer can define some interfaces, add a user annotation to the interfaces for marking them (my code will create Proxy instances for the interfaces) and use them with @Autowire for the necessary services.

During Spring initialization, I need to get a list of all interfaces (correctly annotated) <create a dynamic proxy for the interfaces and enter them where they are needed.

Creating a proxy created by beans injection is fine. Now the problem is:

How to find a list of all interfaces?

They can be placed in any packaging (or even in a separate jar) and have any name. Scanning all classes that exist on the way to classes takes too much time.

I found a question , but this requires a basic package.

I tried a solution based on reflections, but again it requires a basic package, or in the case of starting from root, it takes a lot of time to scan all available classes.

Reflections reflections = new Reflections("..."); Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(<annotation>); 

So, I need a complete list of Spring's base packages for finding my interfaces in packages (should be much faster).

Information is definitely available in SpringContext. I tried to debug and see how basePackages [] is initialized, but many private classes / methods are used for initialization, and I just don’t see how to correctly access basePackages from ApplicationContext.

+5
source share
3 answers

Solution 1: Spring Method

The simplest answer is to keep track of how Spring subprojects (boot, data ...) implement this type of requirement. Usually they define a custom annotation that allows you to use this feature and define a set of packages for scanning.

For example, given this annotation:

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import({MyInterfaceScanRegistrar.class}) public @interface MyInterfaceScan { String[] value() default {}; } 

Where value determines the packets to scan, and @Import enables MyInterfaceScan detection.

Then create an ImportBeanDefinitionRegistrar . This class will be able to create a bean definition.

An interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when working at the bean definition level (as opposed to the @ 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 logic. The definition of a bean can be manipulated and redefined as a bean factory with attributes, or redefined using the generated class from the interface.

MyCustomBean - simple annotation:

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomBean { } 

Which could annotate the interface:

 @MyCustomBean public interface Class1 { } 

Solution 2: retrieve the component check

The code that will retrieve packets in @ComponentScan will be more complex.

You must create a BeanDefinitionRegistryPostProcessor and emulate the ConfigurationClassPostProcessor :

  • Iterate over the bean registry to determine a bean with a declared class that has a ComponentScan attribute, for example (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 as the first solution

+2
source

In your case, I would use a configuration similar to this in your BeanLocation.xml and split the proyect into subfolders like mine, I found it useful:

folders β†’ java / ar / edu / unq / tip / marchionnelattenero

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <tx:annotation-driven transaction-manager="persistence.transactionManager" proxy-target-class="true"/> <!-- Database Configuration --> <!-- Auto scan the components --> <context:component-scan base-package="ar.*"/> </beans> 

As you can see, I say to automatically scan the entire component in folders and subfolders, starting with / ar

Here you can check out my public git project -> git project

Check this out, and if any new question is related, or maybe I misunderstood your question, let me know

0
source

We do this all the time without incident.

Below is the bean service code that the List will use.

 @Service public class SomeService { @Autowired List<MyInterface> myInterfaceInstances; //class stuff } 

Next we have interface implementations.

 @Component public class SomeImpl implements MyInterface { //class stuff } 

and another for a good grade ...

 @Component public class SomeOtherImpl implements MyInterface { //class stuff } 
0
source

Source: https://habr.com/ru/post/1267009/


All Articles