【问题标题】:How to get list of Interfaces from @ComponentScan packages如何从@ComponentScan 包中获取接口列表
【发布时间】:2017-09-18 04:53:16
【问题描述】:

我想实现类似于 Spring Data 的东西。

开发者可以定义一些接口,给接口添加自定义注解来标记它们,(我的代码会为这些接口创建代理实例)并通过@Autowire 将它们用于必要的服务。

在春季初始化期间,我需要获取所有接口的列表(正确注释)

代理创建,创建的bean注入很好。现在的问题:

如何找到所有接口的列表?

它们可以放在任何包中(甚至可以放在单独的 jar 中)并具有任何名称。扫描类路径中存在的所有类需要太多时间。

我找到了the question,但它需要基础包才能启动。

尝试了基于反射的解决方案,但它再次需要基础包,或者如果从根目录开始,则需要大量时间来扫描所有可用的类。

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

所以我需要 Spring 扫描的基本包的完整列表,以便在包中找到我的接口(必须快得多)。

这些信息在 SpringContext 中肯定是可用的。我尝试调试并查看 basePackages[] 是如何初始化的,但是有很多私有类/方法用于初始化,我只是看不到如何从 ApplicationContext 正确访问 basePackages。

【问题讨论】:

  • 您是否尝试创建 beanFactoryPostProccessor?创建它 - 对于每个 bean,查看它是否是使用您的注释创建的,如果是,则将类添加到工厂内的类集中。然后,您可以稍后从该工厂访问此列表。这有一个警告,它是一个单例。
  • @Sarief 我不能。要创建 bean,我需要找到接口。我不知道定义了多少接口以及接口放置在哪里。 bean 创建没有问题。问题是如何从类路径中找到所有接口
  • 抱歉,误解了你的问题
  • AnnotationAttributes 类,快速查看,应该包含对注解 类。至于用途,请检查:AnnotationRepositoryConfigurationSource
  • @Sarief 来自 Spring 数据。我现在没有弹簧数据。我希望避免这种情况。我有一种解决方法来定义一个超级接口,然后找到所有可用接口扩展基本接口。我希望避免这种方法。不想引入限制。

标签: java spring spring-boot component-scan


【解决方案1】:

解决方案一:弹簧方式

最简单的答案是遵循 spring 子项目 (boot,data...) 如何实现这种类型的需求。他们通常定义一个自定义组合注释来启用该功能并定义一组要扫描的包。

例如给出这个注释:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({MyInterfaceScanRegistrar.class})
public @interface MyInterfaceScan {

  String[] value() default {};
}

value 定义要扫描的包,@Import 启用MyInterfaceScan 检测。

然后创建ImportBeanDefinitionRegistrar。这个类将能够创建bean定义

由注册附加 bean 的类型实现的接口 处理@Configuration 类时的定义。有用的时候 在 bean 定义级别操作(而不是 @Bean 方法/实例级别)是需要或必要的。

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);
        }
      }
    }
  }
}

这是逻辑的核心。 bean 定义可以作为具有属性的 bean 工厂进行操作和重新定义,也可以使用从接口生成的类重新定义。

MyCustomBean 是一个简单的注解:

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

}

可以注释接口:

@MyCustomBean
public interface Class1 {

}

解决方案2:提取组件扫描

提取@ComponentScan 中定义的包的代码会更复杂。

您应该创建一个BeanDefinitionRegistryPostProcessor 并模仿ConfigurationClassPostProcessor

  • 使用具有ComponentScan 属性的已声明类例如(从ConfigurationClassPostProcessor 中提取。)遍历bean 注册表以获取bean 定义:

    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
        }
      }
    }
    
  • 像 Spring 一样提取这些属性

    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    
  • 然后像第一个解决方案一样扫描包并注册bean定义

【讨论】:

  • 还没有。稍后会尝试。谢谢!这是一个非常好的答案!
  • @Nicolas Labrot,解决方案 1 就像一个魅力!任何尝试使用这段代码的人,请阅读并理解它的作用,而不是盲目复制粘贴。
  • 正是我想要的。谢谢!
【解决方案2】:

我会在您的 BeanLocation.xml 中使用与此类似的配置,并通过像我这样的子文件夹分隔项目,我发现这很有用:

文件夹 -> 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>

如您所见,我告诉自动扫描从 /ar 开始的文件夹和子文件夹中的所有组件

你可以在这里查看我的公共 git 项目 -> git project

检查一下,如果有新的问题相关,或者我没有很好地理解你的问题,请告诉我

【讨论】:

  • 我没有任何 bean 位置配置。 XML 和类都不是。想象一下我正在创建一个库。该库已加载,例如通过 Maven 依赖。我不知道使用 lib 的开发人员如何配置他的 bean。我只需要从库中访问配置。
【解决方案3】:

我们一直这样做,没有发生任何事故。

下面是使用 List 的服务 bean 的代码。

@Service
public class SomeService {

@Autowired
List<MyInterface> myInterfaceInstances;

//class stuff

}

接下来是接口的实现。

@Component
public class SomeImpl implements MyInterface {

//class stuff

}

还有一个只是为了衡量......

@Component
public class SomeOtherImpl implements MyInterface {

//class stuff

}

【讨论】:

  • 没有类——只有接口。这个想法是只声明接口并从接口动态生成代理类。
  • @StanislavL 我跟着。您将不得不使用返回代理的@Bean 方法。我将在今天进入办公室并编辑我的答案时尝试一下。
猜你喜欢
  • 2012-12-18
  • 1970-01-01
  • 2015-05-08
  • 2014-08-25
  • 1970-01-01
  • 2010-09-16
  • 2017-10-22
  • 2016-09-09
  • 2011-10-21
相关资源
最近更新 更多