commands
すべてが継承するクラスを含むJava パッケージがあるとしICommand
ます。これらのクラスをすべてどうにかして取得できますか? 次の行の中で何かをロックしています:
Package p = Package.getPackage("commands");
Class<ICommand>[] c = p.getAllPackagedClasses(); //not real
そのようなことは可能ですか?
commands
すべてが継承するクラスを含むJava パッケージがあるとしICommand
ます。これらのクラスをすべてどうにかして取得できますか? 次の行の中で何かをロックしています:
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$", "");
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;
}
public Classloader.getResources(String name) から始めます。関心のあるパッケージ内の各名前に対応するクラスをクラスローダーに問い合わせます。関連するすべてのクラスローダーについて繰り返します。
これは私たちが必要としている非常に便利なツールであり、JDK は何らかのサポートを提供するはずです。
しかし、おそらくビルド中に行う方がよいでしょう。すべてのクラス ファイルの場所がわかっているので、それらを静的に調べてグラフを作成できます。実行時にこのグラフをクエリして、すべてのサブタイプを取得できます。これにはさらに多くの作業が必要ですが、それは本当にビルド プロセスに属していると思います。
はい、しかしそれは最も簡単なことではありません。これには多くの問題があります。すべてのクラスが簡単に見つかるわけではありません。一部のクラスは、Jar、クラス ファイルとして、ネットワーク上などにある可能性があります。
このスレッドを見てください。
それらが ICommand タイプであることを確認するには、リフレクションを使用して継承クラスをチェックする必要があります。
OpenPojoを使用してこれを行うことができます。
final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null);
次に、リストを調べて、必要な機能を実行できます。
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 が発生する可能性があります。