質問

ICommand をすべて継承するクラスを含むJavaパッケージ commands があるとしましょう。これらのクラスをすべて何らかの方法で取得できますか?次の行の中で何かをロックしています:

Package p = Package.getPackage("commands");
Class<ICommand>[] c = p.getAllPackagedClasses(); //not real 

そのようなことは可能ですか?

役に立ちましたか?

解決

クラスがJARパッケージ化されていないことを前提とした基本的な例を次に示します。

// Prepare.
String packageName = "com.example.commands";
List<Class<ICommand>> commands = new ArrayList<Class<ICommand>>();
URL root = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/"));

// Filter .class files.
File[] files = new File(root.getFile()).listFiles(new FilenameFilter() {
    public boolean accept(File dir, String name) {
        return name.endsWith(".class");
    }
});

// Find classes implementing ICommand.
for (File file : files) {
    String className = file.getName().replaceAll(".class<*>quot;, "");
    Class<?> cls = Class.forName(packageName + "." + className);
    if (ICommand.class.isAssignableFrom(cls)) {
        commands.add((Class<ICommand>) cls);
    }
}

他のヒント

以下、JSR-199 APIを使用した実装、つまり javax.tools。* のクラス:

List<Class> commands = new ArrayList<Class>();

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
        null, null, null);

Location location = StandardLocation.CLASS_PATH;
String packageName = "commands";
Set<JavaFileObject.Kind> kinds = new HashSet<JavaFileObject.Kind>();
kinds.add(JavaFileObject.Kind.CLASS);
boolean recurse = false;

Iterable<JavaFileObject> list = fileManager.list(location, packageName,
        kinds, recurse);

for (JavaFileObject javaFileObject : list) {
    commands.add(javaFileObject.getClass());
}

これは、Springを使用したユーティリティメソッドです。

パターンの詳細については、こちら

    public static List<Class> listMatchingClasses(String matchPattern) throws IOException {
    List<Class> classes = new LinkedList<Class>();
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(matchPattern);

    for (Resource resource : resources) {
        Class<?> clazz = getClassFromResource(resource);
        classes.add(clazz);
    }

    return classes;
}



public static Class getClassFromResource(Resource resource) {
    try {
        String resourceUri = resource.getURI().toString();
        resourceUri = resourceUri.replace(esourceUri.indexOf(".class"), "").replace("/", ".");
        // try printing the resourceUri before calling forName, to see if it is OK.
        return Class.forName(resourceUri);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

パブリックClassloader.getResources(String name)で開始します。興味のあるパッケージの各名前に対応するクラスをクラスローダーに尋ねます。関連するすべてのクラスローダーについて繰り返します。

はい、しかしそれは最も簡単なことではありません。これには多くの問題があります。すべてのクラスが簡単に見つかるわけではありません。一部のクラスは、JAR、クラスファイルとして、ネットワーク経由などで使用できます。

このスレッドを見てください。

それらがICommand型であることを確認するには、リフレクションを使用して継承クラスを確認する必要があります。

これは私たちが必要とする非常に便利なツールであり、JDKは何らかのサポートを提供する必要があります。

しかし、おそらくビルド中に行う方が良いでしょう。すべてのクラスファイルの場所がわかっているため、静的に検査してグラフを作成できます。実行時に、このグラフを照会してすべてのサブタイプを取得できます。これにはより多くの作業が必要ですが、実際にはビルドプロセスに属していると思います。

Johannes LinkのClasspathSuite を使用して、次のように実行できました。

import org.junit.extensions.cpsuite.ClassTester;
import org.junit.extensions.cpsuite.ClasspathClassesFinder;

public static List<Class<?>> getClasses(final Package pkg, final boolean includeChildPackages) {
    return new ClasspathClassesFinder(new ClassTester() {
        @Override public boolean searchInJars() { return true; }
        @Override public boolean acceptInnerClass() { return false; }
        @Override public boolean acceptClassName(String name) {
            return name.startsWith(pkg.getName()) && (includeChildPackages || name.indexOf(".", pkg.getName().length()) != -1);
        }
        @Override public boolean acceptClass(Class<?> c) { return true; }
    }, System.getProperty("java.class.path")).find();
}

ClasspathClassesFinderは、システムクラスパスでクラスファイルとjarを探します。

特定のケースでは、acceptClassを次のように変更できます。

@Override public boolean acceptClass(Class<?> c) {
    return ICommand.class.isAssignableFrom(c);
}

注意すべき点が1つあります。次に、classpathClassesFinderが行うことは、クラスをロードしてacceptClassを呼び出すことなので、acceptClassNameで何を返すかに注意してください。 acceptClassNameが常にtrueを返す場合、クラスパス内のすべてのクラスをロードすることになり、OutOfMemoryErrorが発生する可能性があります。

OpenPojo を使用して、これを実行できます。

final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null);

その後、リストを調べて、必要な機能を実行できます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top