実行時に、基本クラスを拡張するJavaアプリケーション内のすべてのクラスを検索します


147

私はこのようなことをしたいです:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Animal)
      animals.add( new c() );

そのため、アプリケーションのユニバース内のすべてのクラスを確認し、Animalから派生したクラスを見つけたら、そのタイプの新しいオブジェクトを作成してリストに追加します。これにより、リストを更新しなくても機能を追加できます。以下を回避できます。

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

上記のアプローチでは、Animalを拡張する新しいクラスを作成するだけで、自動的に取得されます。

更新:2008/10/16太平洋標準時午前9:00:

この質問により多くの素晴らしい回答が得られました。ありがとうございました。 回答と私の調査から、私が本当にやりたいことは、Javaでは不可能であることがわかりました。ddimitrovのServiceLoaderメカニズムなど、機能するアプローチはいくつかありますが、私が望むものには非常に重く、問題をJavaコードから外部構成ファイルに移すだけだと思います。 19年5月10日更新(11年後!)@IvanNikの回答に よると、これに役立つライブラリがいくつかありますorg.reflectionsは良さそうです。またClassGraph @Lukeハチソンさんからの答えは面白そうですね。答えにはさらにいくつかの可能性があります。

私が望むものを述べる別の方法:私のAnimalクラスの静的関数は、追加の構成/コーディングなしで、Animalから継承するすべてのクラスを見つけてインスタンス化します。構成する必要がある場合は、いずれにしても、Animalクラスでインスタンス化するだけで済みます。Javaプログラムは.classファイルの緩やかな連合なので、それがそのままの方法であることは理解しています。

興味深いことに、これはC#ではかなり簡単なようです。


1
しばらく検索したところ、これはJavaで解読するのが難しいナットのようです。ここではいくつかの情報を持っているスレッドがあります: forums.sun.com/thread.jspa?threadID=341935&start=15 その中の実装は、より多くのIの必要性よりも、私はちょうど今の第2の実施に固執うと思います。
JohnnyLambada 2008年

それを答えにして、読者に投票させてください
VonC

3
C#でこれを行うためのリンクは、特別なものを示していません。AC#アセンブリはJava JARファイルに相当します。これは、コンパイルされたクラスファイル(およびリソース)のコレクションです。リンクは、単一のアセンブリからクラスを取得する方法を示しています。Javaを使用すると、ほとんど同じようにそれを行うことができます。あなたにとっての問題は、すべてのJARファイルと本当にルーズなファイル(ディレクトリ)を調べる必要があることです。同じことを.NETで行う必要があります(ある種類またはさまざまな種類のPATHを検索します)。
Kevin Brock、2011年

回答:


156

私はorg.reflectionsを使用しています

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);

もう一つの例:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}

6
org.reflectionsに関するリンクをありがとう。これが.Netの世界と同じくらい簡単であると誤って考えました。この回答により、多くの時間を節約できました。
akmad 2012年

1
すばらしいパッケージ「org.reflections」へのこの役立つリンクを本当にありがとう!それで私はようやく私の問題の実用的できちんとした解決策を見つけました。
Hartmut P.

3
すべてのリフレクションを取得する方法はありますか。つまり、特定のパッケージに注意せずに、使用可能なすべてのクラスをロードしますか?
Joey Baruch

クラスを見つけても、インスタンス化の問題(animals.add( new c() );コードの5行目)は解決されないことに注意してください。これはClass.newInstance()存在しますが、特定のクラスにパラメーターのないコンストラクターがあることを保証できないためです(C#ではジェネリックで要求することができます)
usr-local-ΕΨΗΕΛΩΝ2018年

ClassGraphも参照してください:github.com/classgraph/classgraph(免責事項、私は著者です)。別の答えでコード例を示します。
ルークハッチソン

34

Javaが望むことを行う方法は、ServiceLoaderメカニズムを使用することです。

また、多くの人々は、よく知られているクラスパスの場所(つまり/META-INF/services/myplugin.properties)にファイルを置き、ClassLoader.getResources()を使用して、すべてのjarからこの名前のすべてのファイルを列挙することで、独自のロールバックを行います。これにより、各jarが独自のプロバイダーをエクスポートできるようになり、Class.forName()を使用してリフレクションによってそれらをインスタンス化できます


1
簡単にクラスに注釈をもとにMETA-INFサービス・ファイルを生成するために、次のことが確認できます。metainf-services.kohsuke.orgまたはcode.google.com/p/spi
エレク

ブレア。複雑さのレベルを追加するだけですが、クラスを作成して遠く離れた土地に登録する必要をなくすことはできません。
kevin cline 2013年

はるかに優れた26 upvotes、と下の答えを見てください
SobiborTreblinka

4
実際、ある種の実用的なソリューションが必要な場合は、Reflections / Scannotationsが適切なオプションですが、どちらも外部依存関係を課し、非決定的であり、Java Community Processではサポートされていません。さらに、いつでも薄い空気から新しいクラスを製造するのは簡単であるため、インターフェイスを実装するすべてのクラスを確実に取得することはできないことを強調しておきたいと思います。すべてのいぼについて、Javaのサービスローダーは理解しやすく使いやすいです。私は様々な理由のためにそれから離れて滞在するが、その後、クラスパスのスキャンはさらに悪くなる。stackoverflow.com/a/7237152/18187
ddimitrov

11

これをアスペクト指向の観点から考えてください。あなたが実際にしたいことは、実行時にAnimalクラスを拡張したすべてのクラスを知っていることです。(私はそれがタイトルよりも少し正確な問題の説明だと思います。それ以外の場合は、実行時の質問はないと思います。)

だから私があなたが望むのは、インスタンス化されている現在のクラスのタイプを静的配列に追加する基本クラス(動物)のコンストラクターを作成することです(私はArrayLists、自分自身が好きですが、それぞれ独自のものです)。

だいたい、

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

もちろん、instantiatedDerivedClassを初期化するには、Animalの静的コンストラクターが必要になります...これはおそらくあなたが望むことを行うと思います。これは実行パスに依存することに注意してください。決して呼び出されないAnimalから派生するDogクラスがある場合、Animal Classリストには含まれません。


6

残念ながら、ClassLoaderは使用可能なクラスを通知しないため、これは完全に可能ではありません。ただし、次のようなことを行うと、かなり近づくことができます。

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);

        JarInputStream is = new JarInputStream(new FileInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                //   for implementation of the interface
            }
        }
    }
}

編集: johnstokは正解です(コメント内)。これはスタンドアロンのJavaアプリケーションでのみ機能し、アプリケーションサーバーでは機能しません。


これは、たとえばWebまたはJ2EEコンテナなど、他の方法でクラスがロードされている場合はうまく機能しません。
johnstok 2008年

URL[]ClassLoader階層からパスを照会した場合(多くの場合、常にではないため、不可能である可能性があります)、コンテナーの下でも、おそらくより良い仕事をすることができます。通常SecurityManager、JVM内にロードされているため、権限が必要な場合がよくあります。
Kevin Brock、2011年

私の魅力のように働いた!これは重すぎて遅くなり、クラスパスのすべてのクラスを通過するのではないかと心配しましたが、実際には、チェックするファイルを「-ejb-」またはその他の認識可能なファイル名を含むファイルに限定しましたが、起動よりも100倍高速です。埋め込まれたGlassfishまたはEJBContainer。次に、特定の状況で、「ステートレス」、「ステートフル」、および「シングルトン」アノテーションを探すクラスを分析し、それらを見つけた場合は、JNDIマッピング名をMockInitialContextFactoryに追加しました。再度、感謝します!
cs94njw 2013年

4

既存のコードをリファクタリングせずにシンプルで迅速なものが必要な場合は、Stripes FrameworkのResolverUtilrawソース)を使用できます。

以下は、クラスをロードしていない簡単な例です。

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

これは、アプリケーションサーバーでも機能するように設計されているため、アプリケーションサーバーでも機能します。

コードは基本的に次のことを行います。

  • 指定したパッケージ内のすべてのリソースを反復処理します
  • .classで終わるリソースのみを保持する
  • これらのクラスを使用してロードします ClassLoader#loadClass(String fullyQualifiedName)
  • かどうかを確認します Animal.class.isAssignableFrom(loadedClass);

4

特定のクラスのすべてのサブクラスをリストするための最も堅牢なメカニズムは、新しいJPMSモジュールシステムを含むクラスパス指定メカニズムの可能な限り幅広い配列を処理するため、現在はClassGraphです。(私は著者です。)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

結果はタイプList <Class <Animal >>です
hiaclibe

2

Javaはクラスを動的にロードするため、クラスのユニバースはすでにロードされている(まだアンロードされていない)クラスのみになります。おそらく、ロードされた各クラスのスーパータイプをチェックできるカスタムクラスローダーで何かを実行できます。ロードされたクラスのセットをクエリするAPIはないと思います。


2

これを使って

public static Set<Class> getExtendedClasses(Class superClass)
{
    try
    {
        ResolverUtil resolver = new ResolverUtil();
        resolver.findImplementations(superClass, superClass.getPackage().getName());
        return resolver.getClasses();  
    }
    catch(Exception e)
    {Log.d("Log:", " Err: getExtendedClasses() ");}

    return null;
}

getExtendedClasses(Animals.class);

編集:


2
ResolverUtilについてもう少し説明する必要があります。それは何ですか?それはどのライブラリからのものですか?
JohnnyLambada

1

この質問に答えたすべての人に感謝します。

これは確かにクラックするのが難しいナットのようです。結局、私のベースクラスに静的配列とゲッターをあきらめて作成してしまいました。

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

JavaはC#のように自己発見できるように設定されていないようです。問題は、Javaアプリが.classファイルのコレクションであり、どこかディレクトリ/ jarファイルにあるため、ランタイムはクラスが参照されるまでクラスを認識しないということです。その時、ローダーはそれをロードします-私がしようとしていることは、それを参照する前にそれを発見することです。

私は自分自身について語る代わりに、自分自身を発見できるコードがいつも好きですが、悲しいかな、これもうまくいきます。

再度、感謝します!


1
Javaは自己発見用にセットアップされています。あなたはもう少し仕事をする必要があるだけです。詳細については私の回答を参照してください。
Dave Jarvis

1

使用OpenPojo次の操作を行うことができます。

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}

0

これは難しい問題であり、静的分析を使用してこの情報を見つける必要がありますが、実行時には簡単に利用できません。基本的に、アプリのクラスパスを取得し、利用可能なクラスをスキャンして、継承元のクラスのバイトコード情報を読み取ります。クラスDogは、Animalから直接継承するのではなく、Petから継承する可能性があることに注意してください。Petは、Animalから継承されます。したがって、その階層を追跡する必要があります。


0

1つの方法は、クラスに静的初期化子を使用させることです...これらは継承されているとは思われません(継承されている場合は機能しません)。

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

関係するすべてのクラスにこのコードを追加する必要があります。しかし、どこかに大きな醜いループができるのを避け、すべてのクラスをテストして、Animalの子を検索します。


3
これは私の場合は機能しません。クラスはどこからも参照されないため、ロードされないため、静的初期化子が呼び出されることはありません。静的イニシャライザは、クラスがロードされるときに他のすべての前に呼び出されるようですが、私の場合、クラスはロードされません。
JohnnyLambada 2008年

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