How to get list of Interfaces from @ComponentScan packages

后端 未结 3 963
慢半拍i
慢半拍i 2020-12-31 19:12

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 w

3条回答
  •  死守一世寂寞
    2020-12-31 19:19

    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 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 configCandidates = new ArrayList();
        String[] candidateNames = registry.getBeanDefinitionNames();
        for (String beanName : candidateNames) {
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            // Extract component scan
          }
        }
      }
      
    • Extract these attributes as Spring do

      Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
              sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
      
    • Then scan the packages and register the bean definition like the first solution

提交回复
热议问题