Obtenga la versión de artefactos de Maven en tiempo de ejecución

176

He notado que en el JAR de un artefacto Maven, el atributo project.version está incluido en dos archivos:

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

¿Hay alguna forma recomendada de leer esta versión en tiempo de ejecución?

Armand
fuente

Respuestas:

265

No debería necesitar acceder a archivos específicos de Maven para obtener la información de versión de una biblioteca / clase determinada.

Simplemente puede usar getClass().getPackage().getImplementationVersion()para obtener la información de la versión que se almacena en un archivo .jar MANIFEST.MF. Afortunadamente, Maven es lo suficientemente inteligente ¡ Desafortunadamente, Maven no escribe la información correcta en el manifiesto también de forma predeterminada!

En cambio, uno tiene que modificar el <archive>elemento de configuración de maven-jar-pluginestablecer addDefaultImplementationEntriesy addDefaultSpecificationEntriespara true, de esta manera:

<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, esta configuración se debe poner en la empresa pomu otra base-pom.

La documentación detallada del <archive>elemento se puede encontrar en la documentación del Archivo Maven .

Joachim Sauer
fuente
66
lamentablemente, no todos los cargadores de clases parecen cargar estas propiedades desde el archivo de manifiesto (recuerdo haber tenido problemas con Tomcat en este caso exactamente).
dwegener
@avithan: ¿en serio? Nunca tuve un problema con Tomcat con este enfoque. Además, creo que un cargador de clases que ignora el manifiesto probablemente no sea conforme.
Joachim Sauer
@JoachimSauer ok, estaba equivocado. Actualmente parece que funciona muy bien en HotSpot pero no funciona de manera confiable en OpenJDK. Informaré cuando obtenga información detallada
dwegener
@avithan esto es relevante para mí (y no he visto lo que informas): ¿ya has recibido información detallada?
Thorbjørn Ravn Andersen
44
Desafortunadamente, esto no funciona si el proyecto se ejecuta desde Eclipse o usando "mvn exec: java".
Jaan
77

Para seguir la respuesta anterior, para un .warartefacto, descubrí que tenía que aplicar la configuración equivalente a maven-war-plugin, en lugar de 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>

Esto agregó la información de la versión MANIFEST.MFen el proyecto .jar(incluido en WEB-INF/libel .war)

Robar
fuente
3
<archiveClasses> true </archiveClasses> causó un error en mi caso. Pero el problema se resolvió stackoverflow.com/questions/14934299/…
Paul Verest
10
Cuando intento esto, mi resultado es siempre nullaunque el MANIFEST.MF en los archivos de guerra contiene la información correcta.
thomas.mc.work
También necesitaba agregarlo a maven-assembly-plugin
acheron55
2
<archiveClasses> true </archiveClasses> parece no estar relacionado
Karl Kildén
1
@RafaelSimonelli He eliminado <archiveClasses>true</archiveClasses>, y funciona de manera confiable desde entonces.
thomas.mc.work
28

Aquí hay un método para obtener la versión de pom.properties, recurriendo a obtenerla del manifiesto

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;
} 
mysomic
fuente
2
Ponga esto en un bloque inicializador estático.
Opiato
1
Buen consejo. Aunque, si está usando esto en un servlet (o .jsp), asegúrese de usar getServletContext (). GetResourceAsStream en lugar de getClass (). GetResourceAsStream
Sandman
3
Esto solo funciona cuando la aplicación se ejecuta desde el jar. Si se ejecuta desde exec-maven-plugin (por ejemplo, Netbeans), el recurso es nulo.
Leif Gruenwoldt
¡Este código será parte de mis valores predeterminados de clase principal! ¡¡Gracias!!
Wendel
Utilicé esto con la respuesta de Will para una opción sencilla y fácil de mantener.
javydreamercsw
3

Pasé algún tiempo en los dos enfoques principales aquí y no funcionaron para mí. Estoy usando Netbeans para las compilaciones, puede que haya más cosas allí. Tuve algunos errores y advertencias de Maven 3 con algunas construcciones, pero creo que fueron fáciles de corregir. No es problema.

Encontré una respuesta que parece fácil de mantener y de implementar en este artículo en DZone:

Ya tengo una subcarpeta recursos / config, y llamé a mi archivo: app.properties, para reflejar mejor el tipo de cosas que podemos guardar allí (como una URL de soporte, etc.).

La única advertencia es que Netbeans advierte que el IDE necesita filtrado. No estoy seguro de dónde / cómo. No tiene ningún efecto en este momento. Quizás haya una solución para eso si necesito cruzar ese puente. La mejor de las suertes.

será
fuente
3

Estoy usando maven-assembly-pluginpara mi empaque maven. El uso de Apache Maven Archiver en la respuesta de Joachim Sauer también podría funcionar:

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

Debido a que archiever es uno de los componentes compartidos de maven , podría ser utilizado por múltiples complementos de construcción de maven, lo que también podría tener conflictos si se introducen dos o más complementos, incluida la archiveconfiguración interna.

千 木 郷
fuente
2

Para que esto funcione en Eclipse, así como en una compilación de Maven, debe agregar las entradas addDefaultImplementationEntriesy addDefaultSpecificationEntriespom como se describe en otras respuestas, luego use el siguiente código:

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

Si su compilación Java coloca clases de destino en otro lugar que no sea "target / classes", entonces es posible que deba ajustar el valor de segmentosToRemove.

Luke Hutchison
fuente
Sabes si esto es para pruebas unitarias, puedes simplemente System.getProperty("user.dir")/pom.xml. Estoy bastante seguro de que también lo hará para otras cosas, excepto tal vez no para WTP.
Adam Gent el
Eso solo funcionará si su proyecto está en un directorio; si está ejecutando un proyecto basado en archivos jar, su solución no funcionará. Necesitas usar .getResource()o .getResourceAsStream().
Luke Hutchison el
Sí, suponía que ya has verificado el jar (ala getResource). Eso es lo primero que verifica con getResource si eso falla, entonces el proyecto aún no se ha integrado en un jar, lo que significa que lo está ejecutando desde Eclipse o Maven, que significa `System.getProperty (" user.dir ") / pom.xml . El único problema es que este archivo pom no es el verdadero pom efectivo (es decir, algunas propiedades no se expandirán) todavía, pero tampoco es el que está obteniendo con la forma Eclipse.
Adam Gent
1

En mi aplicación de arranque de primavera, la solución de la respuesta aceptada funcionó hasta que recientemente actualicé mi jdk a la versión 12. Intenté todas las otras respuestas también y no pude hacer que eso funcionara.

En ese momento, agregué la línea a continuación a la primera clase de mi aplicación de arranque de primavera, justo después de la anotación @SpringBootApplication

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

Más tarde, uso lo siguiente para obtener el valor del archivo de propiedades en la clase en la que quiero usar su valor y me appVersionda la versión del proyecto:

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

Espero que ayude a alguien.

Reema
fuente
¿Cómo hacer lo mismo con múltiples archivos pom? Quiero cargar la versión de múltiples archivos pom.
THM
0

Una solución simple que es compatible con Maven y funciona para cualquier clase (por lo tanto, también de terceros):

    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();
    }
rdehuyss
fuente
-1

Variante de Java 8 para EJB en archivo war con proyecto maven. Probado en 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!");
            }
        });
    }
}
onderbewustzijn
fuente