私は実行可能な jar を実行しており、実行時にどちらを実行するかを決定できるように、jar 内のクラスのリストを見つけたいと考えています。jarファイルの名前がわからないため、解凍できない可能性があります
5 に答える
Reflection APIを使用して、jarのパッケージからクラスを列挙することはできません。これは、関連する質問のhow-can-i-enumerate-all-classes-in-a-packageおよび can-i-list-the-resources-in-a-given-packageでも明らかにされています。私はかつて、特定のクラスパスで見つかったすべてのクラスを一覧表示するツールを作成しました。ここに貼り付けるには長すぎますが、一般的なアプローチは次のとおりです。
使用されているクラスパスを見つけます。これは、別の回答でeirikmaによってうまく示されています。
ClassLoaderがクラスを検索する可能性のある他の場所を追加します。たとえば、bootclasspath、JREで承認されたlibなどです(単純なアプリがある場合は、1 + 2が簡単です。プロパティからクラスパスを取得するだけです)。
readAllFromSystemClassPath("sun.boot.class.path");
readAllFromSystemClassPath("java.endorsed.dirs");
readAllFromSystemClassPath("java.ext.dirs");
readAllFromSystemClassPath("java.class.path");
クラスパスをスキャンし、JARからフォルダーを分割します。
StringTokenizer pathTokenizer = new StringTokenizer(pPath, File.pathSeparator);
でフォルダをスキャンし
File.listFiles
、でJARを開きますZipFile.entries
。内部クラスとパッケージアクセスクラスに注意してください。おそらくそれらは必要ありません。isInner = (pClassName.indexOf('$') > -1);
JAR内のファイル名またはパスを適切なクラス名に変換します(/->。)
final int i = fullName.lastIndexOf(File.separatorChar);
path = fullName.substring(0, i).replace(File.separatorChar, '.');
name = fullName.substring(i + 1);
これで、Reflectionを使用してそのクラスをロードし、調べることができます。クラスについて知りたいだけの場合は、解決せずにロードするか、BCELなどのバイトコードエンジニアリングライブラリを使用して、JVMにロードせずにクラスを開くことができます。
ClassLoader.getSystemClassLoader().loadClass(name).getModifiers() & Modifier.PUBLIC
現在のクラスローダーに表示されるすべてのクラスを一覧表示する方法があるかどうかはわかりません。
それが欠けていると、
a)クラスパスからjarファイルの名前を見つけて、その内容を確認します。
また
b)探しているクラスの候補リストがあると仮定して、Class.forName()でそれぞれを試してください。
ASMのようなバイトコードインスペクターライブラリを使用します。このClassVisitorは、メインメソッドを探すために使用できます。
import org.objectweb.asm.*;
import org.objectweb.asm.commons.EmptyVisitor;
public class MainFinder extends ClassAdapter {
private String name;
private boolean isMainClass;
public MainFinder() {
super(new EmptyVisitor());
}
@Override
public void visit(int version, int access, String name,
String signature, String superName,
String[] interfaces) {
this.name = name;
super.visit(version, access, name, signature,
superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if ((access & Opcodes.ACC_PUBLIC) != 0
&& (access & Opcodes.ACC_STATIC) != 0
&& "main".equals(name)
&& "([Ljava/lang/String;)V".equals(desc)) {
isMainClass = true;
}
return super.visitMethod(access, name, desc, signature,
exceptions);
}
public String getName() {
return name;
}
public boolean isMainClass() {
return isMainClass;
}
}
クラスがパブリックであることを確認するためにコードを変更したい場合があることに注意してください。
このサンプルアプリは、コマンドラインで指定されたJARで上記のクラスを使用します。
import java.io.*;
import java.util.Enumeration;
import java.util.jar.*;
import org.objectweb.asm.ClassReader;
public class FindMainMethods {
private static void walk(JarFile jar) throws IOException {
Enumeration<? extends JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
MainFinder visitor = new MainFinder();
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) {
continue;
}
InputStream stream = jar.getInputStream(entry);
try {
ClassReader reader = new ClassReader(stream);
reader.accept(visitor, ClassReader.SKIP_CODE);
if (visitor.isMainClass()) {
System.out.println(visitor.getName());
}
} finally {
stream.close();
}
}
}
public static void main(String[] args) throws IOException {
JarFile jar = new JarFile(args[0]);
walk(jar);
}
}
「java.class.path」システムプロパティも確認することをお勧めします。
System.getProperty("java.class.path");
リフレクションを使用して同様の結果を取得することは可能ですが、そのアプローチには、静的初期化子を実行させたり、未使用のクラスをメモリに保持したりするなど、いくつかの不幸な副作用が発生する可能性があります(ClassLoaderがガベージコレクションされるまでロードされたままになる可能性があります)。
簡単なプログラムを使用してjarからすべてのクラスファイルのリストを取得し、実行時にプロパティファイルにダンプしてから、プログラムでreqをロードできます。必要に応じてクラス。反射を使用せずに。
クラスローダーから実際のクラスパスを取得できます。これには jar ファイルを含める必要があります。そうしないと、プログラムが実行されません。クラスパスの URL を調べて、".jar" で終わり、jar ファイルの名前 (できれば最後の "/" の後) に決して変更されないものを含む URL を見つけます。その後、通常の jar (または zip) ファイルとして開き、内容を読み取ります。
クラスパスを取得する方法はいくつかあります。それらのどれもすべてのコンテキストおよびすべてのセットアップで機能するわけではないため、必要なすべての状況で機能するものを見つけるまで、1 つずつ試してみる必要があります。また、(しばしば必要とされる) maven Surefire-plugin のクラスローディング メカニズムをオプションの (デフォルトではない) メカニズムのいずれかに置き換えるなど、ランタイム コンテキストを微調整する必要がある場合もあります。
クラスパス 1 の取得: システム プロパティから:
static String[] getClasspathFromProperty() {
return System.getProperty("java.class.path").split(File.pathSeparator);
}
クラスパス 2 の取得: クラスローダーから (maven 警告あり):
String[] getClasspathFromClassloader() {
URLClassLoader classLoader = (URLClassLoader) (getClass().getClassLoader());
URL[] classpath = classLoader.getURLs();
if (classpath.length == 1
&& classpath[0].toExternalForm().indexOf("surefirebooter") >= 0)
{
// todo: read classpath from manifest in surefireXXXX.jar
System.err.println("NO PROPER CLASSLOADER HERE!");
System.err.println(
"Run maven with -Dsurefire.useSystemClassLoader=false "
+"-Dsurefire.useManifestOnlyJar=false to enable proper classloaders");
return null;
}
String[] classpathLocations = new String[classpath.length];
for (int i = 0; i < classpath.length; i++) {
// you must repair the path strings: "\.\" => "/" etc.
classpathLocations[i] = cleanClasspathUrl(classpath[i].toExternalform());
}
return classpathLocations;
}
クラスパスの取得 3: 現在のスレッド コンテキストから: これはメソッド 2 と似ていますが、メソッドの最初の行を次のように読む必要があります。
URLClassLoader classLoader
= (URLClassLoader)(Thread.currentThread().getContextClassLoader());
幸運を!