在运行时获取Maven构件版本

199

我注意到在一个Maven构件的JAR文件中,project.version属性在两个文件中都被包含:

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

有没有一种推荐的方法可以在运行时读取这个版本?


请见https://dev59.com/LWgu5IYBdhLWcg3wMkbF#26589696 - Leif Gruenwoldt
13个回答

293

您不需要访问与Maven相关的文件来获取任何给定库/类的版本信息。

您可以简单地使用getClass().getPackage().getImplementationVersion()来获取存储在.jar文件的MANIFEST.MF中的版本信息。不幸的是,Maven默认情况下不会将正确的信息写入清单!

相反,您需要修改maven-jar-plugin<archive>配置元素,将addDefaultImplementationEntriesaddDefaultSpecificationEntries设置为true,就像这样:

<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>
理想情况下,这个配置应该被放在公司的POM或另一个基础POM中。有关<archive>元素的详细文档可以在Maven存档文档中找到。

7
遗憾的是,并非每个类加载器都能从清单文件中加载这些属性(我记得在Tomcat中就遇到了这种问题)。 - dwegener
@JoachimSauer 好的,我错了。目前看来它在HotSpot上运行良好,但在OpenJDK上不可靠。我会在获取详细信息后回报。 - dwegener
5
如果项目是从Eclipse或使用"mvn exec:java"运行,则此方法不起作用。 - Jaan
maven-assembly-plugin支持许多与maven-jar-plugin相同的选项,但会默默忽略它们。 - Kevin Krumwiede
这也仅在您控制正在调查的依赖项时才有效 - 即能够更改jar插件配置。 - Antony Stubbs
显示剩余7条评论

81

针对上面的答案进行跟进,在处理 .war 文件时,我发现需要应用等效的配置到 maven-war-plugin 而非 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>

这将版本信息添加到项目的 .jar(包含在.warWEB-INF / lib中)的MANIFEST.MF中。


3
"<archiveClasses>true</archiveClasses>" 在我的情况下导致了错误,但问题已得到解决。 https://dev59.com/pG7Xa4cB1Zd3GeqPrpgA - Paul Verest
14
当我尝试这样做时,我的结果总是null,尽管war文件中的MANIFEST.MF包含正确的信息。 - thomas.mc.work
3
<archiveClasses>true</archiveClasses> 看起来与程序编程无关。 - Karl Kildén
@thomas.mc.work 我这里也有同样的问题,仍然存在。你解决了吗? - Rafael Simonelli
1
@RafaelSimonelli 自从我移除了 <archiveClasses>true</archiveClasses>,它就一直可靠地运行。 - thomas.mc.work
显示剩余2条评论

31

这是一个从pom.properties获取版本号的方法,如果无法获取,则会回退到从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
将此代码放入静态初始化块中。 - opyate
1
好建议。但是,如果您在servlet(或.jsp)中使用它,请确保使用getServletContext()。getResourceAsStream而不是getClass()。getResourceAsStream。 - Sandman
4
只有当应用程序从 jar 中运行时才有效。如果是从 exec-maven-plugin(例如 Netbeans)中运行,则该资源为 null。 - Leif Gruenwoldt
这段代码将成为我的主类默认值的一部分!谢谢!! - Wendel
我将这个与威尔的答案一起使用,以获得一个简单直接且易于维护的选项。 - javydreamercsw
既然我们正在讨论WAR,我不得不替换该行代码: InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artifact/pom.properties"); 为: InputStream is = request.getServletContext().getResourceAsStream("/META-INF/maven/com.my.group/my-artifact/pom.properties"); 另请参见https://dev59.com/Z3I95IYBdhLWcg3wxA8- - John Mikic

12
如果您使用Spring Boot,您可以使用BuildProperties类。
以我们的OpenAPI配置类中的以下代码片段为例:
@Configuration
@RequiredArgsConstructor // <- lombok
public class OpenApi {

    private final BuildProperties buildProperties; // <- you can also autowire it

    @Bean
    public OpenAPI yourBeautifulAPI() {
        return new OpenAPI().info(new Info()
            .title(buildProperties.getName())
            .description("The description")
            .version(buildProperties.getVersion())
            .license(new License().name("Your company")));
    }
}

1
这正是让我寻找运行时Maven细节解决方案的用例,多么方便!也许应该在另一个问题中解决,但仍然很方便。谢谢! - Xerz
你可能需要在pom.xml中添加构建信息,就像@chris-sim的回答中提到的那样。 - Guilherme Taffarel Bergamin

9

我正在使用maven-assembly-plugin对我的maven打包进行操作。在Joachim Sauer的回答中,使用Apache Maven Archiver也可以实现:

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

由于archiever是Maven共享组件之一,因此它可以被多个Maven构建插件使用。如果引入了两个或多个插件(包括内部archive配置),可能会产生冲突。


7

我知道回答晚了,但我想分享一下我按照这个链接所做的事情:

我在 pom.xml 中添加了以下代码:

        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>build-info</id>
                    <goals>
                        <goal>build-info</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

使用这个建议控制器将版本作为模型属性获取:

import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;

@ControllerAdvice
public class CommonControllerAdvice
{
       @Autowired
       BuildProperties buildProperties;
    
       @ModelAttribute("version")
       public String getVersion() throws IOException
       {
          String version = buildProperties.getVersion();
          return version;
       }
    }

如果我想要运行Maven,有什么解决方案吗? - Sonn

4

一个简单的解决方案,适用于任何(包括第三方)类,并且与Maven兼容:

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

这是一个没有使用Optional<>的版本,如果不存在,它只会返回null(用于快速调试/转储):
    private static 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();
                return attributes.getValue("Bundle-Version");
            }
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

什么是clazz?我们应该传递什么参数? - itro
你想获取版本的jar包中的类。例如,对于Jackson,它可以是ObjectMapper.class - rdehuyss

3
为了在Eclipse中以及在Maven构建中运行此项,您应该按照其他回复中描述的方式添加addDefaultImplementationEntriesaddDefaultSpecificationEntries pom项目条目,然后使用以下代码:
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;
}

如果您的Java构建将目标类放在除"target/classes"以外的其他地方,则可能需要调整segmentsToRemove的值。

如果这是用于单元测试,你可以使用 System.getProperty("user.dir")/pom.xml。我相当确定它也适用于其他事情,除了可能不适用于 WTP。 - Adam Gent
只有当您的项目在目录中时,这才有效--如果您正在运行基于jar文件的项目,则您的解决方案将无法工作。您需要使用.getResource().getResourceAsStream() - Luke Hutchison
是的,我假设您已经检查了jar文件(使用getResource)。首先,您可以使用getResource进行检查,如果失败,则表示项目尚未构建为jar文件,这意味着您要么从Eclipse或Maven运行它,这意味着`System.getProperty("user.dir")/pom.xml。唯一的问题是,此pom文件不是真正的有效pom(即某些属性将不会扩展),但是您使用Eclipse方式获取的也不是真正的有效pom。 - Adam Gent

3
我在这两个主要方法上花费了一些时间,但它们对我没有用。我正在使用Netbeans进行构建,可能还有其他的问题。Maven 3给出了一些结构方面的错误和警告,但我认为这些很容易纠正。没什么大不了的。
我在DZone的一篇文章中找到了一个看起来可维护且简单实用的答案:
在Maven中使用Stamping Version Number and Build Time in a Properties File
我已经有了一个resources/config子文件夹,并将我的文件命名为app.properties,以更好地反映我们可能在那里保存的东西(如支持URL等)。
唯一需要注意的是,Netbeans会发出一个警告,说IDE需要过滤掉。不确定在哪里/如何操作。现在它没有影响。如果我需要跨越这个障碍,或许有一个解决办法。祝你好运。

2
我找到的最优雅的解决方案是来自J.Chomel的一个: 链接 不需要使用任何属性的黑客技巧。为了避免将来出现链接损坏的问题,我会在这里进行复制。
YourClass.class.getPackage().getImplementationVersion();

如果您的jar / war中尚未有清单文件,(例如我使用的Intellij Idea的Maven已经包含了它们),您还需要在pom.xml中进行小的更改:

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
     ...
      <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
                <archive>
                    <manifest>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    ...

当您能够添加这些条目时,它会很好地工作,但是当您想要现有工件的版本却未定义它们时(例如 Mockito 定义了 Bundle-Version 但未定义 Implementation-Version ☹),就不能正常工作了。 - mirabilos
你有没有解决方案来获取正在运行的Maven配置文件ID? - Sonn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接