1、概览 {#1概览}
本文将带你了解如何在 Spring 中获取到所有带自定义注解的 Bean。
不同的 Spring 版本,实现方式也不同。
2、使用 Spring Boot 2.2 或更高版本 {#2使用-spring-boot-22-或更高版本}
从 Spring Boot 2.2 起,可以使用 getBeansWithAnnotation
方法。
创建一个示例。首先,定义自定义注解。注意,要使用 @Retention(RetentionPolicy.RUNTIME)
元注解,以确保程序在运行时可以访问注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomAnnotation {
}
现在,用注解定义第一个 Bean(还要添加 @Component
注解):
@Component
@MyCustomAnnotation
public class MyComponent {
}
然后,定义另一个带有注解的 Bean。不过,这次通过 @Configuration
中的 @Bean
方法来创建:
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旧版-spring}
3.1、历史背景 {#31历史背景}
在 Spring 5.2 之前的版本中,getBeansWithAnnotation
方法只能检测到类或接口级别上注解的 Bean,无法检测到在工厂方法级别上注解的 Bean。
如果使用旧版本的 Spring,刚才编写的测试就会失败(Spring Boot 2.2 的 Spring 已升级到 5.2):
MyComponent
Bean 可以被正确检测到,因为注解是在类的级别上。MyService
Bean 检测不到,因为它是通过工厂方法创建的。
3.2、用 @Qualifier 装饰自定义注解 {#32用-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 Qualifier。
3.3、通过 Factory 方法列出所有 Bean {#33通过-factory-方法列出所有-bean}
我们可以使用反射来访问 Bean 的注解,这在所有情况下都起作用且不需要对自定义注解进行任何更改。
由于可以访问 Spring ApplicationContext
,那么我们可以:
- 访问
BeanFactory
- 查找与每个 Bean 相关的
BeanDefinition
- 检查
BeanDefinition
的source
是否为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;
}
}
也可以使用 Stream
实现:
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、总结 {#4总结}
本文介绍了如何在 Spring 中获取带有自定义注解的 Bean,如果你使用的是 Spring Boot 2.2 及其以上版本,可以直接使用 getBeansWithAnnotation
方法。
如果版本较低,那么可以考虑在自定义注解上添加 @Qualifier
或者是通过 BeanFactory 来反射查找带有自定义注解的 Bean。
参考:https://www.baeldung.com/spring-injecting-all-annotated-beans