web-dev-qa-db-ja.com

Javaでプログラムでインターフェイスのすべての実装のリストを取得するにはどうすればよいですか?

リフレクションなどでそれを行うことはできますか?

66
user2427

私はしばらく探していましたが、異なるアプローチがあるようです、ここに要約があります:

  1. 反射依存関係の追加を気にしないのであれば、ライブラリはかなり人気があります。次のようになります。

    _Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    _
  2. ServiceLoader(エリクソンの回答による)そして次のようになります:

    _ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    _

    これが機能するには、PetをServiceProviderInterface(SPI)として定義し、その実装を宣言する必要があることに注意してください。 _resources/META-INF/services_という名前のファイルを_examples.reflections.Pet_という名前で作成し、その中にPetのすべての実装を宣言することにより、それを行います。

    _examples.reflections.Dog
    examples.reflections.Cat
    _
  3. パッケージレベルの注釈。以下に例を示します。

    _Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    _

    および注釈定義:

    _@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    _

    また、そのパッケージ内の_package-info.Java_という名前のファイルでパッケージレベルのアノテーションを宣言する必要があります。サンプルの内容は次のとおりです。

    _@MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    _

    その時点でClassLoaderに認識されているパッケージのみがPackage.getPackages()の呼び出しによってロードされることに注意してください。

さらに、ディレクトリベースの検索を行わない限り、URLClassLoaderに基づく他のアプローチがあり、常に既にロードされているクラスに制限されます。

40

エリクソンが言ったことですが、それでもやりたい場合は Reflections を見てください。彼らのページから:

Reflectionsを使用すると、次のメタデータを照会できます。

  • あるタイプのすべてのサブタイプを取得する
  • 何らかの注釈が付けられたすべてのタイプを取得する
  • 一致する注釈パラメーターを含む、何らかの注釈が付けられたすべてのタイプを取得します
  • いくつかの注釈が付いたすべてのメソッドを取得します
27
Peter Severin

一般に、これを行うには費用がかかります。リフレクションを使用するには、クラスをロードする必要があります。クラスパスで利用可能なすべてのクラスをロードする場合、時間がかかり、メモリがかかるため、お勧めしません。

これを避けたい場合は、リフレクションではなく、より効率的に動作する独自のクラスファイルパーサーを実装する必要があります。このアプローチには、バイトコードエンジニアリングライブラリが役立ちます。

サービスプロバイダーメカニズム は、プラグ可能なサービスの実装を列挙する従来の手段です。 ServiceLoader in Java 6を使用するか、以前のバージョンで独自に実装します。別の回答で を提供しました。 。

24
erickson

Springには、これを達成するための非常に簡単な方法があります。

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

その後、タイプITaskのリストを自動配線すると、Springがすべての実装をリストに追加します。

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
12
kaybee99

エリクソンが言ったことは最高です。関連する質問と回答のスレッドを次に示します- http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

Apache BCELライブラリを使用すると、クラスをロードせずに読み取ることができます。検証手順をスキップできるため、より高速になると思います。クラスローダーを使用してすべてのクラスをロードする際のもう1つの問題は、メモリへの多大な影響を被るだけでなく、おそらく望まない静的コードブロックを誤って実行することです。

Apache BCELライブラリリンク- http://jakarta.Apache.org/bcel/

4
Sachin

はい、最初のステップは、気にしたクラスを「すべて」識別することです。この情報が既にある場合は、それらの各情報を列挙し、instanceofを使用して関係を検証できます。関連記事はこちら http://www.javaworld.com/javaworld/javatips/jw-javatip113.html

4
Zhichao

私は同じ問題に遭遇しました。私の解決策は、リフレクションを使用してObjectFactoryクラスのすべてのメソッドを調べ、バインドされたPOJOのいずれかのインスタンスを返すcreateXXX()メソッドではないメソッドを排除することでした。そのように検出された各クラスはClass []配列に追加され、JAXBContextインスタンス化呼び出しに渡されます。これはうまく機能し、ObjectFactoryクラスをロードするだけでよく、それはとにかく必要でした。 ObjectFactoryクラスを維持する必要があるのは、手作業(POJOで始めてschemagenを使用したため)で実行するか、xjcで必要に応じて生成できるタスクです。いずれにせよ、パフォーマンスが高く、シンプルで効果的です。

1
NDK

また、IDEプラグイン(あなたがやろうとしていることは比較的一般的です)を書いている場合、通常、IDEはより効率的な方法を提供しますユーザーコードの現在の状態のクラス階層にアクセスします。

1
Uri

@ kaybee99の答えの新しいバージョンですが、ユーザーが尋ねたものを返します:実装...

Springには、これを達成するための非常に簡単な方法があります。

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

その後、タイプITaskのリストを自動配線すると、Springがすべての実装をリストに追加します。

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}

ClassGraph を使用すると、非常に簡単です。

my.package.MyInterfaceの実装を見つけるためのGroovyコード:

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.className
0
verglor

ClassGraph を試してください。 (免責事項、私は著者です)。 ClassGraphは、実行時またはビルド時に特定のインターフェイスを実装するクラスのスキャンをサポートしますが、さらに多くのクラスのスキャンもサポートします。 ClassGraphは、メモリ内、クラスパス上のすべてのクラス、またはホワイトリストに登録されたパッケージ内のクラスについて、クラスグラフ全体(すべてのクラス、注釈、メソッド、メソッドパラメーター、フィールド)の抽象表現を構築できますが、そのクラスグラフをクエリできますあなたが欲しい。 ClassGraphは、他のスキャナーよりも クラスパス指定メカニズムとクラスローダー をサポートしており、新しいJPMSモジュールシステムともシームレスに動作します。そのため、ClassGraphに基づいてコードを作成すると、コードの移植性が最大限になります。 ここのAPIを参照してください。

0
Luke Hutchison