Рубрики
Без рубрики

Получить имена классов внутри файла JAR

Узнайте, как получить имена классов внутри данного JAR-файла и как загрузить классы из JAR-файла во время выполнения.

Автор оригинала: Kai Yuan.

1. Обзор

Большинство библиотек Java доступны в виде JAR-файлов . В этом уроке мы рассмотрим, как получить имена классов внутри данного JAR-файла из командной строки и из программы Java.

Затем мы рассмотрим пример Java-программы, загружающей классы из данного JAR-файла во время выполнения.

2. Пример JAR-файла

В этом уроке мы возьмем stripe-0.0.1-SNAPSHOT.jar файл в качестве примера для решения вопроса о том, как получить имена классов в JAR-файле:

3. Использование команды jar

JDK поставляется с командой jar . Мы можем использовать эту команду с опциями t и f для перечисления содержимого файла JAR :

$ jar tf stripe-0.0.1-SNAPSHOT.jar 
META-INF/
META-INF/MANIFEST.MF
...
templates/result.html
templates/checkout.html
application.properties
com/baeldung/stripe/StripeApplication.class
com/baeldung/stripe/ChargeRequest.class
com/baeldung/stripe/StripeService.class
com/baeldung/stripe/ChargeRequest$Currency.class
...

Поскольку нас интересуют только файлы *.class в архиве, мы можем отфильтровать выходные данные с помощью команды grep :

$ jar tf stripe-0.0.1-SNAPSHOT.jar | grep '\.class$'
com/baeldung/stripe/StripeApplication.class
com/baeldung/stripe/ChargeRequest.class
com/baeldung/stripe/StripeService.class
com/baeldung/stripe/ChargeRequest$Currency.class
com/baeldung/stripe/ChargeController.class
com/baeldung/stripe/CheckoutController.class

Это дает нам список файлов классов внутри файла JAR.

4. Получение имен классов JAR – файла в Java

Использование команды jar для печати имен классов из файла JAR довольно просто. Однако иногда мы хотим загрузить некоторые классы из JAR-файла в нашу Java-программу. В этом случае вывода командной строки недостаточно.

Для достижения нашей цели нам нужно отсканировать JAR – файл из Java – программы и получить имена классов.

Давайте посмотрим, как извлечь имена классов из нашего примера JAR-файла с помощью Jar-файла и JarEntry классов :

public static Set getClassNamesFromJarFile(File givenFile) throws IOException {
    Set classNames = new HashSet<>();
    try (JarFile jarFile = new JarFile(givenFile)) {
        Enumeration e = jarFile.entries();
        while (e.hasMoreElements()) {
            JarEntry jarEntry = e.nextElement();
            if (jarEntry.getName().endsWith(".class")) {
                String className = jarEntry.getName()
                  .replace("/", ".")
                  .replace(".class", "");
                classNames.add(className);
            }
        }
        return classNames;
    }
}

Теперь давайте подробнее рассмотрим код в приведенном выше методе и поймем, как он работает:

  • try (JarFile JarFile(givenFile)) – Здесь мы использовали оператор try-with-resources для получения jarFile из данного Файла объекта
  • if (JarEntry.getName().EndsWith(“.class”)){…} – Мы берем каждый класс JarEntry и меняем путь файла класса на квалифицированное имя класса, например change “package1/package2/SomeType.class” into “package1.package2.SomeType”

Давайте проверим, может ли этот метод извлечь имена классов из нашего примера JAR-файла с помощью метода модульного тестирования:

private static final String JAR_PATH = "example-jar/stripe-0.0.1-SNAPSHOT.jar";
private static final Set EXPECTED_CLASS_NAMES = Sets.newHashSet(
  "com.baeldung.stripe.StripeApplication",
  "com.baeldung.stripe.ChargeRequest",
  "com.baeldung.stripe.StripeService",
  "com.baeldung.stripe.ChargeRequest$Currency",
  "com.baeldung.stripe.ChargeController",
  "com.baeldung.stripe.CheckoutController");

@Test
public void givenJarFilePath_whenLoadClassNames_thenGetClassNames() throws IOException, URISyntaxException {
    File jarFile = new File(
      Objects.requireNonNull(getClass().getClassLoader().getResource(JAR_PATH)).toURI());

    Set classNames = GetClassNamesFromJar.getClassNamesFromJarFile(jarFile);

    Assert.assertEquals(EXPECTED_CLASS_NAMES, classNames);
}

5. Получение классов из JAR-файла в Java

Мы видели, как получить имена классов из файла JAR. Иногда мы хотим динамически загружать некоторые классы из JAR-файла во время выполнения.

В этом случае мы можем сначала получить имена классов из данного JAR-файла с помощью нашего метода getClassNamesFromJarFile .

Далее мы можем создать ClassLoader для загрузки необходимых классов по имени:

public static Set getClassesFromJarFile(File jarFile) throws IOException, ClassNotFoundException {
    Set classNames = getClassNamesFromJarFile(jarFile);
    Set classes = new HashSet<>(classNames.size());
    try (URLClassLoader cl = URLClassLoader.newInstance(
           new URL[] { new URL("jar:file:" + jarFile + "!/") })) {
        for (String name : classNames) {
            Class clazz = cl.loadClass(name); // Load the class by its name
            classes.add(clazz);
        }
    }
    return classes;
}

В приведенном выше методе мы создали объект URLClassLoader для загрузки классов. Реализация довольно проста.

Однако, вероятно, стоит немного объяснить синтаксис URL-адреса JAR. Допустимый URL-адрес JAR содержит три части: “ jar: + [расположение файла JAR] + !/”.

Окончание “ !/ ” указывает на то, что URL-адрес JAR ссылается на весь файл JAR. Давайте посмотрим несколько примеров URL-адресов JAR:

jar:http://www.example.com/some_jar_file.jar!/
jar:file:/local/path/to/some_jar_file.jar!/
jar:file:/C:/windows/path/to/some_jar_file.jar!/

В нашем методе get Classes From Jar File файл JAR находится в локальной файловой системе, поэтому префикс URL-адреса равен ” file: “.

Теперь давайте напишем тестовый метод, чтобы проверить, может ли наш метод получить все ожидаемые объекты Class :

@Test
public void givenJarFilePath_whenLoadClass_thenGetClassObjects()
  throws IOException, ClassNotFoundException, URISyntaxException {
    File jarFile
      = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(JAR_PATH)).toURI());
    Set classes = GetClassNamesFromJar.getClassesFromJarFile(jarFile);
    Set names = classes.stream().map(Class::getName).collect(Collectors.toSet());
    Assert.assertEquals(EXPECTED_CLASS_NAMES, names);
}

Как только у нас есть необходимые объекты Class , мы можем использовать Java reflection для создания экземпляров классов и вызова методов.

6. Заключение

В этой статье мы изучили два различных подхода к получению имен классов из данного JAR-файла.

Команда jar может печатать имена классов. Это довольно удобно, если нам нужно проверить, содержит ли файл JAR данный класс. Однако, если нам нужно получить имена классов из запущенной Java-программы, Jar File и JarEntry могут помочь нам достичь этого.

Наконец, мы также видели пример Java-программы для загрузки классов из JAR-файла во время выполнения.

Как всегда, полный исходный код статьи доступен на GitHub .