Чтение Манифеста моего кувшина

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

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

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

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

12 ответов

Решение

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

  1. Вызов getResources() and iterate through the returned collection of URLs, reading them as manifests until you find yours:

    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. You can try checking whether getClass().getClassLoader() это пример java.net.URLClassLoader, Majority of Sun classloaders are, including AppletClassLoader, You can then cast it and call findResource() which has been known - for applets, at least - to return the needed manifest directly:

    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
    }
    

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

Ты можешь использовать Manifestsиз jcabi-manifest и считайте любой атрибут из любого из доступных файлов 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

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

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

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

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

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

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

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

Более простой способ сделать это - использовать getPackage(). Например, чтобы получить версию реализации:

Application.class.getPackage().getImplementationVersion()

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

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

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

Я использовал решение от Энтони Джакеля, но в 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();

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

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

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

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;
    }
}
Другие вопросы по тегам