1. 概述
在本教程中,我們將解釋如何查找所有帶有自定義註解的 Bean 在 Spring 中的用法。 我們將展示根據我們使用的 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 Framework 版本 5.2 之前,getBeansWithAnnotation 方法只會檢測在類或接口級別上添加了註解的 Bean,而無法檢測在工廠方法級別上添加了註解的 Bean。
Spring Framework 在 Spring Boot 2.2 中升級到了 5.2 版本,因此,在舊版本的 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 的 <em >ApplicationContext</em> 訪問權限,我們將遵循以下步驟:
- 訪問 `BeanFactory`
- 查找與每個 Bean 關聯的 `BeanDefinition`
- 檢查 `BeanDefinition` 的來源是否為 `AnnotatedTypeMetadata》,這意味着我們可以訪問 Bean 的註解
- 如果 Bean 具有註解,請檢查所需的註解是否在其中之一
讓我們創建一個自定義的 <em >BeanUtils</em> 工具類,並在一個方法中實現此邏輯:
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;
}
}當然,以下是翻譯後的內容:
或者,我們也可以使用 流 來編寫相同的函數:
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,使用反射來檢查它們是否具有該註解。