如何在运行时以编程方式获取@ComponentScan的basePackages?

时间:2018-06-12 03:44:58

标签: spring-mvc spring-boot

@SpringBootApplication的scanBasePackages配置如下:

package com.xxx.boot.sample;

@SpringBootApplication(scanBasePackages = { "com.xxx.boot.sample", "com.xxx.boot.service" })
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

要求是我们希望通过在运行时编程零属性配置而不是通过注释来集成Apache Dubbo组件注释扫描与Spring Boot。

@Configuration
@ConditionalOnClass({ EnableDubboConfig.class, AbstractConfig.class })
@ConditionalOnProperty(prefix = "dubbo", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DubboAutoConfiguration {

  /// Dubbo配置

  @Configuration
  @EnableDubboConfig
  @ConditionalOnProperty(prefix = "dubbo.config", name = "enabled", havingValue = "true", matchIfMissing = true)
  @EnableConfigurationProperties(DubboProperties.class)
  public static class DubboConfigConfiguration {

  }

  /// Dubbo注解扫描

  @Configuration
  @ConditionalOnClass({ Service.class, Reference.class })
  @ConditionalOnProperty(prefix = "dubbo.annotation", name = "enabled", havingValue = "true", matchIfMissing = true)
  public static class DubboAnnotationConfiguration {

    @Bean(name = "serviceAnnotationBeanPostProcessor")
    @ConditionalOnMissingBean
    public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(BeanFactory beanFactory) {
      // 获取 Spring Boot 主入口类所在的包路径
      List<String> packagesToScan = AutoConfigurationPackages.get(beanFactory);
      if (packagesToScan == null) {
        packagesToScan = Collections.emptyList();
      }
      return new ServiceAnnotationBeanPostProcessor(packagesToScan);
    }

    @Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
    @ConditionalOnMissingBean
    public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
      return new ReferenceAnnotationBeanPostProcessor();
    }

  }

}

AutoConfigurationPackages#get(BeanFactory)仅返回"com.xxx.boot.sample",不包括"com.xxx.boot.service"。我希望返回所有scanBasePackages值。

通过调试,我发现@SpringBootApplication实例是一个代理类实例。我尝试使用注释Class#getAnnotations,然后通过反射获取scanBasePackages字段。但没有成功。

问题:

如何以编程方式获取scanBasePackages of @SpringBootApplicationbasePackages of @ComponentScan

1 个答案:

答案 0 :(得分:2)

尝试为您自己的目的重复使用scanBasePackages属性并不可取。如果您查看@SpringBootApplication的来源,您会看到以下内容:

@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};

这就是说这些属性是@ComponentScan的别名。由于此注释可用于任何@Configuration类,因此拥有其中许多注释实际上是合法的。

@ComponentScan注释会触发ConfigurationClassParser的扫描。查看所有血腥细节的doProcessConfigurationClass方法。

如果您真的想自己找到注释属性,可以执行以下操作:

applicationContext.getBeansWithAnnotation(ComponentScan.class).forEach((name, instance) -> {
    Set<ComponentScan> scans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
    for (ComponentScan scan : scans) {
        System.out.println(Arrays.toString(scan.basePackageClasses()));
        System.out.println(Arrays.toString(scan.basePackages()));
    }
});

这只会让你获得这两个值。您仍未考虑任何@Condition注释或任何包含/排除过滤器。您也不会处理@ComponentScan()这意味着从当前包中扫描。

Spring Boot在这些情况下倾向于为特定目的定义新注释。例如,您可以使用@EntityScan来定义找到JPA实体的位置。然后,如果您未指定任何覆盖,我们将使用AutoConfigurationPackages作为默认值。