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并使用反射检查它们是否具有注解。


» 下一篇: Spark DataFrame