How to read my META-INF/MANIFEST.MF file in a Spring Boot app?

做~自己de王妃 提交于 2019-11-29 12:25:45

问题


I'm trying to read my META-INF/MANIFEST.MF file from my Spring Boot web app (contained in a jar file).

I'm trying the following code:

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

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

But apparently there is something behind the scenes in Spring Boot that a different manifest.mf is loaded (and not my own located in META-INF folder).

Does anyone know how I can read my manifest app in my Spring Boot app?

UPDATE: After some research I noticed that using the usual way to read a manifest.mf file, in a Spring Boot application this is the Jar that is being accessed

org.springframework.boot.loader.jar.JarFile

回答1:


I use java.lang.Package to read the Implementation-Version attribute in spring boot from the manifest.

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

The Implementation-Version attribute should be configured in build.gradle

jar {
    baseName = "my-app"
    version =  "0.0.1"
    manifest {
        attributes("Implementation-Version": version)
    }
}



回答2:


It's simple just add this

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

Working for me.

Note:

getClass().getClassLoader() is important

and

"META-INF/MANIFEST.MF" not "/META-INF/MANIFEST.MF"

Thanks Aleksandar




回答3:


Pretty much all jar files come with a manifest file, so your code is returning the first file it can find in the classpath.

Why would you want the manifest anyway? It's a file used by Java. Put any custom values you need somewhere else, like in a .properties file next to you .class file.

Update 2

As mentioned in a comment below, not in the question, the real goal is the version information from the manifest. Java already supplies that information with the java.lang.Package class.

Don't try to read the manifest yourself, even if you could find it.

Update 1

Note that the manifest file is not a Properties file. It's structure is much more complex than that.

See the example in the java documentation of the 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)

As you can see, Name and SHA-256-Digest occurs more than once. The Properties class cannot handle that, since it's just a Map and the keys have to be unique.




回答4:


After testing and searching on Spring docs, I found a way to read the manifest file.

First off, Spring Boot implements its own ClassLoader that changes the way how resources are loaded. When you call getResource(), Spring Boot will load the first resource that matches the given resource name in the list of all JARs available in the class path, and your app jar is not the first choice.

So, when issuing a command like:

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

The first MANIFEST.MF file found in any Jar of the Class Path is returned. In my case, it came from a JDK jar library.

SOLUTION:

I managed to get a list of all Jars loaded by the app that contains the resource "/META-INF/MANIFEST.MF" and checked if the resource came from my application jar. If so, read its MANIFEST.MF file and return to the app, like that:

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

This method will return all data from a manifest.mf file inside a Manifest object.

I borrowed part of the solution from reading MANIFEST.MF file from jar file using JAVA




回答5:


I have it exploiting Spring's resource resolution:

@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);
        }
    }

}

Here we're getting CI-Build, and you can easily extend the example to load other attributes.




回答6:


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



回答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);
        ...
    }

This will work only if you launch your app via java -jar command, it won't work if one create an integration test.



来源:https://stackoverflow.com/questions/32293962/how-to-read-my-meta-inf-manifest-mf-file-in-a-spring-boot-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!