Ottieni la versione del manufatto Maven in fase di esecuzione


177

Ho notato che nel JAR di un artefatto di Maven, l'attributo project.version è incluso in due file:

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

Esiste un modo consigliato per leggere questa versione in fase di esecuzione?


Risposte:


265

Non è necessario accedere ai file specifici di Maven per ottenere le informazioni sulla versione di una determinata libreria / classe.

Puoi semplicemente usare getClass().getPackage().getImplementationVersion()per ottenere le informazioni sulla versione archiviate in un file .jar MANIFEST.MF. Fortunatamente Maven è abbastanza intelligente Sfortunatamente Maven non scrive le informazioni corrette anche sul manifest per impostazione predefinita!

Invece si deve modificare l' <archive>elemento di configurazione del maven-jar-pluginset addDefaultImplementationEntriese addDefaultSpecificationEntriesto true, in questo modo:

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

Idealmente questa configurazione dovrebbe essere inserita nell'azienda pomo in un'altra base.

La documentazione dettagliata <archive>dell'elemento è disponibile nella documentazione dell'archivio Maven .


6
purtroppo non tutti i classloader sembrano caricare queste proprietà dal file manifest (ricordo che ho avuto problemi con Tomcat proprio in questo caso).
Dwegener,

@avithan: davvero? Non ho mai avuto problemi con Tomcat con questo approccio. Inoltre, penso che un classloader che ignori il manifest probabilmente non sia conforme.
Joachim Sauer,

@JoachimSauer ok, mi sbagliavo. Attualmente sembra funzionare alla grande su HotSpot ma non funziona in modo affidabile su OpenJDK.
Riferirò

@avithan questo è rilevante per me (e non ho visto quello che riferisci) - hai ancora ricevuto informazioni dettagliate?
Thorbjørn Ravn Andersen,

4
Sfortunatamente questo non funziona se il progetto viene eseguito da Eclipse o usando "mvn exec: java".
Jaan,

77

Per seguire la risposta sopra, per a .war artefatto, ho scoperto che dovevo applicare la configurazione equivalente maven-war-plugin, anziché 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>

Questo aggiunto le informazioni sulla versione per MANIFEST.MFnegli anni del progetto .jar(incluso nel WEB-INF/libdel .war)


3
<archiveClasses> true </archiveClasses> ha provocato errori nel mio caso. Ma il problema ha ottenuto risolto stackoverflow.com/questions/14934299/...
Paul Verest

10
Quando provo questo, il mio risultato è sempre nullsebbene MANIFEST.MF nei file di guerra contenga le informazioni corrette.
thomas.mc.work

Avevo anche bisogno di aggiungerlo a maven-assembly-plugin
acheron55,

2
<archiveClasses> true </archiveClasses> non sembra correlato
Karl Kildén

1
@RafaelSimonelli l'ho rimosso <archiveClasses>true</archiveClasses>- e funziona in modo affidabile da allora.
thomas.mc.work

28

Ecco un metodo per ottenere la versione da pom.properties, tornare indietro per ottenerla dal manifest

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
Mettilo in un blocco di inizializzatore statico.
opyate

1
Buon Consiglio. Sebbene, se lo stai usando in un servlet (o .jsp), assicurati di usare getServletContext (). GetResourceAsStream invece di getClass (). GetResourceAsStream
Sandman,

3
Funziona solo quando l'applicazione viene eseguita dal vaso. Se eseguito da exec-maven-plugin (ad esempio Netbeans) la risorsa è nulla.
Leif Gruenwoldt,

Questo codice farà parte delle impostazioni predefinite della mia classe principale! Grazie!!
Wendel,

L'ho usato con la risposta di Will per un'opzione semplice e di facile manutenzione.
javydreamercsw,

3

Ho trascorso un po 'di tempo sui due approcci principali qui e non hanno funzionato per me. Sto usando Netbeans per le build, forse c'è dell'altro. Ho avuto alcuni errori e avvertimenti da Maven 3 con alcuni costrutti, ma penso che fossero facili da correggere. Nessun problema.

Ho trovato una risposta che sembra mantenibile e semplice da implementare in questo articolo su DZone:

Ho già una sottocartella risorse / config e ho chiamato il mio file: app.properties, per riflettere meglio il tipo di cose che possiamo tenere lì (come un URL di supporto, ecc.).

L'unica avvertenza è che Netbeans avverte che l'IDE deve essere filtrato. Non sono sicuro di dove / come. Non ha alcun effetto a questo punto. Forse c'è un modo per aggirarlo se devo attraversare quel ponte. Buona fortuna.


3

Sto usando maven-assembly-pluginper la mia confezione di Maven. L'uso di Apache Maven Archiver nella risposta di Joachim Sauer potrebbe anche funzionare:

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

Poiché archiever è uno dei componenti condivisi di Maven , potrebbe essere utilizzato da più plug-in di Maven Building, che potrebbero anche avere conflitti se vengono introdotti due o più plugin, inclusa la archiveconfigurazione all'interno.


2

Per farlo funzionare in Eclipse, così come in una build Maven, è necessario aggiungere le voci addDefaultImplementationEntriese addDefaultSpecificationEntriespom come descritto nelle altre risposte, quindi utilizzare il seguente codice:

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

Se la build Java inserisce le classi target in un posto diverso da "target / classi", potrebbe essere necessario regolare il valore di segmentiToRemove.


Sai se questo è per unit test che puoi System.getProperty("user.dir")/pom.xml. Sono abbastanza sicuro che lo farà anche per altre cose tranne forse per WTP.
Adam Gent,

Funzionerà solo se il tuo progetto è in una directory - se stai eseguendo un progetto basato su jarfile, la tua soluzione non funzionerà. Devi usare .getResource()o .getResourceAsStream().
Luke Hutchison,

Sì, stavo supponendo che tu abbia già controllato il vaso (ala getResource). Questo è il primo che controlli con getResource se fallisce, quindi il progetto non è stato ancora creato in un barattolo, il che significa che lo stai eseguendo da Eclipse o Maven che significa `System.getProperty (" user.dir ") / pom.xml . L'unico problema è che questo file pom non è il vero pom efficace (cioè alcune proprietà non saranno espanse) ma non è nemmeno quello che stai ottenendo con il modo Eclipse.
Adam Gent,

1

Sulla mia applicazione di avvio a molla, la soluzione dalla risposta accettata ha funzionato fino a quando non ho aggiornato di recente il mio jdk alla versione 12. Ho provato anche tutte le altre risposte e non sono riuscito a farlo funzionare.

A quel punto, ho aggiunto la riga seguente alla prima classe della mia applicazione di avvio a molla, subito dopo l'annotazione @SpringBootApplication

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

Successivamente utilizzo quanto segue per ottenere il valore dal file delle proprietà in qualsiasi classe voglio usare il suo valore e appVersionottenere la versione del progetto per me:

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

Spero che aiuti qualcuno.


Come fare lo stesso con più file pom? Voglio caricare la versione da più file pom.
THM,

0

Una soluzione semplice che è compatibile Maven e funziona per qualsiasi classe (quindi anche di terze parti):

    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

Variante Java 8 per EJB in file di guerra con progetto maven. Testato su 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!");
            }
        });
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.