Obter a versão do artefato do Maven em tempo de execução

176

Percebi que no JAR de um artefato Maven, o atributo project.version está incluído em dois arquivos:

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

Existe uma maneira recomendada de ler esta versão em tempo de execução?

Armand
fonte
Veja stackoverflow.com/a/26589696/52176
Leif Gruenwoldt

Respostas:

265

Você não precisa acessar arquivos específicos do Maven para obter as informações da versão de qualquer biblioteca / classe.

Você pode simplesmente usar getClass().getPackage().getImplementationVersion()para obter as informações da versão armazenadas em um arquivo .jar MANIFEST.MF. Felizmente, o Maven é inteligente o suficiente Infelizmente, por padrão, o Maven não grava as informações corretas no manifesto!

Em vez disso, é necessário modificar o <archive>elemento de configuração de maven-jar-pluginto set addDefaultImplementationEntriesand addDefaultSpecificationEntriesto true, assim:

<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, essa configuração deve ser colocada na empresa pomou em outro pom base.

A documentação detalhada do <archive>elemento pode ser encontrada na documentação do Maven Archive .

Joachim Sauer
fonte
6
infelizmente, nem todo carregador de classes parece carregar essas propriedades do arquivo de manifesto (lembro-me de ter problemas com o Tomcat exatamente nesse caso).
dwegener
@avithan: sério? Eu nunca tive um problema com o Tomcat com essa abordagem. Além disso, acho que um carregador de classes que ignora o manifesto provavelmente não está em conformidade.
Joachim Sauer
@JoachimSauer ok, eu estava errado. Atualmente, parece que funciona muito bem no HotSpot, mas não funciona de maneira confiável no OpenJDK. Vou relatar quando tiver informações detalhadas
dwegener
@ avithan isso é relevante para mim (e eu não vi o que você denuncia) - você já recebeu informações detalhadas?
Thorbjørn Ravn Andersen 13/03
4
Infelizmente, isso não funciona se o projeto for executado no Eclipse ou usando "mvn exec: java".
Jaan
77

Para acompanhar a resposta acima, para um .warartefato, descobri que tinha de aplicar a configuração equivalente maven-war-plugin, em vez 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>

Isso, somado as informações de versão para MANIFEST.MFnos do projeto .jar(incluído no WEB-INF/libdo .war)

Roubar
fonte
3
<archiveClasses> true </archiveClasses> causou erro no meu caso. Mas o problema foi resolvido stackoverflow.com/questions/14934299/...
Paul Verest
10
Quando tento isso, meu resultado é sempre, nullembora o MANIFEST.MF nos arquivos de guerra contenha as informações corretas.
thomas.mc.work
Eu também precisava para adicioná-lo maven-assembly-plugin
acheron55
2
<archiveClasses> true </archiveClasses> parece não ter relação
alguma
1
@RafaelSimonelli eu removi <archiveClasses>true</archiveClasses>- e funciona de maneira confiável desde então.
precisa saber é o seguinte
28

Aqui está um método para obter a versão do pom.properties, voltando a obtê-la do manifesto

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;
} 
místico
fonte
2
Coloque isso em um bloco estático de inicialização.
opyate
1
Bom conselho. Embora, se você estiver usando isso em um servlet (ou JSP), certifique-se de usar getServletContext () getResourceAsStream vez de getClass () getResourceAsStream..
Sandman
3
Isso funciona apenas quando o aplicativo é executado a partir do jar. Se executado a partir do exec-maven-plugin (por exemplo, Netbeans), o recurso é nulo.
Leif Gruenwoldt 27/10/2014
Este código fará parte dos meus padrões de classe principal! Obrigado!!
Wendel
Usei isso com a resposta de Will para uma opção direta e fácil de manter.
Javydreamercsw
3

Passei algum tempo nas duas principais abordagens aqui e elas não deram certo para mim. Estou usando o Netbeans para as compilações, pode haver mais acontecendo lá. Eu tive alguns erros e avisos do Maven 3 com algumas construções, mas acho que foram fáceis de corrigir. Nada demais.

Encontrei uma resposta que parece sustentável e simples de implementar neste artigo no DZone:

Eu já tenho uma subpasta resources / config e nomeei meu arquivo: app.properties, para refletir melhor o tipo de coisa que podemos manter lá (como uma URL de suporte etc.).

A única ressalva é que o Netbeans avisa que o IDE precisa ser filtrado. Não sabe onde / como. Não tem efeito neste momento. Talvez haja uma solução para isso, se eu precisar atravessar a ponte. Boa sorte.

vai
fonte
3

Estou usando maven-assembly-pluginpara minha embalagem maven. O uso do Apache Maven Archiver na resposta de Joachim Sauer também pode 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>

Como o archiever é um dos componentes compartilhados do maven , ele pode ser usado por vários plugins de construção do maven, que também podem entrar em conflito se forem introduzidos dois ou mais plugins, incluindo a archiveconfiguração interna.

千 木 郷
fonte
2

Para que isso seja executado no Eclipse, bem como em uma construção do Maven, você deve adicionar as entradas addDefaultImplementationEntriese addDefaultSpecificationEntriespom conforme descrito em outras respostas e, em seguida, usar o seguinte 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;
}

Se sua construção Java coloca as classes de destino em algum lugar que não seja "target / classes", talvez seja necessário ajustar o valor de segmentosToRemove.

Luke Hutchison
fonte
Você sabe se isso é apenas para testes de unidade System.getProperty("user.dir")/pom.xml. Tenho certeza de que o fará para outras coisas, exceto talvez não para WTP.
Adam Gent
Isso só funcionará se o seu projeto estiver em um diretório - se você estiver executando um projeto baseado em jarfiles, sua solução não funcionará. Você precisa usar .getResource()ou .getResourceAsStream().
Luke Hutchison
Sim, eu estava assumindo que você já verificou o jar (ala getResource). Primeiro, verifique com getResource se isso falhar, mas o projeto ainda não foi incorporado em um jar, o que significa que você o está executando no Eclipse ou Maven, o que significa `System.getProperty (" user.dir ") / pom.xml . O único problema é que esse arquivo pom não é o verdadeiro pom efetivo (ou seja, algumas propriedades não serão expandidas), mas ainda não é o que você está obtendo com o modo Eclipse.
Adam Gent
1

No meu aplicativo de inicialização da primavera, a solução da resposta aceita funcionou até que eu atualizei recentemente meu jdk para a versão 12. Tentei todas as outras respostas também e não consegui fazê-la funcionar.

Nesse ponto, adicionei a linha abaixo à primeira classe do meu aplicativo de inicialização por primavera, logo após a anotação @SpringBootApplication

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

Posteriormente, uso o abaixo para obter o valor do arquivo de propriedades em qualquer classe que desejar usar seu valor e appVersionobter a versão do projeto para mim:

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

Espero que ajude alguém.

Reema
fonte
Como fazer o mesmo com vários arquivos pom? Quero carregar a versão de vários arquivos pom.
THM 28/02
0

Uma solução simples que é compatível com Maven e funciona para qualquer classe (portanto também de terceiros):

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

Variante Java 8 para EJB no arquivo war com projeto maven. Testado no 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
fonte