Чтение манифеста моей собственной банки


135

Мне нужно прочитать Manifestфайл, в котором был доставлен мой класс, но когда я использую:

getClass().getClassLoader().getResources(...)

Я получаю MANIFESTиз первого .jarзагруженного в Java Runtime.
Мое приложение будет запускаться из апплета или веб-старта,
поэтому я полагаю, что у меня не будет доступа к моему собственному .jarфайлу.

Я действительно хочу прочитать Export-packageатрибут из .jarфайла, который запустил Felix OSGi, чтобы я мог предоставить эти пакеты Феликсу. Любые идеи?


3
Я думаю, что ответ FrameworkUtil.getBundle () ниже - лучший. Он отвечает на то, что вы действительно хотите сделать (получить экспорт пакета), а не на то, что вы просили (прочтите манифест).
— Крис Долан,

Ответы:


117

Вы можете сделать одну из двух вещей:

  1. Вызовите getResources()и переберите возвращенную коллекцию URL-адресов, считывая их как манифесты, пока не найдете свой:

    Enumeration<URL> resources = getClass().getClassLoader()
      .getResources("META-INF/MANIFEST.MF");
    while (resources.hasMoreElements()) {
        try {
          Manifest manifest = new Manifest(resources.nextElement().openStream());
          // check that this is your manifest and do what you need or get the next one
          ...
        } catch (IOException E) {
          // handle
        }
    }
  2. Вы можете попробовать проверить, getClass().getClassLoader()является ли он экземпляром java.net.URLClassLoader. Большинство загрузчиков классов Sun, в том числе AppletClassLoader. Затем вы можете преобразовать его и вызвать findResource(), по крайней мере, для апплетов, чтобы напрямую вернуть необходимый манифест:

    URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
    try {
      URL url = cl.findResource("META-INF/MANIFEST.MF");
      Manifest manifest = new Manifest(url.openStream());
      // do stuff with it
      ...
    } catch (IOException E) {
      // handle
    }

5
Отлично! Я никогда не знал, что можно перебирать ресурсы с одинаковыми именами.
— Houtman

Откуда вы знаете, что загрузчик классов знает только об одном файле .jar? (я полагаю, что это правда во многих случаях) Я бы предпочел использовать что-то, связанное непосредственно с рассматриваемым классом.
— Jason S

7
это хорошая практика , чтобы сделать отдельные ответы для каждого из них, а в том числе 2 исправления в одном ответе. Отдельные ответы можно проголосовать независимо.
— Альба Мендес,

просто примечание: мне нужно что-то подобное, но я нахожусь в WAR на JBoss, поэтому второй подход у меня не сработал. Я закончил с вариантом stackoverflow.com/a/1283496/160799
— Грегор

1
Первый вариант мне не подошел. Я получил манифесты своих 62 jar-файлов зависимостей, но не тот, в котором был определен текущий класс ...
— Джолта

120

Сначала вы можете найти URL-адрес вашего класса. Если это JAR, вы загружаете манифест оттуда. Например,

Class clazz = MyClass.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
  // Class not from JAR
  return;
}
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + 
    "/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");

Мне нравится это решение, так как оно напрямую получает ваш собственный манифест, а не ищет его.
— Джей,

1
можно немного улучшить, убрав проверку условийclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
— Джей

2
Кто закрывает поток?
— откр 03

1
Это не работает во внутренних классах, потому что getSimpleNameудаляет имя внешнего класса. Это будет работать для внутренних классов: clazz.getName().replace (".", "/") + ".class".
— откр 03

3
Вам нужно закрыть поток, конструктор манифеста - нет.
— BrianT.

21

Вы можете использовать Manifestsfrom jcabi-manifestests и прочитать любой атрибут из любого из доступных файлов MANIFEST.MF, используя всего одну строку:

String value = Manifests.read("My-Attribute");

Единственная нужная вам зависимость:

<dependency>
  <groupId>com.jcabi</groupId>
  <artifactId>jcabi-manifests</artifactId>
  <version>0.7.5</version>
</dependency>

Также см. Это сообщение в блоге для получения более подробной информации: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html


Очень хорошие библиотеки. Есть ли способ контролировать уровень журнала?
— assylias

1
Все библиотеки jcabi регистрируются через SLF4J. Вы можете отправлять сообщения журнала, используя любое средство по вашему желанию, например log4j или logback
— yegor256 01

при использовании logback.xml строка, которую вам нужно добавить, выглядит как<logger name="com.jcabi.manifests" level="OFF"/>
— driftcatcher

1
Несколько манифестов из одного и того же загрузчика классов перекрываются и перезаписывают друг друга
— гуай

13

Я сразу признаю, что этот ответ не отвечает на исходный вопрос о возможности доступа к манифесту. Однако, если на самом деле требуется прочитать один из ряда «стандартных» атрибутов манифеста, следующее решение намного проще, чем те, что опубликованы выше. Так что я надеюсь, что модератор это разрешит. Обратите внимание, что это решение находится на Kotlin, а не на Java, но я ожидал, что перенос на Java будет тривиальным. (Хотя я признаю, что не знаю Java-эквивалента ".`package`".

В моем случае я хотел прочитать атрибут «Версия-реализация», поэтому я начал с решений, приведенных выше, чтобы получить поток, а затем прочитал его, чтобы получить значение. Пока это решение работало, коллега, проверявший мой код, показал мне более простой способ сделать то, что я хотел. Обратите внимание, что это решение находится на Kotlin, а не на Java.

val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion

Еще раз обратите внимание, что это не отвечает на исходный вопрос, в частности, "Export-package" не кажется одним из поддерживаемых атрибутов. Тем не менее, есть myPackage.name, который возвращает значение. Возможно, кто-то, кто понимает это лучше меня, сможет прокомментировать, возвращает ли это значение, запрашиваемое исходным плакатом.


4
Действительно, порт java прост:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
— Ян Робертсон,

На самом деле это то, что я искал. Я также рад, что у Java есть аналог.
— Александр Стельмацонек

12

Я считаю, что наиболее подходящий способ получить манифест для любого пакета (включая пакет, который загрузил данный класс) - использовать объект Bundle или BundleContext.

// If you have a BundleContext
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
bundle.getHeaders();

Обратите внимание, что объект Bundle также предоставляет getEntry(String path) поиск ресурсов, содержащихся в определенном пакете, вместо поиска всего пути к классам этого пакета.

В общем, если вам нужна информация, относящаяся к конкретному пакету, не полагайтесь на предположения о загрузчиках классов, просто используйте API OSGi напрямую.


10

Самый простой способ - использовать класс JarURLConnection:

String className = getClass().getSimpleName() + ".class";
String classPath = getClass().getResource(className).toString();
if (!classPath.startsWith("jar")) {
    return DEFAULT_PROPERTY_VALUE;
}

URL url = new URL(classPath);
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
Manifest manifest = jarConnection.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue(PROPERTY_NAME);

Поскольку в некоторых случаях ...class.getProtectionDomain().getCodeSource().getLocation();дает путь с помощью vfs:/, с этим следует обращаться дополнительно.


Это, безусловно, самый простой и чистый способ сделать это.
— walen 04

9

Следующий код работает с несколькими типами архивов (jar, war) и несколькими типами загрузчиков классов (jar, url, vfs, ...)

  public static Manifest getManifest(Class<?> clz) {
    String resource = "/" + clz.getName().replace(".", "/") + ".class";
    String fullPath = clz.getResource(resource).toString();
    String archivePath = fullPath.substring(0, fullPath.length() - resource.length());
    if (archivePath.endsWith("\\WEB-INF\\classes") || archivePath.endsWith("/WEB-INF/classes")) {
      archivePath = archivePath.substring(0, archivePath.length() - "/WEB-INF/classes".length()); // Required for wars
    }

    try (InputStream input = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) {
      return new Manifest(input);
    } catch (Exception e) {
      throw new RuntimeException("Loading MANIFEST for class " + clz + " failed!", e);
    }
  }

может результат clz.getResource(resource).toString()иметь обратную косую черту?
— бассейн

6

Вы можете использовать getProtectionDomain (). GetCodeSource () следующим образом:

URL url = Menu.class.getProtectionDomain().getCodeSource().getLocation();
File file = DataUtilities.urlToFile(url);
JarFile jar = null;
try {
    jar = new JarFile(file);
    Manifest manifest = jar.getManifest();
    Attributes attributes = manifest.getMainAttributes();
    return attributes.getValue("Built-By");
} finally {
    jar.close();
}

1
getCodeSourceможет вернуться null. По каким критериям это сработает? В документации это не объясняется.
— откр. 03

4
Откуда DataUtilitiesпривозят? Похоже, этого нет в JDK.
— Jolta

2

Почему вы включаете шаг getClassLoader? Если вы скажете this.getClass (). GetResource (), вы должны получать ресурсы относительно вызывающего класса. Я никогда не использовал ClassLoader.getResource (), хотя при беглом взгляде на документацию Java кажется, что вы получите первый ресурс с таким именем, найденный в любом текущем пути к классам.


Если ваш класс назван "com.mypackage.MyClass", при вызове class.getResource("myresource.txt")будет предпринята попытка загрузить этот ресурс из com/mypackage/myresource.txt. Как именно вы собираетесь использовать этот подход для получения манифеста?
— ChssPly76

1
Ладно, мне нужно отступить. Вот что получается из-за отсутствия тестирования. Я думал, что вы могли бы сказать this.getClass (). GetResource ("../../ META-INF / MANIFEST.MF") (Однако, учитывая имя вашего пакета, необходимо много ".."). Но пока это работает для файлов классов в каталоге, чтобы продвигаться вверх по дереву каталогов, по-видимому, он не работает для JAR. Не понимаю, почему нет, но так оно и есть. Также не работает this.getClass (). GetResource ("/ META-INF / MANIFEST.MF") - это дает мне манифест для rt.jar. (Продолжение следует ...)
— Джей

Что вы можете сделать, так это использовать getResource, чтобы найти путь к вашему собственному файлу класса, а затем удалить все после символа "!" чтобы получить путь к банке, добавьте "/META-INF/MANIFEST.MF". Как и предложил Чжихун, я голосую за него.
— Джей,

1
  public static Manifest getManifest( Class<?> cl ) {
    InputStream inputStream = null;
    try {
      URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
      String classFilePath = cl.getName().replace('.','/')+".class";
      URL classUrl = classLoader.getResource(classFilePath);
      if ( classUrl==null ) return null;
      String classUri = classUrl.toString();
      if ( !classUri.startsWith("jar:") ) return null;
      int separatorIndex = classUri.lastIndexOf('!');
      if ( separatorIndex<=0 ) return null;
      String manifestUri = classUri.substring(0,separatorIndex+2)+"META-INF/MANIFEST.MF";
      URL url = new URL(manifestUri);
      inputStream = url.openStream();
      return new Manifest( inputStream );
    } catch ( Throwable e ) {
      // handle errors
      ...
      return null;
    } finally {
      if ( inputStream!=null ) {
        try {
          inputStream.close();
        } catch ( Throwable e ) {
          // ignore
        }
      }
    }
  }

В этом ответе используется очень сложный и подверженный ошибкам способ загрузки манифеста. гораздо более простое решение - использовать cl.getResourceAsStream("META-INF/MANIFEST.MF").
— Роберт

Вы пробовали это? Какой манифест jar он получит, если у вас есть несколько jar-файлов в пути к классам? Потребуется первая, которая вам не нужна. Мой код решает эту проблему, и он действительно работает.
— Alex Konshin

Я не критиковал то, как вы использовали загрузчик классов для загрузки определенного ресурса. Я указывал, что весь код между classLoader.getResource(..)и url.openStream()совершенно неуместен и подвержен ошибкам, поскольку он пытается делать то же самое, что и classLoader.getResourceAsStream(..)делает.
— Роберт

Нет. Это отличается. Мой код берет манифест из конкретной банки, в которой расположен класс, а не из первой банки в пути к классам.
— Alex Konshin

Ваш «код загрузки для конкретной банки» эквивалентен следующим двум строкам:ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
— Роберт

0

Я использовал решение Anthony Juckel, но в MANIFEST.MF ключ должен начинаться с верхнего регистра.

Итак, мой файл MANIFEST.MF содержит такой ключ, как:

Mykey: значение

Затем в активаторе или другом классе вы можете использовать код Энтони для чтения файла MANIFEST.MF и нужного вам значения.

// If you have a BundleContext 
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2 
Bundle bundle = `FrameworkUtil.getBundle(this.getClass()); 
bundle.getHeaders();

0

У меня есть это странное решение, которое запускает военные приложения на встроенном сервере Jetty, но эти приложения также необходимо запускать на стандартных серверах Tomcat, и у нас есть некоторые специальные свойства в manfest.

Проблема заключалась в том, что в Tomcat манифест можно было прочитать, но когда он был на пристани, был выбран случайный манифест (в котором пропущены специальные свойства)

Основываясь на ответе Алекса Коншина, я придумал следующее решение (входной поток затем используется в классе Manifest):

private static InputStream getWarManifestInputStreamFromClassJar(Class<?> cl ) {
    InputStream inputStream = null;
    try {
        URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
        String classFilePath = cl.getName().replace('.','/')+".class";
        URL classUrl = classLoader.getResource(classFilePath);
        if ( classUrl==null ) return null;
        String classUri = classUrl.toString();
        if ( !classUri.startsWith("jar:") ) return null;
        int separatorIndex = classUri.lastIndexOf('!');
        if ( separatorIndex<=0 ) return null;
        String jarManifestUri = classUri.substring(0,separatorIndex+2);
        String containingWarManifestUri = jarManifestUri.substring(0,jarManifestUri.indexOf("WEB-INF")).replace("jar:file:/","file:///") + MANIFEST_FILE_PATH;
        URL url = new URL(containingWarManifestUri);
        inputStream = url.openStream();
        return inputStream;
    } catch ( Throwable e ) {
        // handle errors
        LOGGER.warn("No manifest file found in war file",e);
        return null;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.