1. 概要

この簡単なチュートリアルでは、 Classgraph ライブラリについて説明します—それが何に役立つのか、そしてそれをどのように使用できるのか。

Classgraphは、Javaクラスパスでターゲットリソースを見つけるのに役立ち、見つかったリソースに関するメタデータを構築し、メタデータを操作するための便利なAPIを提供します。

このユースケースは、ステレオタイプアノテーションでマークされたコンポーネントがアプリケーションコンテキストに自動的に登録されるSpringベースのアプリケーションで非常に人気があります。 ただし、カスタムタスクにもそのアプローチを利用できます。 たとえば、特定のアノテーションを持つすべてのクラス、または特定の名前を持つすべてのリソースファイルを検索したい場合があります。

すばらしいのは、 Classgraphはバイトコードレベルで動作するため高速です。つまり、検査されたクラスはJVMにロードされず、処理にリフレクションを使用しません。

2. Mavenの依存関係

まず、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’getResourcesWithExtension()メソッドを使用して特定のファイルを検索したことがわかります。 このクラスには、getAllResources()、getResourcesWithPath()、 getResourcesMatchingPattern()。など、他にもいくつかの便利なリソース関連メソッドがあります。

これらのメソッドはResourceListオブジェクトを返します。このオブジェクトをさらに使用して、Resourceオブジェクトを反復処理および操作できます。

8. インスタンス化

見つかったクラスをインスタンス化する場合は、 Class.forName、ではなく、ライブラリメソッドClassInfo.loadClassを使用してインスタンス化することが非常に重要です。

その理由は、Classgraphが独自のクラスローダーを使用して、一部のJARファイルからクラスをロードするためです。 したがって、 Class.forName を使用すると、同じクラスが異なるクラスローダーによって複数回ロードされる可能性があり、これにより重大なバグが発生する可能性があります。

9. 結論

この記事では、クラスパスリソースを効果的に見つけ、Classgraphライブラリを使用してその内容を検査する方法を学びました。

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