8

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

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

在春季初始化期间,我需要获取所有接口的列表(正确注释)<为接口创建动态代理并将它们注入必要的地方。

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

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

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

我找到了问题,但它需要基本包才能启动。

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

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

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

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

4

3 回答 3

10

解决方案1:弹簧方式

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

例如给出这个注释:

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

  String[] value() default {};
}

Wherevalue定义要扫描的包并@Import启用MyInterfaceScan检测。

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

在处理 @Configuration 类时注册附加 bean 定义的类型实现的接口。在需要或必须在 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

  • 使用具有属性的声明类迭代 bean 注册表以获取 bean 定义,ComponentScan例如(从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
        }
      }
    }
    
  • 像 Spring 一样提取这些属性

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

于 2017-04-27T07:37:28.730 回答
0

在您的情况下,我会在您的 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 项目

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

于 2017-04-25T15:01:11.590 回答
0

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

下面是将使用 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

}
于 2017-04-25T19:40:04.067 回答