1. 概述
在这个教程中,我们将解释如何在Spring中查找所有使用自定义注解标记的bean。我们会根据使用的Spring版本展示不同的方法。
2. 使用Spring Boot 2.2及以上版本
从Spring Boot 2.2开始,我们可以使用getBeansWithAnnotation
方法。
让我们构建一个示例。首先,我们定义我们的自定义注解。为了确保程序运行时可以访问注解,我们将其注解为@Retention(RetentionPolicy.RUNTIME)
:
@Retention( RetentionPolicy.RUNTIME )
public @interface MyCustomAnnotation {
}
现在,我们定义一个使用我们注解的第一个bean。我们还会将它注解为@Component
:
@Component
@MyCustomAnnotation
public class MyComponent {
}
然后,我们定义另一个使用我们注解的bean。但这次,我们将通过一个@Bean
注解的方法在一个@Configuration
文件中创建它:
public class MyService {
}
@Configuration
public class MyConfigurationBean {
@Bean
@MyCustomAnnotation
MyService myService() {
return new MyService();
}
}
现在,编写一个测试来检查getBeansWithAnnotation
方法是否能检测到这两个bean:
@Test
void whenApplicationContextStarted_ThenShouldDetectAllAnnotatedBeans() {
try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( MyComponent.class, MyConfigurationBean.class )) {
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(MyCustomAnnotation.class);
assertEquals(2, beans.size());
assertTrue(beans.keySet().containsAll(List.of("myComponent", "myService")));
}
}
3. 使用较旧的Spring版本
3.1. 历史背景
在Spring框架的早期版本(5.2之前),getBeansWithAnnotation
方法只能检测到类或接口级别的注解,无法检测到工厂方法级别的bean。
Spring Boot 2.2升级了对Spring Framework的依赖,因此在较旧版本的Spring中,我们刚才写的测试可能会失败:
MyComponent
bean被正确检测到,因为注解在类级别上MyService
bean未被检测到,因为它通过工厂方法创建
让我们看看如何解决这个问题。
3.2. 用@Qualifier装饰自定义注解
有一个相当简单的解决方案:我们可以简单地**在我们的注解上添加@Qualifier
**。
我们的注解将如下所示:
@Retention( RetentionPolicy.RUNTIME )
@Qualifier
public @interface MyCustomAnnotation {
}
现在,我们能够自动注入两个带有注解的bean。让我们用一个测试来看看:
@Autowired
@MyCustomAnnotation
private List<Object> annotatedBeans;
@Test
void whenAutowiring_ThenShouldDetectAllAnnotatedBeans() {
assertEquals(2, annotatedBeans.size());
List<String> classNames = annotatedBeans.stream()
.map(Object::getClass)
.map(Class::getName)
.map(s -> s.substring(s.lastIndexOf(".") + 1))
.collect(Collectors.toList());
assertTrue(classNames.containsAll(List.of("MyComponent", "MyService")));
}
这个工作流是最简单的,但如果注解不是我们自己拥有的,可能不符合我们的需求。
还要注意,通过@Qualifier
装饰我们的自定义注解会使它成为Spring的资格器。
3.3. 列出通过工厂方法创建的bean
既然我们理解问题主要出现在通过工厂方法创建的bean上,让我们专注于如何只列出这些bean。我们将提供一个在所有情况下都不需要更改自定义注解的解决方案。我们将使用反射来访问bean的注解。
既然我们有Spring的ApplicationContext
访问权限,我们将遵循一系列步骤:
- 获取
BeanFactory
- 查找每个bean关联的
BeanDefinition
- 检查
BeanDefinition
的来源是否是AnnotatedTypeMetadata
,这意味着我们将能够访问bean的注解 - 如果bean有注解,检查是否包含所需的注解
让我们创建一个名为BeanUtils
的实用工具类,并在其中实现这个逻辑:
public class BeanUtils {
public static List<String> getBeansWithAnnotation(GenericApplicationContext applicationContext, Class<?> annotationClass) {
List<String> result = new ArrayList<String>();
ConfigurableListableBeanFactory factory = applicationContext.getBeanFactory();
for(String name : factory.getBeanDefinitionNames()) {
BeanDefinition bd = factory.getBeanDefinition(name);
if(bd.getSource() instanceof AnnotatedTypeMetadata) {
AnnotatedTypeMetadata metadata = (AnnotatedTypeMetadata) bd.getSource();
if (metadata.getAnnotationAttributes(annotationClass.getName()) != null) {
result.add(name);
}
}
}
return result;
}
}
或者,我们也可以使用Streams
写同样的函数:
public static List<String> getBeansWithAnnotation(GenericApplicationContext applicationContext, Class<?> annotationClass) {
ConfigurableListableBeanFactory factory = applicationContext.getBeanFactory();
return Arrays.stream(factory.getBeanDefinitionNames())
.filter(name -> isAnnotated(factory, name, annotationClass))
.collect(Collectors.toList());
}
private static boolean isAnnotated(ConfigurableListableBeanFactory factory, String beanName, Class<?> annotationClass) {
BeanDefinition beanDefinition = factory.getBeanDefinition(beanName);
if(beanDefinition.getSource() instanceof AnnotatedTypeMetadata) {
AnnotatedTypeMetadata metadata = (AnnotatedTypeMetadata) beanDefinition.getSource();
return metadata.getAnnotationAttributes(annotationClass.getName()) != null;
}
return false;
}
在这两个方法中,我们使用了GenericApplicationContext
,它是Spring的ApplicationContext
的一个实现,不假设特定的bean定义格式。
要访问GenericApplicationContext
,例如,我们可以在Spring组件中注入它:
@Component
public class AnnotatedBeansComponent {
@Autowired
GenericApplicationContext applicationContext;
public List<String> getBeansWithAnnotation(Class<?> annotationClass) {
return BeanUtils.getBeansWithAnnotation(applicationContext, annotationClass);
}
}
4. 总结
在这篇文章中,我们讨论了如何列出带有特定注解的bean。我们看到,自从Spring Boot 2.2以来,getBeansWithAnnotation
方法自然地完成了这项任务。
另一方面,我们展示了如何克服这个方法过去行为限制的一些替代方法:要么在我们的注解上添加@Qualifier
,要么通过查找bean并使用反射检查它们是否具有注解。