1

我正在运行一个可执行的 jar,并希望在 jar 中找到一个类列表,以便我可以在运行时决定运行哪个。我可能不知道 jar 文件的名称,所以无法解压缩它

4

5 回答 5

1

我不确定是否有办法列出当前类加载器可见的所有类。

缺少这个,你可以

a) 尝试从类路径中找出 jar 文件的名称,然后查看其内容。

或者

b) 假设您有一个正在寻找的类的候选列表,请使用 Class.forName() 尝试每个类。

于 2009-06-30T10:10:32.480 回答
1

您不能使用反射 API 从 jar 包中枚举类。这在相关问题how-can-i-enumerate-all-classes-in-a-packagecan-i-list-the-resources-in-a-given-package中也很清楚。我曾经写过一个工具,它列出了在某个类路径中找到的所有类。在这里粘贴太长了,但这里是一般的方法:

  1. 找到使用的类路径。eirikma 在另一个答案中很好地展示了这一点。

  2. 添加 ClassLoader 可能搜索类的其他位置,例如 bootclasspath、JRE 中的认可库等。(如果您只有一个简单的应用程序,那么 1 + 2 很容易,只需从属性中获取类路径。)

    • readAllFromSystemClassPath("sun.boot.class.path");
    • readAllFromSystemClassPath("java.endorsed.dirs");
    • readAllFromSystemClassPath("java.ext.dirs");
    • readAllFromSystemClassPath("java.class.path");
  3. 从 JAR 中扫描类路径和拆分文件夹。

    • StringTokenizer pathTokenizer = new StringTokenizer(pPath, File.pathSeparator);
  4. 使用 扫描文件夹File.listFiles并使用ZipFile.entries. 注意内部类和包访问类,你可能不想要它们。

    • isInner = (pClassName.indexOf('$') > -1);
  5. 将 JAR 中的文件名或路径转换为正确的类名 (/ -> .)

    • final int i = fullName.lastIndexOf(File.separatorChar);
    • path = fullName.substring(0, i).replace(File.separatorChar, '.');
    • name = fullName.substring(i + 1);
  6. 现在您可以使用反射来加载该类并查看它。如果您只是想了解有关该类的信息,您可以在不解析的情况下加载它,或者使用像BCEL这样​​的字节码工程库来打开该类而不将其加载到 JVM 中。

    • ClassLoader.getSystemClassLoader().loadClass(name).getModifiers() & Modifier.PUBLIC
于 2009-06-30T21:22:16.327 回答
0

您可以使用一个简单的程序从 jar 中获取所有类文件的列表,并在运行时将其转储到属性文件中,然后在您的程序中加载 req. 按要求上课;不使用反射。

于 2009-06-30T10:20:51.633 回答
0

您可以从类加载器中获取实际的类路径。这必须包含 jar 文件,否则程序将无法运行。通过类路径 URL 查找以“.jar”结尾的 URL,并且包含在您的 jar 文件名称中永远不会更改的内容(最好在最后一个“/”之后)。之后,您将其作为常规 jar(或 zip)文件打开并阅读内容。

有几种方法可用于获取类路径。它们都不适用于所有环境和每种设置,因此您必须逐个尝试它们,直到找到一种适用于您需要它工作的所有情况的方法。此外,有时您可能需要调整运行时上下文,例如(经常需要)将 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());

祝你好运!

于 2009-06-30T10:26:06.257 回答
0

我会使用像ASM这样的字节码检查器库。这个 ClassVisitor 可用于查找 main 方法:

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 被垃圾收集)。

于 2009-06-30T10:51:45.863 回答