How to get list of Interfaces from @ComponentScan packages

人走茶凉 提交于 2019-12-03 07:20:41

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

Damián Rafael Lattenero

I your case I would use a config similar to this in your BeanLocation.xml and separate the proyect by subfolders like mine, I found that 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 tell to auto scan all component in folders and subfolders begining from /ar

You can check my public git project here -> git project

Check it, and if some new question is related, or maybe I did not understand your question well, let me know

We do this all the time without incident.

Below is the code for the service bean that will be using the List.

@Service
public class SomeService {

@Autowired
List<MyInterface> myInterfaceInstances;

//class stuff

}

Next we have the implementations of the interface.

@Component
public class SomeImpl implements MyInterface {

//class stuff

}

and another one just for good measure...

@Component
public class SomeOtherImpl implements MyInterface {

//class stuff

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!