1. 概要

このチュートリアルでは、Springでカスタムアノテーションが付けられたすべてのBeanを見つける方法を説明します。 使用するSpringバージョンに応じて異なる方法を示します。

2. SpringBoot2.2以降の場合

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. 古い春のバージョンで

3.1. 歴史的背景

5.2より前のSpringFrameworkバージョンでは、 getBeansWithAnnotation メソッドは、クラスまたはインターフェースレベルで注釈が付けられたBeanのみを検出しましたが、ファクトリメソッドレベルで注釈が付けられたBeanを検出できませんでした。

Springフレームワークの依存関係がSpring Boot2.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のアノテーションにアクセスします。

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 を使用しました。これは、特定のbean定義形式を想定しないSpring ApplicationContextの実装です。

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を検索します。注釈があるかどうかを確認します。

いつものように、完全なコードはGitHubから入手できます。