如何从@ComponentScan 包中获取接口列表
How to get list of Interfaces from @ComponentScan packages
我想实现类似于 Spring 数据的东西。
开发者可以定义一些接口,在接口上添加自定义注解来标记它们,(我的代码会为接口创建代理实例)并通过@Autowire 将它们用于必要的服务。
在 spring 初始化期间,我需要获取所有接口的列表(正确注释)<为接口创建动态代理并将它们注入到需要的地方。
代理创建,创建的bean注入没问题。现在的问题:
如何找到所有接口的列表?
它们可以放在任何包中(甚至放在单独的罐子中)并具有任何名称。扫描类路径中存在的所有 类 需要太多时间。
我找到了 但它需要基础包才能启动。
尝试了基于 Reflections 的解决方案,但它再次需要基础包,或者如果从 root 开始需要大量时间来扫描所有可用的 类。
Reflections reflections = new Reflections("...");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(<annotation>);
所以我需要基本包的完整列表 Spring 扫描以在包中找到我的接口(必须快得多)。
信息在 SpringContext 中绝对可用。我尝试调试并查看 basePackages[] 是如何初始化的,但是有很多私有 classes/methods 用于初始化,我只是看不到如何从 ApplicationContext 正确访问 basePackages。
根据你的情况,我会在你的 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 开始的文件夹和子文件夹中的所有组件
你可以在这里查看我的 public git 项目 -> git project
检查一下,如果有相关的新问题,或者我没有很好地理解你的问题,请告诉我
我们一直这样做,没有发生任何意外。
下面是将使用列表的服务 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
}
解决方案一:Spring方式
最简单的答案是遵循 spring 子项目(引导、数据...)如何实现此类要求。他们通常定义一个自定义组合注释来启用该功能并定义一组要扫描的包。
例如给出这个注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({MyInterfaceScanRegistrar.class})
public @interface MyInterfaceScan {
String[] value() default {};
}
其中 value
定义要扫描的包,@Import
启用 MyInterfaceScan
检测。
然后创建 ImportBeanDefinitionRegistrar
。这样class就能创建bean定义
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);
}
}
}
}
}
这是逻辑的核心。 bean 定义可以被操作并重新定义为具有属性的 bean 工厂,或者使用从接口生成的 class 重新定义。
MyCustomBean
是一个简单的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomBean {
}
可以注解接口的:
@MyCustomBean
public interface Class1 {
}
方案二:提取组件扫描
提取@ComponentScan
中定义的包的代码会更复杂。
您应该创建一个 BeanDefinitionRegistryPostProcessor and mimic the ConfigurationClassPostProcessor:
使用声明的 class 具有 ComponentScan
属性的 bean 定义迭代 bean 注册表,例如(从 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定义
我想实现类似于 Spring 数据的东西。
开发者可以定义一些接口,在接口上添加自定义注解来标记它们,(我的代码会为接口创建代理实例)并通过@Autowire 将它们用于必要的服务。
在 spring 初始化期间,我需要获取所有接口的列表(正确注释)<为接口创建动态代理并将它们注入到需要的地方。
代理创建,创建的bean注入没问题。现在的问题:
如何找到所有接口的列表?
它们可以放在任何包中(甚至放在单独的罐子中)并具有任何名称。扫描类路径中存在的所有 类 需要太多时间。
我找到了
尝试了基于 Reflections 的解决方案,但它再次需要基础包,或者如果从 root 开始需要大量时间来扫描所有可用的 类。
Reflections reflections = new Reflections("...");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(<annotation>);
所以我需要基本包的完整列表 Spring 扫描以在包中找到我的接口(必须快得多)。
信息在 SpringContext 中绝对可用。我尝试调试并查看 basePackages[] 是如何初始化的,但是有很多私有 classes/methods 用于初始化,我只是看不到如何从 ApplicationContext 正确访问 basePackages。
根据你的情况,我会在你的 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 开始的文件夹和子文件夹中的所有组件
你可以在这里查看我的 public git 项目 -> git project
检查一下,如果有相关的新问题,或者我没有很好地理解你的问题,请告诉我
我们一直这样做,没有发生任何意外。
下面是将使用列表的服务 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
}
解决方案一:Spring方式
最简单的答案是遵循 spring 子项目(引导、数据...)如何实现此类要求。他们通常定义一个自定义组合注释来启用该功能并定义一组要扫描的包。
例如给出这个注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({MyInterfaceScanRegistrar.class})
public @interface MyInterfaceScan {
String[] value() default {};
}
其中 value
定义要扫描的包,@Import
启用 MyInterfaceScan
检测。
然后创建 ImportBeanDefinitionRegistrar
。这样class就能创建bean定义
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);
}
}
}
}
}
这是逻辑的核心。 bean 定义可以被操作并重新定义为具有属性的 bean 工厂,或者使用从接口生成的 class 重新定义。
MyCustomBean
是一个简单的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomBean {
}
可以注解接口的:
@MyCustomBean
public interface Class1 {
}
方案二:提取组件扫描
提取@ComponentScan
中定义的包的代码会更复杂。
您应该创建一个 BeanDefinitionRegistryPostProcessor and mimic the ConfigurationClassPostProcessor:
使用声明的 class 具有
ComponentScan
属性的 bean 定义迭代 bean 注册表,例如(从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定义