クラスグラフライブラリのガイド

1. 概要

この簡単なチュートリアルでは、https://github.com/classgraph/classgraph [Classgraph]ライブラリについて説明します。これが何を助け、どのように使用できるかを説明します。
  • Classgraphは、Javaクラスパスでターゲットリソースを見つけるのに役立ち、見つかったリソースに関するメタデータを構築し、メタデータを操作するための便利なAPIを提供します。*

    このユースケースは、ステレオタイプアノテーションでマークされたコンポーネントがアプリケーションコンテキストに自動的に登録されるSpringベースのアプリケーションで非常に人気があります。 ただし、カスタムタスクにもこのアプローチを活用できます。 たとえば、特定の注釈を持つすべてのクラス、または特定の名前を持つすべてのリソースファイルを検索する場合があります。
    クールなのは、バイトコードレベルで動作するため、* Classgraphは高速であるということです。つまり、検査されたクラスはJVMにロードされず、処理にリフレクションを使用しません。

2. Mavenの依存関係

まず、https://search.maven.org/search?q = g:io.github.classgraph%20AND%20a:classgraph [classgraph]ライブラリを_pom.xml_に追加しましょう。
<dependency>
    <groupId>io.github.classgraph</groupId>
    <artifactId>classgraph</artifactId>
    <version>4.8.28</version>
</dependency>
次のセクションでは、ライブラリのAPIを使用したいくつかの実用的な例を見ていきます。

3. 基本的な使い方

*ライブラリを使用するには、3つの基本的な手順があります*
  1. スキャンオプションを設定します。たとえば、ターゲットパッケージ

  2. スキャンを実行する

  3. スキャン結果を操作する

    サンプルのセットアップ用に次のドメインを作成しましょう。
@Target({TYPE, METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

    String value() default "";
}
@TestAnnotation
public class ClassWithAnnotation {
}
_ @ TestAnnotation_を使用してクラスを検索する例について、上記の3つのステップを見てみましょう。
try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(ClassWithAnnotation.class.getName());
}
上記の例を分解してみましょう。
  • まず、スキャンオプションを設定します*(
    クラスおよび注釈情報のみを解析するスキャナ、およびターゲットパッケージのファイルのみを解析するように指示するスキャナ)

  • _ClassGraph.scan()_メソッドを使用して*スキャンを実行しました*

  • ScanResult *を使用して、
    _getClassWithAnnotation()_メソッド

    次の例でも見るように、_ScanResult_オブジェクトには、_ClassInfoList._など、検査するAPIに関する多くの情報を含めることができます。

4. メソッド注釈によるフィルタリング

例をメソッド注釈に拡張しましょう:
public class MethodWithAnnotation {

    @TestAnnotation
    public void service() {
    }
}
*同様のメソッド_getClassesWithMethodAnnotations()_:*を使用して、ターゲットアノテーションでマークされたメソッドを持つすべてのクラスを見つけることができます。
try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(MethodWithAnnotation.class.getName());
}
*このメソッドは、スキャンに一致するクラスに関する情報を含む_ClassInfoList_オブジェクトを返します。*

5. 注釈パラメーターによるフィルタリング

また、ターゲットアノテーションでマークされたメソッドとターゲットアノテーションパラメーター値を持つすべてのクラスを見つける方法も見てみましょう。
最初に、2つの異なるパラメーター値を持つ_ @ TestAnnotation、_を持つメソッドを含むクラスを定義しましょう。
public class MethodWithAnnotationParameterDao {

    @TestAnnotation("dao")
    public void service() {
    }
}
public class MethodWithAnnotationParameterWeb {

    @TestAnnotation("web")
    public void service() {
    }
}
それでは、_ClassInfoList_の結果を反復処理して、各メソッドの注釈を確認しましょう。
try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());
    ClassInfoList webClassInfos = classInfos.filter(classInfo -> {
        return classInfo.getMethodInfo().stream().anyMatch(methodInfo -> {
            AnnotationInfo annotationInfo = methodInfo.getAnnotationInfo(TestAnnotation.class.getName());
            if (annotationInfo == null) {
                return false;
            }
            return "web".equals(annotationInfo.getParameterValues().getValue("value"));
        });
    });

    assertThat(webClassInfos).extracting(ClassInfo::getName)
      .contains(MethodWithAnnotationParameterWeb.class.getName());
}
*ここでは、_AnnotationInfo_および_MethodInfo_メタデータクラスを使用して、確認するメソッドと注釈のメタデータを検索しました。*

6. フィールド注釈によるフィルタリング

_getClassesWithFieldAnnotation()_メソッドを使用して、フィールド注釈に基づいて_ClassInfoList_結果をフィルター処理することもできます。
public class FieldWithAnnotation {

    @TestAnnotation
    private String s;
}
try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithFieldAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(FieldWithAnnotation.class.getName());
}

7. リソースの検索

最後に、クラスパスリソースに関する情報を見つける方法を見ていきます。
_classgraph_クラスパスルートディレクトリ(たとえば、_src / test / resources / classgraph / my.config_)にリソースファイルを作成し、それにコンテンツを与えましょう。
my data
これで、リソースを見つけてそのコンテンツを取得できます。
try (ScanResult result = new ClassGraph().whitelistPaths("classgraph").scan()) {
    ResourceList resources = result.getResourcesWithExtension("config");
    assertThat(resources).extracting(Resource::getPath).containsOnly("classgraph/my.config");
    assertThat(resources.get(0).getContentAsString()).isEqualTo("my data");
}
ここで、__ScanResult '__ s _getResourcesWithExtension()_メソッドを使用して特定のファイルを検索したことがわかります。 *このクラスには、_getAllResources()、getResourcesWithPath()_、* _ * getResourcesMatchingPattern()* ._など、その他の便利なリソース関連メソッドがいくつかあります。
これらのメソッドは_ResourceList_オブジェクトを返します。これを使用して、_Resource_オブジェクトを繰り返し処理したり操作したりできます。

8. インスタンス化

見つかったクラスをインスタンス化する場合、_Class.forName、_経由ではなく、ライブラリメソッド_ClassInfo.loadClass_を使用してインスタンス化することが非常に重要です。
理由は、Classgraphは独自のクラスローダーを使用して、いくつかのJARファイルからクラスをロードするためです。 そのため、Class.forNameを使用すると、同じクラスが異なるクラスローダーによって複数回ロードされる可能性があり、これは重大なバグにつながる可能性があります。

9. 結論

この記事では、クラスパスリソースを効果的に見つけて、Classgraphライブラリでその内容を検査する方法を学びました。
いつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/libraries-2[GitHub上]で入手できます。