実行時のJavaアノテーションのスキャン[終了]


253

注釈付きクラスのクラスパス全体を検索する最良の方法は何ですか?

私はライブラリーをやっていて、ユーザーがクラスに注釈を付けられるようにしたいので、Webアプリケーションが開始するときに、クラスパス全体をスキャンして特定の注釈を見つける必要があります。

これを行うためのライブラリまたはJava機能を知っていますか?

編集:Java EE 5 WebサービスまたはEJBの新機能のようなものについて考えています。@WebServiceまたは@EJBでクラスに注釈を付けると、システムはロード中にこれらのクラスを見つけ、リモートでアクセスできるようにします。

回答:


210

使用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

基本パッケージからクラスパスをスキャンするコンポーネントプロバイダー。次に、結果のクラスに除外フィルターと包含フィルターを適用して、候補を見つけます。

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());

5
情報をありがとう。また、フィールドにカスタムアノテーションがあるクラスのクラスパスをスキャンする方法を知っていますか?
Javatar

6
@Javatar JavaのリフレクションAPIを使用します。<YOUR_CLASS> .class.getFields()各フィールドについて、getAnnotation(<YOUR_ANNOTATION>)を呼び出します
Arthur Ronald

1
注:これをSpringアプリケーション内で実行している場合でも、Springは@Conditionalアノテーションに基づいて評価および動作します。したがって、クラスの@Conditional値がfalseを返しfindCandidateComponentsている場合、スキャナーのフィルターに一致していても、によって返されません。これは今日私を投げました-私は代わりに以下のジョナサンのソリューションを使用することになりました。
Adam Burley

1
@ArthurRonaldすみません、アーサー。つまり、BeanDefinitionオブジェクトはクラスを直接取得する方法を提供していません。最も近いものgetBeanClassNameは、完全修飾クラス名を返すようですが、このメソッドの正確な動作は明確ではありません。また、クラスが見つかったクラスローダーは明確ではありません
最大

3
@Maxこれを試してください:Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

そして別の解決策はGoogleの反射ですです。

簡単なレビュー:

  • Springを使用している場合は、Springソリューションが適しています。そうでなければ、それは大きな依存関係です。
  • ASMを直接使用するのは少し面倒です。
  • Java Assistを直接使用するのも不格好です。
  • Annoventionは超軽量で便利です。Maven統合はまだありません。
  • GoogleリフレクションはGoogleコレクションを取り込みます。すべてにインデックスを付けてから、超高速です。

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)。c'est売り込み。
zapp

4
パッケージ名を指定する必要がありますか?ワイルドカード?クラスパスのすべてのクラスはどうですか?
2018年

1
Java 9のサポートはまだ進展していないことに注意してください:github.com/ronmamo/reflections/issues/186
Vadzim

org.reflectionsライブラリはJava 13で正しく動作しません(おそらく以前にも)。初めて呼び出されたときは問題ないようです。後続のインスタンス化と使用は、検索URLが構成されていないことを示して失敗します。
Evvo

44

ClassGraphを使用して、特定の注釈が付いたクラスを検索したり、特定のインターフェースを実装するクラスなど、関心のある他の基準を検索したりできます。(免責事項、私はClassGraphの作成者です。)ClassGraphは、メモリ内、クラスパス上のすべてのクラス、またはホワイトリストに登録されたパッケージを使用して、必要に応じてクラスグラフをクエリできます。ClassGraphは、他のどのスキャナーよりも多くのクラスパス仕様メカニズムとクラスローダーをサポートし、新しいJPMSモジュールシステムとシームレスに連携するため、コードをClassGraphに基づいている場合、コードは最大限に移植可能になります。こちらのAPIをご覧ください。


1
これを実行するにはJava 8が必要ですか?
デビッドジョージ

1
Java7を使用するように更新されました。問題ありません。注釈を削除し、匿名の内部クラスを使用するように関数を変換するだけです。1ファイルスタイルが好きです。コードはすっきりしているので、必要なものがいくつかサポートされていなくても(クラス+アノテーションを同時に)、簡単に追加できます。すごい仕事!誰かがv7用に変更する作業をなんとかできない場合は、おそらくに進む必要がありReflectionsます。また、グアバなどを使用していて、コレクションを簡単に変更したい場合も簡単です。内部にも素晴らしいコメント。
Andrew Backer 2014

2
@Alexandrosよろしくお願いします。ClassGraphをチェックしてください。FastClasspathScannerよりも大幅に改善されています。
ルークハッチソン、

2
@AndrewBacker ClassGraph(FastClasspathScannerの新バージョン)は、フィルターまたは集合演算を介したブール演算を完全にサポートしています。こちらのコード例を参照してください:github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison、

1
@Luke HutchisonはすでにClassGraphを使用しています。Java 10への移行を手伝ってくれました。本当に便利なライブラリです。
アレクサンドロス


20

Java Pluggable Annotation Processing APIを使用して、コンパイルプロセス中に実行され、すべての注釈付きクラスを収集し、ランタイムで使用するインデックスファイルを構築する注釈プロセッサを作成できます。

これは、実行時にクラスパスをスキャンする必要がないため、注釈付きのクラス検出を行うための可能な最速の方法です。これは通常、非常に遅い操作です。また、このアプローチは、ランタイムスキャナーで通常サポートされているURLClassLoadersだけでなく、あらゆるクラスローダーで機能します。

上記のメカニズムは、ClassIndexライブラリにすでに実装されてます。

これを使用するには、@ IndexAnnotatedメタ注釈でカスタム注釈に注釈を付けます。これにより、コンパイル時にインデックスファイルが作成されます。すべての注釈付きクラスをリストするMETA-INF / annotations / com / test / YourCustomAnnotation。次のコマンドを実行すると、実行時にインデックスにアクセスできます。

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

5

答えるには遅すぎますか?ClassPathScanningCandidateComponentProviderScannotationsのようなライブラリを使用する方が良いと思います

しかし、誰かがclassLoaderを使って実際に試してみたいと思った後でも、パッケージ内のクラスからの注釈を出力するために、自分でいくつか書いています。

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

そして、設定ファイルで、パッケージ名を入れてクラスにアンマーシャルします。


3

Springでは、AnnotationUtilsクラスを使用して次のように記述することもできます。つまり:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

詳細およびすべての異なる方法については、公式ドキュメントを確認してください:https : //docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
いい考えですがnull、2番目のパラメーター(クラスを定義し、継承階層が注釈を使用するクラスをSpringがスキャンする)として値を指定nullした場合、実装によれば、いつでも元に戻すことができます。
jonashackt 2017年

3

これらすべての答えに陥るzappによる素晴らしいコメントがあります。

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

クラスローダーは「オンデマンド」アクティビティであるため、Classloader APIには「列挙」メソッドがありません。通常、クラスパスには何千ものクラスがあり、その一部のみが必要になる(rt.jar今日だけで48MBです!)

したがって、すべてのクラスを列挙できたとしても、これは非常に時間とメモリを消費します。

単純なアプローチは、関係するクラスをセットアップファイル(xmlまたは空想に適したもの)にリストすることです。これを自動的に行う場合は、1つのJARまたは1つのクラスディレクトリに制限します。


2

インターフェースも発見したい場合は、Google Reflection

Spring ClassPathScanningCandidateComponentProviderはインターフェースを発見していません。


1

Google ReflectionsはSpringよりもはるかに高速であるようです。この違いに対処するこの機能リクエストが見つかりました:http ://www.opensaga.org/jira/browse/OS-738

アプリケーションの起動時間は開発中に非常に重要であるため、これがReflectionsを使用する理由です。リフレクションは、私のユースケースでも非常に使いやすいようです(インターフェイスのすべての実装者を見つけます)。


1
JIRAの問題を見ると、安定性の問題のためにReflectionsから離れたというコメントがあります。
Wim Deblauwe、2015年

1

リフレクションの代替手段を探している場合は、Panda Utilities-AnnotationsScannerをお勧めします。ます。これは、反射ライブラリのソースコードに基づく、グアバフリー(グアバは最大3MB、パンダユーティリティは最大200kb)スキャナーです。

また、将来に基づく検索専用です。含まれているソースを複数回スキャンしたい場合や、誰かが現在のクラスパスをスキャンできるようにするAPIを提供したい場合AnnotationsScannerProcessClassFiles、フェッチされたすべてをキャッシュするため、非常に高速です。

簡単なAnnotationsScanner使用例:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);

1

春にはAnnotatedTypeScannerクラスと呼ばれるものがあります。
このクラスは内部で使用します

ClassPathScanningCandidateComponentProvider

このクラスには、クラスパスリソースを実際にスキャンするためのコードがあります。これは、実行時に使用可能なクラスメタデータを使用して行われます。

このクラスを拡張するか、同じクラスをスキャンに使用できます。以下はコンストラクタの定義です。

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.