avatar

Ryan's Blog

The first step is always the hardest.

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于
  • 工具
Home Maven 构建产物添加时间戳:maven.build.timestamp、build-helper 与可复现构建说明
文章

Maven 构建产物添加时间戳:maven.build.timestamp、build-helper 与可复现构建说明

Posted 2021-11-25 Updated 7 days ago
By Ryan Chen
23~29 min read

前言

在 Maven 项目发布或手工交付时,有时希望构建产物文件名带上时间戳,例如:

order-service-1.0.0-20260629173000.jar

这样做的好处是:

  • 方便区分不同构建批次;
  • 便于排查线上部署的是哪一次构建;
  • 临时交付、测试环境部署时更直观;
  • 可以和 CI/CD 构建号、Git Commit ID 一起提升可追踪性。

但也要注意:时间戳构建和可复现构建存在天然冲突。如果每次构建产物名和归档内容都带当前时间,那么同一份源码重复构建就不容易得到完全一致的结果。

方案一:使用 Maven 内置 maven.build.timestamp

Maven 内置了 ${maven.build.timestamp} 属性,表示构建开始时间。

可以在 pom.xml 中设置时间格式:

<properties>
    <maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
</properties>

然后在 build.finalName 中使用:

<build>
    <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
</build>

构建后产物名类似:

order-service-1.0.0-20260629173000.jar

需要注意:Maven 官方文档说明 ${maven.build.timestamp} 使用的是 UTC 时间。因此如果你的服务器位于中国时区,看到的时间可能和本地时间相差 8 小时。

方案二:使用 build-helper-maven-plugin 指定时区

如果希望生成指定时区的时间戳,可以使用 build-helper-maven-plugin 的 timestamp-property 目标。

示例:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>timestamp-property</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>timestamp-property</goal>
                    </goals>
                    <configuration>
                        <name>build.time</name>
                        <pattern>yyyyMMddHHmmss</pattern>
                        <timeZone>Asia/Shanghai</timeZone>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>

    <finalName>${project.artifactId}-${project.version}-${build.time}</finalName>
</build>

这里要注意变量名要一致:

<name>build.time</name>

对应使用:

${build.time}

如果写成 <name>current.time</name>,后面就应该使用 ${current.time}。变量名不一致会导致 finalName 中取不到期望的值。

方案三:使用 buildnumber-maven-plugin

buildnumber-maven-plugin 也可以生成时间戳或构建编号。

示例:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>buildnumber-maven-plugin</artifactId>
    <version>3.2.1</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>create-timestamp</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <timestampFormat>yyyyMMddHHmmss</timestampFormat>
        <timestampPropertyName>buildTimestamp</timestampPropertyName>
    </configuration>
</plugin>

然后使用:

<build>
    <finalName>${project.artifactId}-${project.version}-${buildTimestamp}</finalName>
</build>

注意插件名是 buildnumber-maven-plugin,不是 buildnubmer-maven-plugin。

方案四:使用 CI/CD 构建号或 Git Commit ID

在正式发布流程中,比单纯时间戳更推荐的是:

版本号 + 构建号 + Git Commit ID

例如:

order-service-1.0.0-build.1024-a1b2c3d.jar

CI/CD 中可以通过环境变量传入:

mvn clean package \
  -Dbuild.number=${BUILD_NUMBER} \
  -Dgit.commit=${GIT_COMMIT}

POM 中使用:

<build>
    <finalName>${project.artifactId}-${project.version}-build.${build.number}-${git.commit}</finalName>
</build>

这种方式更适合 Jenkins、GitLab CI、GitHub Actions 等流水线,因为构建号和 Commit ID 比本地时间更容易追踪源码版本。

如果你在使用 Jenkins / GitLab 做发布,可以参考:Jenkins 基于 GitLab 分支与 Tag 的自动化打包发布实践。

finalName 只影响产物文件名

finalName 只影响 target 目录下生成的文件名,不等同于 Maven 坐标版本。

例如:

<groupId>com.example</groupId>
<artifactId>order-service</artifactId>
<version>1.0.0</version>

即使你设置:

<finalName>order-service-1.0.0-20260629173000</finalName>

Maven 坐标仍然是:

com.example:order-service:1.0.0

如果发布到 Maven 仓库,真正用于依赖解析的是 groupId、artifactId、version,而不是文件名里的时间戳。

时间戳与可复现构建的取舍

Maven 支持可复现构建,常用属性是:

<properties>
    <project.build.outputTimestamp>2026-06-29T17:30:00Z</project.build.outputTimestamp>
</properties>

它用于控制归档文件中的输出时间戳,帮助同一份源码在相同环境下构建出可重复的产物。

因此需要区分两件事:

目标 推荐做法
临时交付、测试环境区分包 可以在 finalName 中加时间戳。
正式发布、Maven 仓库依赖 优先使用语义化版本、构建号、Git Commit ID。
追求可复现构建 避免使用当前时间作为产物内容的一部分,使用固定 outputTimestamp。

完整示例

下面是一个使用 build-helper-maven-plugin 生成上海时区时间戳的示例:

<project>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <build>
        <finalName>${project.artifactId}-${project.version}-${build.time}</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <id>timestamp-property</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>timestamp-property</goal>
                        </goals>
                        <configuration>
                            <name>build.time</name>
                            <pattern>yyyyMMddHHmmss</pattern>
                            <timeZone>Asia/Shanghai</timeZone>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

执行:

mvn clean package

产物示例:

target/order-service-1.0.0-20260629173000.jar

常见问题

1. 为什么时间少了 8 小时?

如果使用 ${maven.build.timestamp},它是 UTC 时间。需要本地时区时,可以使用 build-helper-maven-plugin 设置 timeZone。

2. 为什么 ${timestamp} 没有值?

检查插件生成的属性名是否叫 timestamp。如果配置的是:

<name>build.time</name>

就应该使用:

${build.time}

3. 是否应该把时间戳写进 version?

一般不建议。version 是 Maven 坐标的一部分,会影响依赖解析和仓库管理。时间戳更适合用于 finalName、CI 构建元数据或发布说明。

小结

Maven 构建产物加时间戳有三种常见方式:

  1. 简单场景使用 ${maven.build.timestamp};
  2. 需要指定时区时使用 build-helper-maven-plugin;
  3. 正式 CI/CD 发布优先使用构建号和 Git Commit ID。

如果目标是可复现构建,则要谨慎使用当前时间,避免每次构建都产生不可重复的产物。

参考资料

  • Maven 官方文档:Introduction to the POM
  • Maven 官方文档:Configuring for Reproducible Builds
  • MojoHaus build-helper-maven-plugin:timestamp-property
  • MojoHaus buildnumber-maven-plugin
  • Jenkins 基于 GitLab 分支与 Tag 的自动化打包发布实践
Java
Maven Java 构建工具 时间戳 CI/CD 可复现构建 版本管理
License:  CC BY 4.0
Share

Further Reading

Sep 12, 2024

RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析

本文基于 RocketMQ 4.x 经典架构,梳理 NameServer、Broker、Producer、Consumer、Remoting 与 Store 模块,结合消息轨迹、存储模型、FastFail、事务消息和高可用部署,总结高并发场景下的实践要点。

Aug 16, 2023

Java List 核心数据结构解析:ArrayList、LinkedList 与线程安全

系统梳理 Java List 接口、ArrayList 动态数组、LinkedList 双向链表、容量扩容、遍历与 fail-fast 机制,并对比 synchronizedList、CopyOnWriteArrayList、Vector 等线程安全方案的适用场景。

Apr 24, 2023

数组基础详解:概念、存储结构与常用操作

从数组的连续存储、下标访问、Java 数组对象、一维与多维数组、遍历、查找、插入、删除、复制、排序和 Arrays 工具类出发,系统梳理数据结构学习中的数组基础。

OLDER

RocketMQ DLedger 高可用集群搭建与故障排查:自动选主、Docker 网络与常见踩坑

NEWER

Linux 常用命令速查:磁盘、日志、进程、网络、Docker 与文件排查实践

Recently Updated

  • Agent 架构设计原则:Router、Runtime 与 Business Script 的职责划分
  • RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析
  • Redis 核心概念、数据结构与高可用架构详解
  • B+树原理与 MySQL InnoDB 索引机制解析
  • MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案

Trending Tags

RocketMQ Windows Feign Docker Zipkin SonarQube OkHttp HttpClient API 性能优化

Contents

©2026 Ryan's Blog. Some rights reserved. · 粤ICP备2022031588号