Как прочитать мой файл 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

Ответы

Ответ 1

Я использую 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)
    }
}

Ответ 2

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

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

Обновление 2

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

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

Обновление 1

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

См. Пример в java-документации JAR File Specification.

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 и ключи должны быть уникальными.

Ответ 3

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

    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"

Спасибо Aleksandar

Ответ 4

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

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

Таким образом, при выдаче команды, например:

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

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

РЕШЕНИЕ:

Мне удалось получить список всех Jars, загруженных приложением, которое содержит ресурс "/META-INF/MANIFEST.MF", и проверил, появился ли ресурс из моего приложения. Если да, прочитайте его файл 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.MF из файла jar с помощью JAVA

Ответ 5

public Properties readManifest() throws IOException {
    Object inputStream = this.getClass().getProtectionDomain().getCodeSource().getLocation().getContent();
    JarInputStream jarInputStream = new JarInputStream((InputStream) inputStream);
    Manifest manifest = jarInputStream.getManifest();
    Attributes attributes = manifest.getMainAttributes();
    Properties properties = new Properties();
    properties.putAll(attributes);
    return properties;
}

Ответ 6

Я использую 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, и вы можете легко расширить этот пример, чтобы загрузить другие атрибуты.

Ответ 7

    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, это не сработает, если вы создадите тест интеграции.