Чтение моего собственного манифеста Jar

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

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

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

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

Ответ 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
        }
    }
    
  • Вы можете проверить, является ли 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
    }
    

Ответ 2

Сначала вы можете найти 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");

Ответ 3

Вы можете использовать Manifests из 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

Ответ 4

Я считаю, что наиболее подходящим способом получить манифест для любого пакета (включая пакет, который загружал данный класс), является использование объекта 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 напрямую.

Ответ 5

Следующий код работает с несколькими типами архивов (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);
    }
  }

Ответ 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();
}

Ответ 7

Самый простой способ - использовать класс 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:/, поэтому это нужно обработать дополнительно.

Ответ 8

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

Ответ 9

  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
        }
      }
    }
  }

Ответ 10

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

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

Значение Mykey:

Затем в активаторе или другом классе вы можете использовать код от Anthony, чтобы прочитать файл 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();

Ответ 11

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

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

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

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

Ответ 12

У меня есть это странное решение, которое запускает военные приложения на встроенном сервере 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;
    }
}