Как найти классы при запуске исполняемого файла jar - PullRequest
1 голос
/ 30 июня 2009

Я запускаю исполняемый файл jar и хочу найти список классов внутри файла jar, чтобы я мог во время выполнения решить, какой запускать. Возможно, я не знаю имя файла jar, поэтому не могу его разархивировать

Ответы [ 5 ]

1 голос
/ 01 июля 2009

Вы не можете перечислять классы из пакета jar, используя Reflection API. Это также разъясняется в смежных вопросах как можно перечислить все классы в пакете и можно-я-список-The-ресурсы-в-заданном-пакет . Однажды я написал инструмент, в котором перечислены все классы, найденные в определенном пути к классам. Слишком долго вставлять сюда, но вот общий подход:

  1. найти используемый путь к классам. Это хорошо показано Эйрикмой в другом ответе.

  2. добавить другие места, где ClassLoader может искать классы, например, bootclasspath, одобренный lib в 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 и открытие файлов JAR с помощью 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. Теперь вы можете использовать Reflection, чтобы загрузить этот класс и взглянуть на него. Если вы просто хотите узнать кое-что о классе, вы можете загрузить его без разрешения или использовать библиотеку разработки байт-кода, такую ​​как BCEL , чтобы открыть класс без загрузки его в JVM.

    • ClassLoader.getSystemClassLoader().loadClass(name).getModifiers() & Modifier.PUBLIC
1 голос
/ 30 июня 2009

Я не уверен, есть ли способ перечислить все классы, видимые текущему загрузчику классов.

Не имея этого, вы могли бы

а) попытаться узнать имя файла jar из пути к классам, а затем посмотреть его содержимое.

или

b) предположим, что у вас есть список классов, которые вы ищете, попробуйте каждый из них с помощью Class.forName ().

0 голосов
/ 30 июня 2009

Я бы использовал библиотеку инспекторов байт-кода, например 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 не станет мусором) взысканных).

0 голосов
/ 30 июня 2009

Вы можете получить фактический путь к классу из загрузчика классов. это должно включать файл jar, иначе программа не запустится. Просмотрите URL-адреса пути к классам, чтобы найти URL, заканчивающийся на «.jar» и содержащий что-то, что никогда не меняется в имени вашего файла 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());

Удачи!

0 голосов
/ 30 июня 2009

вы можете использовать простую программу, чтобы получить список всех файлов классов из jar и выгрузить его в файл свойств во время выполнения, а затем в вашей программе вы можете загрузить req. класс как и когда требуется; без использования отражений.

...