【问题标题】:How to read my META-INF/MANIFEST.MF file in a Spring Boot app?如何在 Spring Boot 应用程序中读取我的 META-INF/MANIFEST.MF 文件?
【发布时间】:2015-11-24 11:13:34
【问题描述】:

我正在尝试从我的 Spring Boot Web 应用程序中读取我的 META-INF/MANIFEST.MF 文件(包含在一个 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

【问题讨论】:

    标签: java spring-boot manifest.mf


    【解决方案1】:

    几乎所有 jar 文件都带有清单文件,因此您的代码返回 第一个文件,它可以在 类路径中找到它。

    为什么你仍然想要清单?这是Java使用的文件。将您需要的任何自定义值放在其他位置,例如在您旁边的 .properties 文件中 .class 文件中。

    更新 2

    正如下面评论中提到的,不是在问题中,真正的目标是清单中的版本信息。 Java 已经通过 java.lang.Package 类提供了该信息。

    不要试图自己阅读清单,即使你能找到它。

    更新 1

    请注意,清单文件不是Properties 文件。它的结构比这复杂得多。

    请参阅JAR File Specification 的 java 文档中的示例。

    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)
    

    如您所见,NameSHA-256-Digest 不止一次出现。 Properties 类无法处理这个问题,因为它只是一个 Map 并且键必须是唯一的。

    【讨论】:

    • 它适用于其他 Java 应用程序,但显然 Spring Boot 确实有不同的方法。我需要阅读清单文件,因为 Maven 就在其中包含有关版本号、内部版本号、实施供应商等的信息,我想向用户显示这些信息。
    • @RicardoMemoria 查看java.lang.Package 以获取该信息。正如我所说,您的类路径中有 许多 清单文件。如果你保证你的 jar 是第一个,你只会得到 your 文件。这很少是您可以获得的保证。
    • 谢谢...看来使用 java.lang.Package 可以解决我的问题。但是你说不要自己去读清单。可能 Properties 类不是最好的方法(尽管它可以满足我的需要),但是 Java 中有一个 Manifest 类可以专门用于处理清单文件的内容。我们不应该尝试自己阅读清单是否有任何具体原因?
    • @RicardoMemoria 没错,Manifest 类可用于读取清单文件,但正如您已经看到的,查找清单文件是一个问题,因为所有 Jar 文件都有一。您需要阅读的情况极为罕见,尤其是因为 Package 类以一种处理多个文件问题的方式提供了您可能需要的唯一信息。
    • @Andreas 询问清单的原因?因为 Java 和 Maven 可以轻松地自动将信息放在那里,例如 Jenkins 内部版本号,这是测试人员报告发现错误的版本时的关键信息。必须尝试将所有内容都填充到 Implementation-Version 项目中。
    【解决方案2】:
    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;
    }
    

    【讨论】:

    • 我得到这个错误 java.lang.ClassCastException: org.springframework.boot.loader.jar.JarFile cannot be cast to java.io.InputStream.显然它正在访问其他的 spring boot 库来读取我自己的 jar 文件。
    • 你能评估这个表达式吗:this.getClass().getProtectionDomain().getCodeSource().getLocation()
    • 对我来说也失败了,同样的例外,getLocation() 调用返回 URL jar:file:/Users/chrisinmtown/git/common-code/cmn-data/target/cmn-data-1.0。 0-SNAPSHOT.jar!/BOOT-INF/classes!/
    【解决方案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”

    谢谢 亚历山大

    【讨论】:

    • 它适用于其他 java 应用程序,但不适用于 Spring Boot 应用程序。在 Spring Boot 应用程序中,MANIFEST.MF 来自应用程序中使用的所有 jar 列表中的第一个 jar(包括应用程序本身),并且使用您的示例,读取的清单来自标准 Java 库。
    • 嗨,这两种情况都对我有用。它应该对你有用。也许你忘记了 maven-war-plugin 或 maven-jar-plugin。如果你愿意,我可以与你分享代码。
    • 嗨。是的,它可以在常规 Java 应用程序中运行,但是 Spring Boot 引入了另一个 ClassLoader,它会阻止上面的代码按预期工作。实际上,在调用getResourceXX时,spring boot会在类路径的所有jar中搜索第一个与名称匹配的资源。
    • 它在 PC 上为我工作,但在 Mac 上它读取错误的清单文件。
    【解决方案4】:

    在对 Spring 文档进行测试和搜索后,我找到了一种读取清单文件的方法。

    首先,Spring Boot 实现了自己的 ClassLoader,它改变了资源的加载方式。当您调用getResource() 时,Spring Boot 将加载与类路径中所有可用 JAR 列表中给定资源名称匹配的第一个资源,并且您的应用程序 jar 不是首选。

    所以,当发出类似这样的命令时:

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

    返回在类路径的任何 Jar 中找到的第一个 MANIFEST.MF 文件。在我的例子中,它来自一个 JDK jar 库。

    解决方案:

    我设法获得了包含资源“/META-INF/MANIFEST.MF”的应用程序加载的所有 jar 的列表,并检查了资源是否来自我的应用程序 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 对象内 manifest.mf 文件中的所有数据。

    我从reading MANIFEST.MF file from jar file using JAVA借了部分解决方案

    【讨论】:

    • 这感觉足以表明真正的答案是“不要那样做”。
    • 为什么不直接打开url而不是循环遍历所有元素并检查它是否与appManifestFileName匹配?
    • 因为 SpringBoot 特性。您的应用程序由启动器在内部执行,因此如果您尝试直接读取 URL 资源,它将指向启动器,而不是您的 jar。到一天结束时,您的 jar 只是启动器使用的另一个 jar。
    【解决方案5】:

    我使用java.lang.Package 从清单中读取spring boot 中的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)
        }
    }
    

    【讨论】:

    • 请注意:正如 spring-boot 问题6619 解释的那样,这种方式仅适用于运行打包的 jar/war。 gradle bootRun 将不起作用。
    • 在 Jetty 嵌入式服务器上完美运行。要使用 maven shade 插件创建 Jetty uber jar,请为 Implementation-Version 添加清单条目。 <Implementation-Version>${project.version}_${dev.build.timestamp}_branch:${scmBranch}_rev:${buildNumber}</Implementation-Version> 然后从您的代码中您可以轻松地从 String version = Application.class.getPackage().getImplementationVersion(); 获取值
    • 这表明 Spring-Boot 应用程序中唯一可用的 Manifest 项是 Implementation-Version,因此只需将您需要的任何信息塞入其中即可。我理解正确吗?
    【解决方案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,您可以轻松扩展示例以加载其他属性。

    【讨论】:

    • resource.exists() 在我的测试中总是返回 false,我使用的是 Spring Boot 版本 1.5.3.RELEASE。异常是 java.io.FileNotFoundException:ServletContext 资源 [/META-INF/MANIFEST.MF] 无法解析为 URL。但是重新打包的 jar 在路径 /META-INF/MANIFEST.MF 文件中有我的清单,所以 IDK。
    【解决方案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 命令启动你的应用程序时才有效,如果创建一个集成测试它就不起作用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-14
      • 2020-11-03
      • 2018-07-23
      • 2011-05-19
      • 1970-01-01
      • 2015-01-26
      • 2015-02-09
      • 2010-11-11
      相关资源
      最近更新 更多