Как прочитать мой файл META-INF/MANIFEST.MF в приложении Spring Boot?

Я пытаюсь прочитать свой файл META-INF/MANIFEST.MF из своего веб-приложения Spring Boot (содержащегося в файле jar).

Я пытаюсь использовать следующий код:

        InputStream is = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");

        Properties prop = new Properties();
        prop.load( is );

Но, видимо, в Spring Boot есть что-то за кулисами, что загружается другой manifest.mf (а не мой, расположенный в папке META-INF).

Кто-нибудь знает, как я могу прочитать свое приложение манифеста в моем приложении Spring Boot?

ОБНОВЛЕНИЕ: после некоторых исследований я заметил, что при обычном способе чтения файла manifest.mf в приложении Spring Boot это Jar, к которому осуществляется доступ

org.springframework.boot.loader.jar.JarFile

person Ricardo Memoria    schedule 30.08.2015    source источник


Ответы (7)


Я использую java.lang.Package для чтения атрибут Implementation-Version в весенней загрузке из манифеста.

String version = Application.class.getPackage().getImplementationVersion();

Атрибут Implementation-Version должен быть настроен в build.gradle

jar {
    baseName = "my-app"
    version =  "0.0.1"
    manifest {
        attributes("Implementation-Version": version)
    }
}
person samsong8610    schedule 24.06.2016
comment
Просто примечание: как объясняется проблема весенней загрузки 6619, таким образом работал только при запуске упакованного jar/war. gradle bootRun не получится. - person btpka3; 15.03.2017
comment
Отлично работает на встроенном сервере Jetty. чтобы создать банку Jetty uber с плагином maven shadow, добавьте записи манифеста для версии реализации. <Implementation-Version>${project.version}_${dev.build.timestamp}_branch:${scmBranch}_rev:${buildNumber}</Implementation-Version> Затем из вашего кода вы можете легко получить значение из String version = Application.class.getPackage().getImplementationVersion(); - person user3774109; 04.05.2017
comment
Это говорит о том, что единственный элемент манифеста, легко доступный из приложения Spring-Boot, — это версия реализации, поэтому просто втисните туда любую информацию, которая вам нужна. Я правильно это понял? - person chrisinmtown; 06.11.2017

Просто добавьте это

    InputStream is = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF");

    Properties prop = new Properties();
    try {
        prop.load( is );
    } catch (IOException ex) {
        Logger.getLogger(IndexController.class.getName()).log(Level.SEVERE, null, ex);
    }

Работает на меня.

Примечание:

getClass().getClassLoader() важен

и

«META-INF/MANIFEST.MF», а не «/META-INF/MANIFEST.MF»

Спасибо, Александр.

person abosancic    schedule 31.08.2015
comment
Он работает в других приложениях Java, но не в приложении Spring Boot. В приложении Spring Boot файл MANIFEST.MF исходит из первого jar-файла, найденного в списке всех jar-файлов, используемых в приложении (включая само приложение), и, используя ваш пример, прочитанный манифест исходит из стандартной Java-библиотеки. - person Ricardo Memoria; 01.09.2015
comment
Привет, это определенно работает для меня в обоих случаях. Это должно работать у вас. Возможно, вы забыли maven-war-plugin или maven-jar-plugin. Если хотите, я могу поделиться с вами кодом. - person abosancic; 01.09.2015
comment
Привет. Да, это работает в обычном Java-приложении, но Spring Boot представляет еще один ClassLoader, который не позволяет приведенному выше коду работать должным образом. На самом деле, при вызове getResourceXX Spring Boot будет искать первый ресурс, который соответствует имени во всех банках пути к классу. - person Ricardo Memoria; 09.09.2015
comment
У меня это сработало на ПК, но на Mac он читает неправильный файл манифеста. - person Dmitry Klochkov; 19.09.2016

Практически все файлы jar поставляются с файлом манифеста, поэтому ваш код возвращает первый файл, который он может найти в пути к классам.

Зачем вам манифест в любом случае? Это файл, используемый Java. Поместите любые пользовательские значения, которые вам нужны, в другое место, например, в файл .properties рядом с вашим файлом .class.

Обновление 2

Как упоминалось в комментарии ниже, не в вопросе, реальной целью является информация о версии из манифеста. Java уже предоставляет эту информацию с помощью класса java.lang.Package. .

Не пытайтесь читать манифест самостоятельно, даже если сможете его найти.

Обновление 1

Обратите внимание, что файл манифеста не является файлом Properties. Его структура гораздо сложнее.

См. пример в документации по Java для файла JAR. Спецификация.

Manifest-Version: 1.0
Created-By: 1.7.0 (Sun Microsystems Inc.)

Name: common/class1.class
SHA-256-Digest: (base64 representation of SHA-256 digest)

Name: common/class2.class
SHA1-Digest: (base64 representation of SHA1 digest)
SHA-256-Digest: (base64 representation of SHA-256 digest)

Как видите, Name и SHA-256-Digest встречаются более одного раза. Класс Properties не может с этим справиться, поскольку это всего лишь класс Map, а ключи должны быть уникальными.

person Andreas    schedule 30.08.2015
comment
Он правильно работает с другими Java-приложениями, но, по-видимому, Spring Boot использует другой подход. Мне нужно прочитать файл манифеста, потому что именно там Maven включает информацию о номере версии, номере сборки, поставщике реализации и т. д., и я хочу отобразить это пользователю. - person Ricardo Memoria; 30.08.2015
comment
@RicardoMemoria Взгляните на java.lang.Package для этой информации. Как я уже сказал, в вашем пути к классам есть много файлов манифеста. Вы получите ваш файл только в том случае, если вам гарантировано, что ваша банка будет первой. Это очень редко гарантия, которую вы можете получить. - person Andreas; 30.08.2015
comment
Спасибо... Кажется, использование java.lang.Package решит мою проблему. Но вы говорите не пытаться читать манифест самостоятельно. Вероятно, класс Properties — не лучший способ (хотя он работал для того, что мне было нужно), но в Java есть класс Manifest, который можно использовать специально для обработки содержимого файла манифеста. Есть ли какая-то конкретная причина, по которой мы не должны пытаться читать манифест самостоятельно? - person Ricardo Memoria; 31.08.2015
comment
@RicardoMemoria Вы правы, класс Manifest можно использовать для чтения файла манифеста, но, как вы уже видели, поиск файла манифеста является проблемой, потому что он есть во всех файлах Jar. Чрезвычайно редко вам нужно будет читать его, тем более что класс Package предоставляет единственную информацию, которая вам, вероятно, понадобится, таким образом, чтобы решить проблему с несколькими файлами. - person Andreas; 31.08.2015
comment
@Андреас спрашивает, почему манифест? Потому что Java и Maven могут ЛЕГКО и автоматически помещать туда информацию, такую ​​как номер сборки Jenkins, которая является важной информацией, когда тестировщики сообщают о версии, в которой обнаружена ошибка. Придется попробовать запихнуть все в элемент Реализация-Версия. - person chrisinmtown; 06.11.2017

После тестирования и поиска в документации Spring я нашел способ прочитать файл манифеста.

Во-первых, Spring Boot реализует собственный ClassLoader, который изменяет способ загрузки ресурсов. Когда вы вызываете getResource(), Spring Boot загрузит первый ресурс, который соответствует заданному имени ресурса в списке всех JAR-файлов, доступных в пути к классу, и ваш jar-файл приложения не является первым выбором.

Итак, при выдаче команды типа:

getClassLoader().getResourceAsStream("/META-INF/MANIFEST.MF");

Возвращается первый файл MANIFEST.MF, найденный в любой банке пути к классу. В моем случае это произошло из jar-библиотеки JDK.

РЕШЕНИЕ:

Мне удалось получить список всех JAR-файлов, загруженных приложением, которое содержит ресурс «/META-INF/MANIFEST.MF», и проверить, поступил ли ресурс из JAR-файла моего приложения. Если это так, прочитайте его файл MANIFEST.MF и вернитесь в приложение, например:

private Manifest getManifest() {
    // get the full name of the application manifest file
    String appManifestFileName = this.getClass().getProtectionDomain().getCodeSource().getLocation().toString() + JarFile.MANIFEST_NAME;

    Enumeration resEnum;
    try {
        // get a list of all manifest files found in the jars loaded by the app
        resEnum = Thread.currentThread().getContextClassLoader().getResources(JarFile.MANIFEST_NAME);
        while (resEnum.hasMoreElements()) {
            try {
                URL url = (URL)resEnum.nextElement();
                // is the app manifest file?
                if (url.toString().equals(appManifestFileName)) {
                    // open the manifest
                    InputStream is = url.openStream();
                    if (is != null) {
                        // read the manifest and return it to the application
                        Manifest manifest = new Manifest(is);
                        return manifest;
                    }
                }
            }
            catch (Exception e) {
                // Silently ignore wrong manifests on classpath?
            }
        }
    } catch (IOException e1) {
        // Silently ignore wrong manifests on classpath?
    }
    return null;
}

Этот метод вернет все данные из файла manifest.mf внутри объекта Manifest.

Я позаимствовал часть решения из чтение файла MANIFEST.MF из jar-файла с помощью JAVA

person Ricardo Memoria    schedule 09.09.2015
comment
Это похоже на то, что кода достаточно, чтобы предложить реальный ответ: не делайте этого. - person Michael Haefele; 21.10.2016
comment
Почему бы вам не открыть URL-адрес напрямую, вместо того, чтобы перебирать все элементы и проверять, совпадает ли он с appManifestFileName? - person Pawan; 30.10.2017
comment
Из-за функции SpringBoot. Ваше приложение внутренне выполняется с помощью средства запуска, поэтому, если вы попытаетесь напрямую прочитать ресурс URL, он будет указывать на средство запуска, а не на вашу банку. К концу дня ваша банка — это просто еще одна банка, используемая пусковой установкой. - person Ricardo Memoria; 22.11.2017

Я использую разрешение ресурсов Spring:

@Service
public class ManifestService {

    protected String ciBuild;

    public String getCiBuild() { return ciBuild; }

    @Value("${manifest.basename:/META-INF/MANIFEST.MF}")
    protected void setManifestBasename(Resource resource) {
        if (!resource.exists()) return;
        try (final InputStream stream = resource.getInputStream()) {
            final Manifest manifest = new Manifest(stream);
            ciBuild = manifest.getMainAttributes().getValue("CI-Build");
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

Здесь мы получаем CI-Build, и вы можете легко расширить пример для загрузки других атрибутов.

person usethe4ce    schedule 30.10.2016
comment
В моих тестах resource.exists() всегда возвращает false, я использую версию весенней загрузки 1.5.3.RELEASE. Исключением является java.io.FileNotFoundException: ресурс ServletContext [/META-INF/MANIFEST.MF] не может быть преобразован в URL-адрес. Но в переупакованной банке есть мой манифест по пути /META-INF/MANIFEST.MF, поэтому IDK. - person chrisinmtown; 26.06.2017

    try {
        final JarFile jarFile = (JarFile) this.getClass().getProtectionDomain().getCodeSource().getLocation().getContent();
        final Manifest manifest = jarFile.getManifest();
        final Map<Object, Object> manifestProps = manifest.getMainAttributes().entrySet().stream()
                .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
    ...
    } catch (final IOException e) {
        LOG.error("Unable to read MANIFEST.MF", e);
        ...
    }

Это будет работать, только если вы запустите свое приложение с помощью команды java -jar, оно не будет работать, если вы создадите интеграционный тест.

person Ihor M.    schedule 05.04.2018

person    schedule
comment
Я получаю эту ошибку java.lang.ClassCastException: org.springframework.boot.loader.jar.JarFile не может быть преобразован в java.io.InputStream. По-видимому, он обращается к библиотеке весенней загрузки в другом, чтобы прочитать мой собственный файл jar. - person Ricardo Memoria; 30.08.2015
comment
Можете ли вы оценить это выражение: this.getClass().getProtectionDomain().getCodeSource().getLocation() - person outdev; 30.08.2015
comment
У меня также не работает с тем же исключением, вызов getLocation() возвращает URL-адрес jar:file:/Users/chrisinmtown/git/common-code/cmn-data/target/cmn-data-1.0.0-SNAPSHOT.jar!/BOOT -INF/классы!/ - person chrisinmtown; 26.06.2017