Holen Sie sich zur Laufzeit die Maven-Artefaktversion


175

Ich habe festgestellt, dass in der JAR eines Maven-Artefakts das Attribut project.version in zwei Dateien enthalten ist:

META-INF/maven/${groupId}/${artifactId}/pom.properties
META-INF/maven/${groupId}/${artifactId}/pom.xml

Gibt es eine empfohlene Möglichkeit, diese Version zur Laufzeit zu lesen?


Antworten:


263

Sie sollten nicht auf Maven-spezifische Dateien zugreifen müssen, um die Versionsinformationen einer bestimmten Bibliothek / Klasse abzurufen.

Sie können einfach verwenden getClass().getPackage().getImplementationVersion(), um die Versionsinformationen abzurufen, die in einer .jar-Datei gespeichert sind MANIFEST.MF. Zum Glück ist Maven klug genug Leider schreibt Maven standardmäßig auch nicht die richtigen Informationen in das Manifest!

Stattdessen muss man das <archive>Konfigurationselement von maven-jar-pluginto set addDefaultImplementationEntriesund addDefaultSpecificationEntriesto truewie folgt ändern :

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

Idealerweise sollte diese Konfiguration in das Unternehmen pomoder einen anderen Basis-Pom übernommen werden.

Eine ausführliche Dokumentation des <archive>Elements finden Sie in der Dokumentation zum Maven-Archiv .


6
Leider scheint nicht jeder Klassenladeprogramm diese Eigenschaften aus der Manifestdatei zu laden (ich erinnere mich, dass ich in genau diesem Fall Probleme mit Tomcat hatte).
Dwegener

@avithan: wirklich? Ich hatte mit diesem Ansatz nie ein Problem mit Tomcat. Ich denke auch, dass ein Klassenladeprogramm, das das Manifest ignoriert, wahrscheinlich nicht konform ist.
Joachim Sauer

@ JoachimSauer ok, ich habe mich geirrt. Derzeit scheint es unter HotSpot großartig zu funktionieren, aber unter OpenJDK funktioniert es nicht zuverlässig. Ich werde
zurückmelden,

@avithan das ist für mich relevant (und ich habe nicht gesehen, was Sie berichten) - haben Sie schon detaillierte Informationen erhalten?
Thorbjørn Ravn Andersen

4
Leider funktioniert dies nicht, wenn das Projekt von Eclipse ausgeführt wird oder "mvn exec: java" verwendet.
Jaan

76

Um der obigen Antwort für ein .warArtefakt nachzugehen, musste ich die entsprechende Konfiguration anwenden auf maven-war-pluginanstatt maven-jar-plugin:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

Dies fügte die Versionsinformationen MANIFEST.MFin das Projekt ein .jar(enthalten in WEB-INF/libder .war)


3
<archiveClasses> true </ archiveClasses> hat in meinem Fall einen Fehler verursacht. Aber das Problem wurde gelöst stackoverflow.com/questions/14934299/…
Paul Verest

10
Wenn ich das versuche, ist mein Ergebnis immer, nullobwohl die MANIFEST.MF in den Kriegsdateien die richtigen Informationen enthält.
Thomas.mc.work

Ich musste es auch zum Maven-Assembly-Plugin
hinzufügen

2
<archiveClasses> true </ archiveClasses> scheint nichts damit zu
tun zu haben

1
@ RafaelSimonelli habe ich entfernt <archiveClasses>true</archiveClasses>- und es funktioniert seitdem zuverlässig.
Thomas.mc.work

27

Hier ist eine Methode, um die Version aus den pom.properties abzurufen und auf das Abrufen aus dem Manifest zurückzugreifen

public synchronized String getVersion() {
    String version = null;

    // try to load from maven properties first
    try {
        Properties p = new Properties();
        InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artefact/pom.properties");
        if (is != null) {
            p.load(is);
            version = p.getProperty("version", "");
        }
    } catch (Exception e) {
        // ignore
    }

    // fallback to using Java API
    if (version == null) {
        Package aPackage = getClass().getPackage();
        if (aPackage != null) {
            version = aPackage.getImplementationVersion();
            if (version == null) {
                version = aPackage.getSpecificationVersion();
            }
        }
    }

    if (version == null) {
        // we could not compute the version so use a blank
        version = "";
    }

    return version;
} 

2
Fügen Sie dies in einen statischen Initialisierungsblock ein.
Opyate

1
Guter Rat. Wenn Sie dies in einem Servlet (oder .jsp) verwenden, müssen Sie getServletContext (). GetResourceAsStream anstelle von getClass () verwenden. GetResourceAsStream
Sandman

3
Dies funktioniert nur, wenn die Anwendung aus dem JAR ausgeführt wird. Wenn die Ressource vom exec-maven-plugin (z. B. Netbeans) ausgeführt wird, ist sie null.
Leif Gruenwoldt

Dieser Code wird Teil meiner Standardwerte für die Hauptklasse sein! Vielen Dank!!
Wendel

Ich habe dies mit Wills Antwort für eine einfache und leicht zu wartende Option verwendet.
Javydreamercsw

3

Ich habe einige Zeit mit den beiden Hauptansätzen verbracht und sie haben für mich nicht funktioniert. Ich verwende Netbeans für die Builds. Vielleicht ist dort mehr los. Ich hatte einige Fehler und Warnungen von Maven 3 mit einigen Konstrukten, aber ich denke, diese waren leicht zu korrigieren. Kein Problem.

Ich habe in diesem Artikel auf DZone eine Antwort gefunden, die wartbar und einfach zu implementieren ist:

Ich habe bereits einen Unterordner für resources / config und habe meine Datei app.properties genannt, um die Art der Dinge, die wir dort aufbewahren können (wie eine Support-URL usw.), besser wiederzugeben.

Die einzige Einschränkung ist, dass Netbeans eine Warnung ausgibt, dass die IDE herausgefiltert werden muss. Nicht sicher, wo / wie. Es hat zu diesem Zeitpunkt keine Auswirkung. Vielleicht gibt es dafür eine Lösung, wenn ich diese Brücke überqueren muss. Viel Glück.


3

Ich benutze maven-assembly-pluginfür meine Maven-Verpackung. Die Verwendung von Apache Maven Archiver in Joachim Sauers Antwort könnte auch funktionieren:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution .../>
    </executions>
</plugin>

Da Archiever eine der gemeinsam genutzten Maven-Komponenten ist , kann es von mehreren Maven-Building-Plugins verwendet werden. Dies kann auch zu Konflikten führen, wenn zwei oder mehr Plugins eingeführt werden, einschließlich der archiveKonfiguration im Inneren.


2

Um dies sowohl in Eclipse als auch in einem Maven-Build zum Laufen zu bringen, sollten Sie die Einträge addDefaultImplementationEntriesund addDefaultSpecificationEntriespom wie in anderen Antworten beschrieben hinzufügen und dann den folgenden Code verwenden:

public synchronized static final String getVersion() {
    // Try to get version number from pom.xml (available in Eclipse)
    try {
        String className = getClass().getName();
        String classfileName = "/" + className.replace('.', '/') + ".class";
        URL classfileResource = getClass().getResource(classfileName);
        if (classfileResource != null) {
            Path absolutePackagePath = Paths.get(classfileResource.toURI())
                    .getParent();
            int packagePathSegments = className.length()
                    - className.replace(".", "").length();
            // Remove package segments from path, plus two more levels
            // for "target/classes", which is the standard location for
            // classes in Eclipse.
            Path path = absolutePackagePath;
            for (int i = 0, segmentsToRemove = packagePathSegments + 2;
                    i < segmentsToRemove; i++) {
                path = path.getParent();
            }
            Path pom = path.resolve("pom.xml");
            try (InputStream is = Files.newInputStream(pom)) {
                Document doc = DocumentBuilderFactory.newInstance()
                        .newDocumentBuilder().parse(is);
                doc.getDocumentElement().normalize();
                String version = (String) XPathFactory.newInstance()
                        .newXPath().compile("/project/version")
                        .evaluate(doc, XPathConstants.STRING);
                if (version != null) {
                    version = version.trim();
                    if (!version.isEmpty()) {
                        return version;
                    }
                }
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Try to get version number from maven properties in jar's META-INF
    try (InputStream is = getClass()
        .getResourceAsStream("/META-INF/maven/" + MAVEN_PACKAGE + "/"
                + MAVEN_ARTIFACT + "/pom.properties")) {
        if (is != null) {
            Properties p = new Properties();
            p.load(is);
            String version = p.getProperty("version", "").trim();
            if (!version.isEmpty()) {
                return version;
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Fallback to using Java API to get version from MANIFEST.MF
    String version = null;
    Package pkg = getClass().getPackage();
    if (pkg != null) {
        version = pkg.getImplementationVersion();
        if (version == null) {
            version = pkg.getSpecificationVersion();
        }
    }
    version = version == null ? "" : version.trim();
    return version.isEmpty() ? "unknown" : version;
}

Wenn Ihr Java-Build Zielklassen an einer anderen Stelle als "Ziel / Klassen" platziert, müssen Sie möglicherweise den Wert von segmentesToRemove anpassen.


Sie wissen, ob dies für Unit-Tests ist, können Sie einfach System.getProperty("user.dir")/pom.xml. Ich bin mir ziemlich sicher, dass dies auch für andere Dinge gilt, außer vielleicht nicht für WTP.
Adam Gent

Dies funktioniert nur, wenn sich Ihr Projekt in einem Verzeichnis befindet. Wenn Sie ein auf Jarfiles basierendes Projekt ausführen, funktioniert Ihre Lösung nicht. Sie müssen .getResource()oder verwenden .getResourceAsStream().
Luke Hutchison

Ja, ich ging davon aus, dass Sie das Glas bereits überprüft haben (ala getResource). Das heißt, Sie überprüfen zuerst mit getResource, ob dies fehlschlägt. Dann wurde das Projekt noch nicht in ein JAR integriert. Dies bedeutet, dass Sie es entweder über Eclipse oder Maven ausführen, was "System.getProperty (" user.dir ") / pom.xml bedeutet . Das einzige Problem ist, dass diese POM-Datei noch nicht die wirklich effektive POM-Datei ist (das heißt, einige Eigenschaften werden nicht erweitert), aber auch nicht die, die Sie mit der Eclipse-Methode erhalten.
Adam Gent

1

In meiner Spring-Boot-Anwendung funktionierte die Lösung aus der akzeptierten Antwort, bis ich kürzlich mein JDK auf Version 12 aktualisiert habe. Ich habe auch alle anderen Antworten ausprobiert und konnte das nicht zum Laufen bringen.

Zu diesem Zeitpunkt fügte ich der ersten Klasse meiner Spring Boot-Anwendung unmittelbar nach der Anmerkung die folgende Zeile hinzu @SpringBootApplication

@PropertySources({ 
        @PropertySource("/META-INF/maven/com.my.group/my-artefact/pom.properties")
})

Später verwende ich Folgendes, um den Wert aus der Eigenschaftendatei in der Klasse abzurufen, in der ich den Wert verwenden möchte, und appVersionerhalte die Projektversion für mich:

@Value("${version}")
private String appVersion;

Hoffe das hilft jemandem.


Wie mache ich dasselbe mit mehreren POM-Dateien? Ich möchte die Version aus mehreren POM-Dateien laden.
THM

0

Eine einfache Lösung, die Maven-kompatibel ist und für jede Klasse (also auch für Dritte) funktioniert:

    private static Optional<String> getVersionFromManifest(Class<?> clazz) {
        try {
            File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
            if (file.isFile()) {
                JarFile jarFile = new JarFile(file);
                Manifest manifest = jarFile.getManifest();
                Attributes attributes = manifest.getMainAttributes();
                final String version = attributes.getValue("Bundle-Version");
                return Optional.of(version);
            }
        } catch (Exception e) {
            // ignore
        }
        return Optional.empty();
    }

-1

Java 8-Variante für EJB in Kriegsdatei mit Maven-Projekt. Getestet auf EAP 7.0.

@Log4j // lombok annotation
@Startup
@Singleton
public class ApplicationLogic {

    public static final String DEVELOPMENT_APPLICATION_NAME = "application";

    public static final String DEVELOPMENT_GROUP_NAME = "com.group";

    private static final String POM_PROPERTIES_LOCATION = "/META-INF/maven/" + DEVELOPMENT_GROUP_NAME + "/" + DEVELOPMENT_APPLICATION_NAME + "/pom.properties";

    // In case no pom.properties file was generated or wrong location is configured, no pom.properties loading is done; otherwise VERSION will be assigned later
    public static String VERSION = "No pom.properties file present in folder " + POM_PROPERTIES_LOCATION;

    private static final String VERSION_ERROR = "Version could not be determinated";

    {    
        Optional.ofNullable(getClass().getResourceAsStream(POM_PROPERTIES_LOCATION)).ifPresent(p -> {

            Properties properties = new Properties();

            try {

                properties.load(p);

                VERSION = properties.getProperty("version", VERSION_ERROR);

            } catch (Exception e) {

                VERSION = VERSION_ERROR;

                log.fatal("Unexpected error occured during loading process of pom.properties file in META-INF folder!");
            }
        });
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.