Читання мого власного Ярового маніфесту


134

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

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

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

Я насправді хочу прочитати Export-packageатрибут, з .jarякого розпочався Felix OSGi, тому я можу виставити ці пакети Felix. Будь-які ідеї?


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
Ідеально! Я ніколи не знав, що ви можете повторити ресурси з однойменною назвою.
Хаутман

Як ви знаєте, що завантажувач класів знає лише один файл .jar? (правда, у багатьох випадках я думаю, я б швидше використовував щось, пов'язане безпосередньо з відповідним класом).
Jason S

7
це хороша практика , щоб зробити окремі відповіді для кожного з них, а в тому числі 2 виправлення в одній відповіді. Окремі відповіді можна проголосувати самостійно.
Альба Мендес

лише зауваження: мені було потрібно щось подібне, але я всередині ВІЙНИ на JBoss, тому другий підхід не працював для мене. Я закінчив варіант stackoverflow.com/a/1283496/160799
Грегор

1
Перший варіант не працював для мене. Я отримав маніфести 62 своїх залежних банок, але не тих, де визначено поточний клас ...
Jolta

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"
Jay

2
Хто закриває потік?
припинення

1
Це не працює у внутрішніх класах, оскільки getSimpleNameвидаляє зовнішнє ім’я класу. Це буде працювати для внутрішніх класів: clazz.getName().replace (".", "/") + ".class".
припинення

3
Вам потрібно закрити потік, конструктор маніфесту цього не робить.
БрайанТ.

21

Ви можете використовувати Manifestsз jcabi-маніфестів і читати будь-який атрибут з будь-якого з доступних файлів 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 libs входять через SLF4J. Ви можете відправляти повідомлення журналу, використовуючи будь-який об'єкт, який хочете, наприклад log4j або logback
yegor256

якщо ви використовуєте logback.xml, рядок, який потрібно додати, виглядає як<logger name="com.jcabi.manifests" level="OFF"/>
driftcatcher

1
Кілька маніфестів одного і того ж завантажувача класів перекриваються та перезаписують один одного
guai

13

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

У моєму випадку я хотів прочитати атрибут "Реалізація-Версія", тому я почав з рішень, наведених вище, щоб отримати потік, а потім прочитав його, щоб отримати значення. Поки це рішення працювало, колега, який переглядав мій код, показав мені простіший спосіб зробити те, що я хотів. Зауважте, що це рішення знаходиться в Котліні, а не на Java.

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

Ще раз зауважте, що це не дає відповіді на початкове запитання, зокрема, "Export-package" не здається одним із підтримуваних атрибутів. Однак це є myPackage.name, яке повертає значення. Можливо, хтось, хто розуміє це більше, ніж я, може прокоментувати, чи повертає це значення, яке вимагає оригінальний плакат.


4
Дійсно, порт java простий:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
Ian Robertson

Адже це те, що я шукав. Я також радий, що у 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 безпосередньо.


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()виникнення зворотних нахилів?
басейн

9

Найпростіший спосіб - використовувати клас 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

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. Які критерії, щоб це спрацювало? Документація не пояснює це.
припинення

4
Звідки DataUtilitiesімпорт? Схоже, це не в JDK.
Джолта

2

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


Якщо ваш клас названий "com.mypackage.MyClass", виклик class.getResource("myresource.txt")спробує завантажити цей ресурс з com/mypackage/myresource.txt. Як саме ви збираєтесь використовувати цей підхід, щоб отримати маніфест?
ChssPly76

1
Гаразд, я маю відмовитися. Ось що випливає з не тестування. Я думав, що ти можеш сказати this.getClass (). GetResource ("../../ META-INF / MANIFEST.MF") (Однак багато ".." потрібні з урахуванням назви вашого пакета.) Але поки який працює для файлів класів у каталозі, щоб випрацювати ваше дерево каталогів, очевидно, не працює для JAR. Я не бачу, чому ні, але це так. Також не працює цей.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").
Роберт

Ви пробували? Який баночний маніфест він отримає, якщо у вас буде кілька баночок на класі? Знадобиться перший, який не є тим, що вам потрібно. Мій код вирішує цю проблему, і вона справді працює.
Алекс Коншин

Я не критикував те, як ви використовуєте завантажувач класів для завантаження певного ресурсу. Я вказував, що весь код між classLoader.getResource(..)і url.openStream()абсолютно не має значення та схильний до помилок, оскільки він намагається зробити так само, як і classLoader.getResourceAsStream(..)він.
Роберт

Ні. Це різне. Мій код виявляється з конкретної jar, де знаходиться клас, а не з першого jar в classpath.
Алекс Коншин

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

0

Я використав рішення від Ентоні Юккеля, але в 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();

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.