在运行时获取 Maven 工件版本

Posted

技术标签:

【中文标题】在运行时获取 Maven 工件版本【英文标题】:Get Maven artifact version at runtime 【发布时间】:2011-02-12 08:49:15 【问题描述】:

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

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

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

【问题讨论】:

见***.com/a/26589696/52176 【参考方案1】:

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

您可以简单地使用getClass().getPackage().getImplementationVersion() 来获取存储在.jar 文件MANIFEST.MF 中的版本信息。 幸运的是 Maven 足够聪明不幸的是,默认情况下 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 或其他 base-pom。

&lt;archive&gt; 元素的详细文档可以在Maven Archive documentation 中找到。

【讨论】:

遗憾的是,并非每个类加载器似乎都从清单文件中加载这些属性(我记得在这种情况下 Tomcat 存在问题)。 @avithan:真的吗?使用这种方法,我从来没有遇到过 Tomcat 的问题。另外,我认为忽略清单的类加载器可能不符合要求。 @JoachimSauer 好的,我错了。目前它似乎在 HotSpot 上运行良好,但在 OpenJDK 上运行不可靠。当我得到详细信息时,我会报告 @avithan 这与我有关(我还没有看到你报告的内容)- 你得到详细信息了吗? 不幸的是,如果项目从 Eclipse 运行或使用“mvn exec:java”运行,这将不起作用。【参考方案2】:

为了跟进上面的答案,对于.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中的MANIFEST.MF(包含在.warWEB-INF/lib中)

【讨论】:

true 在我的情况下导致错误。但是问题解决了***.com/questions/14934299/… 当我尝试这个时,我的结果总是null 尽管战争文件中的 MANIFEST.MF 包含正确的信息。 我还需要将它添加到 maven-assembly-plugin true 似乎无关 @RafaelSimonelli 我已经删除了&lt;archiveClasses&gt;true&lt;/archiveClasses&gt;——从那时起它就可以可靠地工作了。【参考方案3】:

这是一种从 pom.properties 中获取版本的方法,回退到从清单中获取它

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;
 

【讨论】:

把它放在一个静态初始化块中。 好建议。虽然,如果您在 servlet(或 .jsp)中使用它,请务必使用 getServletContext().getResourceAsStream 而不是 getClass().getResourceAsStream 这仅在应用程序从 jar 运行时有效。如果从 exec-maven-plugin(例如 Netbeans)运行,则资源为空。 这段代码将成为我的主类默认值的一部分!谢谢!! 我将其与 Will 的答案一起使用,以获得直接且易于维护的选项。【参考方案4】:

我使用 maven-assembly-plugin 进行我的 maven 包装。在Joachim Sauer's answer 中使用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>

因为archiver是maven shared components之一,所以它可以被多个maven构建插件使用,如果引入两个或多个插件也会发生冲突,包括archive里面的配置。

【讨论】:

【参考方案5】:

我在这里花了一些时间研究这两种主要方法,但它们并不适合我。我正在使用 Netbeans 进行构建,可能还有更多内容。我有一些来自 Maven 3 的错误和警告以及一些构造,但我认为这些很容易纠正。没什么大不了的。

我确实在这篇关于 DZone 的文章中找到了一个看起来可维护且易于实现的答案:

Stamping Version Number and Build Time in a Properties File with Maven

我已经有一个资源/配置子文件夹,我将我的文件命名为:app.properties,以更好地反映我们可能保留在那里的东西(如支持 URL 等)。

唯一需要注意的是,Netbeans 会发出 IDE 需要过滤掉的警告。不确定在哪里/如何。在这一点上它没有效果。如果我需要过那座桥,也许有办法解决这个问题。祝你好运。

【讨论】:

【参考方案6】:

要让它在 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。 这仅在您的项目位于目录中时才有效——如果您正在运行基于 jarfiles 的项目,您的解决方案将不起作用。您需要使用.getResource().getResourceAsStream() 是的,我假设您已经检查了 jar (ala getResource)。首先你检查 getResource 是否失败,然后项目尚未构建到 jar 中,这意味着你要么从 Eclipse 或 Maven 运行它,这意味着 `System.getProperty("user.dir")/pom.xml .唯一的问题是这个 pom 文件不是真正有效的 pom(即某些属性不会被扩展),但也不是你通过 Eclipse 方式获得的那个。【参考方案7】:

在我的 Spring Boot 应用程序中,接受答案的解决方案一直有效,直到我最近将我的 jdk 更新到版本 12。也尝试了所有其他答案,但无法使其正常工作。

此时,我在我的 Spring Boot 应用程序的第一类中添加了以下行,就在注释 @SpringBootApplication 之后

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

稍后我使用以下内容从属性文件中获取我想要使用其值的任何类中的值,appVersion 将项目版本提供给我:

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

希望对某人有所帮助。

【讨论】:

如何处理多个 pom 文件?我想从多个 pom 文件中加载版本。【参考方案8】:

如果你碰巧使用 Spring Boot,你可以使用 BuildProperties 类。

以我们OpenAPI配置类中的如下sn-p为例:

@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")));
    

【讨论】:

这正是让我寻找解决运行时 Maven 细节的用例,多么方便!也许它应该在另一个问题中解决,但它仍然很方便。谢谢!【参考方案9】:

我知道这是一个很晚的答案,但我想根据this 链接分享我所做的事情:

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

这个 Advice Controller 以获取版本作为模型属性:

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;
       
    

【讨论】:

【参考方案10】:

我发现的最优雅的解决方案是来自J.Chomel:link

不需要任何带有属性的 hack。为避免将来出现链接断开的问题,我将在此处复制它:

YourClass.class.getPackage().getImplementationVersion();

而且(如果您的 jar/war 中还没有 Manifest 文件,对我来说 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 ☹)。【参考方案11】:

一个与 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&lt;&gt; 的版本,如果不存在则只返回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。我们到底应该怎么做? jar 中要获取其版本的类。例如。对于杰克逊来说,它可能是ObjectMapper.class【参考方案12】:

带有 maven 项目的 war 文件中 EJB 的 Java 8 变体。在 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!");
            
        );
    

【讨论】:

以上是关于在运行时获取 Maven 工件版本的主要内容,如果未能解决你的问题,请参考以下文章

在 Eclipse 中运行时从 pom 获取 maven 项目版本和工件 ID

如何停止 Maven 的验证阶段重建工件?

在 Jenkins 中运行 mvn 测试时 maven-surefire-plugin 的 Java 版本问题

发布到 gitlab 工件时,401 未经授权从 maven

在 Maven 中跟踪托管依赖项版本

如何删除 maven 获取的缓存本地工件?