ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JReleaser实战:Java项目发布自动化从入门到落地

JReleaser实战:Java项目发布自动化从入门到落地 每次发版都是一场小型的杂技表演。我接手的一个 Java 项目从“代码合并完”到“用户能下载到新版本”中间要经历改版本号、打 tag、生成变更日志、编译打包、算校验和、签 GPG、推到 GitHub Releases、再上传到 Maven 仓库这一整套流程。最夸张的一次我的发布脚本膨胀到了 200 多行 shell结果发布到一半发现忘了生成校验和文件只能灰溜溜地删掉 Release 重来。后来我把这套流程整体切换到了 JReleaser情况才真正好转——它不是又一个“帮你发个版”的玩具脚本而是一个把发布流程标准化、可复核、可自动化的开源发布工具。这篇博客就以我迁移到一个 JReleaser 项目的完整过程为线索讲清楚它的核心机制、最小配置、常用参数以及我在真实发布中踩过的五个坑和完整排查链路。如果你还在用手工步骤或者维护一坨发布脚本这篇文章应该能帮你省下不少时间。JReleaser 的官方定位是“release automation tool for Java projects”但它实际能做的事情比名字看起来更宽。它不绑定 Maven 或 Gradle你完全可以把它当成一个独立的命令行工具跑在本地也可以跑在 CI 里。它能把发布这件事拆成几个清晰的阶段assemble 负责打出可分发的归档或原生镜像changelog 从 git 历史生成变更日志release 把产物和日志一起推到 GitHub、GitLab、Gitea 等平台announce 负责发通知deploy 处理上传到 Maven 中央仓库这类包源。下面我从实际使用的角度把这套工具怎么用、怎么配、坑在哪里一次性讲透。1. JReleaser 到底帮你省掉了哪些发布环节1.1 手动发布到底有多折腾先算一笔账。一次标准的 Java 项目发版至少包含下面这些动作更新项目版本号、修改 changelog 草稿、创建 git tag、推 tag、执行构建命令产出 jar 包、计算 SHA-256 校验和、用 GPG 给 jar 签名、到 GitHub 网页上创建 Release、关联 tag、粘贴 changelog、上传多个附件文件、如果还有 Docker 镜像或 Homebrew 包又是一轮额外操作。这一套做下来熟练的人也要花掉三十分钟到一小时而且每一步都依赖“人记得住”。更麻烦的是这些步骤之间往往有隐含依赖。比如你忘了先推 tagGitHub Release 就关联不上正确的 commit你换了台机器发布GPG key 没导出签名步骤直接失败你在本地构建的 jar 和 CI 上构建的 jar 内容不一致用户下载回去发现和源码对不上。我见过最痛的一次是同事手动发版时漏掉了校验和用户在下载页看到 jar 和 asc 文件唯独没有 sha256只能干等维护者补传。这些问题本质上不是“某个命令不会写”而是流程没有结构化。1.2 JReleaser 覆盖的发布链路JReleaser 做的事情就是把上述这些零散动作收敛成一个声明式的发布流水线。它本身是一个 Java CLI 工具下载解压后就能跑不需要改动你现有的构建脚本。它会读取一个jreleaser.yml配置文件按照里面声明的内容依次执行各个阶段。它的核心价值在于几个设计选择。第一所有发布参数都写在配置文件里代码评审可以看到“这次发版到底会做什么”第二它提供完善的 dryrun 模式可以先在本地模拟一次完整发布把将要执行的 git 命令、HTTP 请求、文件上传清单全部打印出来确认无误后再真正执行第三它不锁定平台GitHub、GitLab、Gitea、Codeberg 都支持同一个配置换一下 host 段落就能用在不同代码托管平台第四它和构建工具解耦无论项目用 Maven、Gradle 还是纯手动打包JReleaser 只关心你产出的文件在哪里。这里要特别强调一下 dryrun 的价值。我以前用脚本发布最怕的就是“脚本本身有 bug但发布前发现不了”。JReleaser 的 dryrun 输出非常细连“将要 POST 到哪个 API 地址、携带哪些参数”都会打出来相当于每次发版前先做一次完整的发布会演练。我现在的习惯是任何一次发布先跑jreleaser release --dryrun逐行看输出再跑正式命令。1.3 和常规发布脚本、CI 内嵌 action 的对比很多团队现在的做法是在 GitHub Actions 里写一个 release workflow用softprops/action-gh-release这类 action 直接创建 Release 并上传文件。这种方式对“只发布到 GitHub 一个平台”的小项目来说够用但一旦涉及多平台、多产物、签名、上传到额外包源配置文件就开始失控。我整理过一个对比表格方便你判断自己到底该用哪种方式发布方式能覆盖的环节主要短板手工 curl 脚本创建 Release、上传附件没有 changelog 生成、签名、校验和逻辑步骤脆弱依赖人工记忆CI 内嵌专用 action发布到单一平台换 Git 托管平台要重写多产物、多平台支持弱流程逻辑散落在 workflow 里JReleaserassemble、changelog、release、announce、deploy 全链路需要学习配置结构初次上手有成本但学完后一套配置到处用我并不是说所有项目都该立刻迁移到 JReleaser。个人玩具项目用 action 完全没问题。但如果你维护的项目需要定期发版、有多个产物文件、或者要同步发布到 GitHub 和 Maven 中央仓库那 JReleaser 的投资回报率非常高。一套jreleaser.yml写好后本地能跑CI 也能跑换机器、换人、换平台都不影响。2. 五分钟跑通第一次发布最小配置实测2.1 安装与版本验证JReleaser 的安装不复杂。先去它的 GitHub Releases 页面下载对应系统的分发包解压到本地目录比如~/opt/jreleaser然后把bin目录加入PATH。它要求本机有 JDK老版本在 JDK 8 上能跑但我建议直接用 JDK 11 或 17新版本对现代 JDK 的支持更完整。jreleaser --version看到版本号输出就算装好了。这里要注意JReleaser 是一个独立的 CLI不是 Maven 插件也不是 Gradle 插件所以它不会侵入你的pom.xml或build.gradle。这一点我很喜欢因为很多项目不愿意为了一个发布工具去改构建脚本JReleaser 可以完全独立存在。2.2 最小可用的 jreleaser.yml假设你的项目用 Gradle 构建产出物在build/libs/demo-app-1.0.0.jar你希望发布到 GitHub。那么一个最小可用的配置长这样project: name: demo-app version: 1.0.0 description: A demo application released with JReleaser website: https://example.com authors: - Alice license: Apache-2.0 java: groupId: com.example artifactId: demo-app release: github: owner: your-github-account name: demo-app tagName: v{{projectVersion}} overwrite: false files: artifacts: - path: build/libs/demo-app-{{projectVersion}}.jar逐个字段解释一下。project段定义了项目元数据其中name和version是整个配置的核心后面很多地方都会用{{projectName}}、{{projectVersion}}这样的模板变量来引用它们。release.github段声明了发布目标是 GitHubowner是你的 GitHub 用户名或组织名name是仓库名tagName是发布时使用的 git tag 格式。这里我特意写了v{{projectVersion}}因为很多项目的 tag 习惯带v前缀而project.version本身通常不带。files.artifacts声明了要作为 Release 附件上传的文件路径路径里的{{projectVersion}}会自动替换成1.0.0。这个配置跑起来的前提是你的项目已经构建出 jar并且代码已经推送到 GitHub 仓库。2.3 先用 dryrun 把流程完整演练一遍第一次发布我强烈建议先用 dryrun 模式演练。在项目根目录执行jreleaser release --dryrunJReleaser 会读取jreleaser.yml解析配置然后模拟整个发布过程。你会看到它打印出将要执行的 git 操作、将要访问的 GitHub API 地址、将要上传的文件清单、changelog 的生成结果。注意观察几点tagName 是否正确解析成v1.0.0文件路径build/libs/demo-app-1.0.0.jar是否能找到changelog 里有没有内容。dryrun 模式下所有 HTTP 请求都是假的不会真的在 GitHub 上创建 Release所以随便跑跑不坏任何东西。我见过不少人在这一步直接跳过结果正式发布时才发现路径写错或者 tag 名不符合预期。dryrun 输出的信息密度非常高值得逐行读一遍尤其是文件解析路径和 API 请求摘要这两段。2.4 正式发布与验证dryrun 确认没问题后接着做正式发布。首先需要准备一个 GitHub Personal Access Token权限至少要包含repo范围如果你用 fine-grained token需要勾选Contents: Read and write权限。然后把 token 设置到环境变量里export JRELEASER_GITHUB_TOKENghp_xxx再执行jreleaser release发布完成后去 GitHub 仓库的 Releases 页面验证三件事版本号是否正确jar 附件是否上传成功changelog 是否正常展示。另外去 git tag 列表里确认v1.0.0这个 tag 已经存在。3. 核心配置逐项拆解版本、tag、变更日志与产物上传3.1 版本号与 git tag 的联动逻辑用 JReleaser 项目最需要先理解的一点是project.version和 git tag 之间的关系。JReleaser 默认情况下会使用配置里的project.version来计算默认 tag但具体行为和你指定的tagName模板有关。很多项目第一次跑发布失败就是因为 tag 和版本号不匹配。我建议从一开始就把 tag 规则固定下来。比如统一用v{{projectVersion}}作为 tag 格式那么版本号1.0.0对应的 tag 就是v1.0.0。在发布前手动打 taggit tag v1.0.0 git push origin v1.0.0然后执行 JReleaser 发布它会用这个已存在的 tag 创建 Release。有一点值得注意JReleaser不会自动帮你打 tag如果当前仓库没有匹配的 tag它会在命令执行时直接报错退出。这个设计本身是稳健的——它避免了把一次没有 tag 的提交发布成正式版本。但也意味着“打 tag、推 tag”这两个动作必须是你发布检查单里的固定步骤。如果你只是想临时验证某个阶段的配置可以手动指定 tagjreleaser release --tag-name v1.0.0 --dryrun这个参数会覆盖配置里的tagName规则很适合在调试阶段使用。3.2 changelog 生成规则与格式化技巧JReleaser 的 changelog 是从 git 历史生成的而不是让你手工维护一个独立的 CHANGELOG.md。它默认读取当前 tag 之前的所有 commit按你指定的格式渲染成 Markdown。配置示例changelog: formatted: ALWAYS preset: conventional-commits format: - {{commitShortHash}} {{commitTitle}} ({{commitAuthor}}) categories: - title: 新功能 labels: - enhancement - feature - title: Bug 修复 labels: - bug - fix - title: 其他 labels: - **这里的逻辑是如果 commit message 采用 conventional commits 规范比如feat: 添加登录功能、fix: 修复空指针JReleaser 就能按preset: conventional-commits把 commit 自动分类。categories定义了分类名称和匹配规则labels对应 commit 里的类型关键词**作为兜底匹配所有未归类的 commit。如果你的团队没有强制 conventional commitscommit message 写得比较随意那preset反倒会帮倒忙——所有 commit 都会被扔到“其他”分类里。这种情况下不如去掉preset直接用format定义单条 commit 的渲染格式再用categories里的**兜底。另外一个实用技巧是用excludeLabels把chore、ci这类不影响用户的提交从 changelog 里过滤掉让发布的变更日志真正聚焦在用户可见的变化上。3.3 files 区段的四类产物与路径模板发布到 GitHub Release 的附件都配置在files段下。这个段我拆成四类来看files: artifacts: - path: build/libs/demo-app-{{projectVersion}}.jar signatures: - path: build/libs/demo-app-{{projectVersion}}.jar.asc checksums: - path: build/libs/demo-app-{{projectVersion}}.jar.sha256 extraProperties: key: valueartifacts是主产物比如 jar、zip、tarball。signatures是 GPG 签名文件如果你配置了signing段后面会讲签名文件会自动生成这里可以不用手工声明。checksums是校验和文件同样如果你配置了checksum段并指定算法JReleaser 会自动计算并生成不需要自己先算好。extraProperties则用来附加一些自定义信息供后续模板或脚本引用。路径里的模板变量非常关键。除了{{projectVersion}}还支持{{projectName}}、{{projectEffectiveVersion}}等。这些变量在 dryrun 时会一并解析所以发布前盯着 dryrun 输出里的文件清单就能确认路径解析是否正确。如果文件路径写错JReleaser 在发布阶段会报“文件不存在”的错误但这个问题本可以在 dryrun 阶段就发现。自动生成 checksum 的配置示例checksum: active: ALWAYS algorithms: - SHA-256这条配置会让 JReleaser 为所有 artifacts 生成.sha256文件并和 jar 一起上传。对发布开源项目来说这一步能显著提升用户对产物的信任度成本却几乎为零。3.4 多平台 host 配置与 token 管理JReleaser 支持 GitHub、GitLab、Gitea、Codeberg 等多个平台。切换平台时只需把release段下的github换成对应平台名。比如发布到 Gitearelease: gitea: owner: your-gitea-account name: demo-app host: https://git.example.com username: your-name token: ${GITEA_TOKEN}注意token字段可以直接写成环境变量占位符${GITEA_TOKEN}这样 token 不会出现在配置文件里避免误提交到仓库。JReleaser 也支持从环境变量读取标准命名的 token比如JRELEASER_GITHUB_TOKEN对应 GitHubJRELEASER_GITEA_TOKEN对应 Gitea。如果你同时配置了多个 hostJReleaser 会按照release段里声明的顺序逐个发布这在“一个项目同时发布到 GitHub 和 Gitea”的场景下非常实用。关于 token 权限不同平台的要求不完全一样。GitHub 的 fine-grained token 需要勾选Contents: Read and write才能创建 ReleaseGitea 的 token 需要具备repository相关的写权限。这块细节容易踩坑我在下一章单独展开说。4. 正式发布中踩过的五个坑与完整排查链路4.1 坑一token 权限不足发布在 API 调用阶段失败现象dryrun 全程正常正式发布时日志在Creating GitHub release这一行停住紧接着报出 HTTP 403 或 401错误信息里常见Resource not accessible by integration或Bad credentials。排查链路先看日志里实际请求的 API endpoint 和 token 前缀。JReleaser 日志会打印请求的 HTTP 状态码403 多数是权限范围问题401 更可能是 token 本身失效。如果你在 GitHub Actions 里用${{ secrets.GITHUB_TOKEN }}检查 workflow 有没有开启写入权限。需要在仓库的Settings Actions General Workflow permissions里勾选Read and write permissions同时 workflow 文件里也要声明permissions: contents: write。如果你是本地用个人 token检查 fine-grained token 的权限设置确认Contents的权限级别是Read and write而不只是Read-only。用 curl 快速验证 token 是否有效curl -H Authorization: Bearer $JRELEASER_GITHUB_TOKEN https://api.github.com/user能返回你的用户名说明 token 本身没问题问题大概率出在权限范围。经验总结这类问题最迷惑人的地方在于 dryrun 一切正常因为 dryrun 根本不发真实请求。所以千万别因为 dryrun 没报错就跳过 token 检查。第一次接 JReleaser 时我建议先用一个有完整repo权限的 token 跑通全流程之后再按最小权限原则收敛。4.2 坑二git tag 和 project.version 不一致发布被中途拦截现象执行jreleaser release时日志在解析配置后直接报错提示当前 git 仓库没有匹配的 tag或者 tag 名称与配置不一致。排查链路先确认当前 HEAD 上有没有 taggit tag --points-at HEAD如果输出为空说明当前 commit 没有任何 tag。对比配置文件里的 version 和实际 tag。比如project.version是1.0.0但 tag 打成了1.0.0而不是v1.0.0就会和tagName: v{{projectVersion}}不匹配。JReleaser 在发布前会校验 tag确保 Release 关联的 commit 是可控的。两个方向的解决办法要么改 tag重新打一个符合规则的 tag要么在配置里显式指定tagName让 tag 规则和你的实际习惯一致。如果是快速验证配置用--tag-name参数临时指定jreleaser release --tag-name v1.0.0 --dryrun经验总结每个项目都应该明确自己的 tag 规则比如“正式版一律v开头”。JReleaser 不会帮你创建 tag它只负责在你已经打好的 tag 上创建 Release所以发布前先git tag再git push origin tag是必须坚持的步骤。我在项目 README 里专门写了一段发布检查单把打 tag、推 tag、跑 dryrun、正式发布四步列清楚团队其他人照着执行再也没有出现过 tag 不匹配的问题。4.3 坑三dryrun 一片正常正式发布后 release 却没有附件现象GitHub Release 创建成功页面标题和 changelog 都在但 Release 附件里找不到 jar 文件或者上传的文件列表和预期不符。排查链路检查files.artifacts[].path是不是相对路径。JReleaser 的工作目录可能和你jar所在的构建目录不一致路径解析出来没有文件上传阶段自然就跳过了。发布前手工确认产物文件存在ls -l build/libs/demo-app-1.0.0.jardryrun 输出里找到文件解析的汇总信息看它打印的最终路径是不是你预期的那一个。JReleaser dryrun 会详细打印每个文件的解析结果这一步的眼睛不要快进。如果本地路径是build/libs/demo-app-1.0.0.jar但你希望上传到 Release 后显示成demo-app-1.0.0.jar用transform字段控制上传后的文件名files: artifacts: - path: build/libs/demo-app-{{projectVersion}}.jar transform: demo-app-{{projectVersion}}.jar经验总结JReleaser 的 dryrun 对文件解析的输出非常透明问题在于很多人不会逐行去看。我第一次迁移时dryrun 输出里其实已经把“文件不存在”或者“路径被跳过”的信息打出来了但我没注意直到正式发布后才发现 Release 是空的。现在我的习惯是dryrun 跑完先搜输出里有没有Uploading或file相关的行核对路径和文件名。4.4 坑四changelog 内容为空或密集成一团现象Release 页面的 changelog 是空的或者所有 commit 标题被挤成一个没有分段的长列表看起来非常糟。排查链路单独跑一次 changelog 生成确认 JReleaser 从 git 历史里读到了什么jreleaser changelog --dryrun检查 commit message 是否规范。如果配置了preset: conventional-commits但 commit 都是“更新代码”“修改 bug”这类自由文本preset 匹配不到任何分类内容就可能落入空分组或全部堆在“其他”里。如果团队没有统一的 commit 规范先去掉preset直接用format自定义渲染格式并用categories里的**兜底保证所有 commit 都至少出现在“其他”分类下。使用excludeLabels排除噪音提交changelog: formatted: ALWAYS format: - {{commitShortHash}} {{commitTitle}} ({{commitAuthor}}) excludeLabels: - chore - ci - docs经验总结changelog 好不好看根源在 commit 规范不在工具。JReleaser 只是把你 git log 里的内容结构化。想发布出让人眼前一亮的变更日志先让团队统一用 conventional commits 写 message再在 JReleaser 里配置对应的分类规则。如果项目历史已经一团乱那就先不加 preset用一个简单的 format 保证基本可读性。4.5 坑五重复发布同一版本GitHub 返回 422现象第一次发布成功后因为某个附件没传对你想重新发布同一个版本结果 JReleaser 报 HTTP 422 Unprocessable Entity。排查链路GitHub 不允许两个 Release 使用同一个 tag。如果配置里overwrite: false默认重复发布同版本会被 API 拒绝。如果你只是想覆盖同名 Release设置overwrite: true。但注意overwrite只能覆盖 Release不能覆盖已经存在的 tag。如果同名 tag 已经指向了其他 commit你需要先删除本地和远程的 taggit tag -d v1.0.0 git push origin :refs/tags/v1.0.0然后重新打 tag、推 tag再执行发布。某些平台比如 Gitea对重复 tag 的处理有细微差别但“先清理旧 tag再发新版”是通用规则。经验总结我建议正式发布流程中保持overwrite: false宁可让它失败也要让人停下来看看“为什么重复发布同一个版本”。覆盖发布偶尔会在 CI 误触发时产生意想不到的后果。如果实在需要覆盖在本地手工执行一次带--dryrun的命令确认清楚再真正覆盖。5. 把 JReleaser 接进日常工程签名、中央仓库与 CI 一体化5.1 用 GPG 给产物签名并生成校验和开源项目发布时GPG 签名是一个增强可信度的重要手段。JReleaser 的signing段配置好之后会自动为所有 artifacts 生成.asc签名文件并作为附件上传signing: active: ALWAYS armored: true使用前需要先准备好 GPG key。如果本机没有可以用gpg --gen-key生成一个。JReleaser 在签名时通常需要私钥的 passphrase可以通过环境变量GPG_PASSPHRASE提供避免把密码写在配置里。签名文件生成后files.signatures段可以不再手工声明JReleaser 会自动把它们放进 release 附件。需要注意JReleaser 要求能够读取到你的 GPG key你需要在本地导入私钥或者把私钥导出后放到 CI 的 secrets 里。配合自动校验和checksum: active: ALWAYS algorithms: - SHA-256发布后用户可以在 Release 页面同时看到 jar、jar.asc、jar.sha256 三个文件。这样一套组合拳打下来发布产物的可信度比裸传一个 jar 高出一大截。5.2 发布到 Maven 中央仓库的配置思路JReleaser 的deploy阶段支持将产物发布到 Maven 中央仓库。对于 Java 库项目来说这一步是刚需。典型配置deploy: maven: central: sonatype: active: ALWAYS url: https://central.sonatype.com/api/v1/publisher stagingRepositories: - target/nexus-staging使用思路是先用./gradlew publish或mvn deploy把产物部署到本地的 staging 目录再由 JReleaser 执行 deploy 阶段把 staging 里的文件上传到 Sonatype 完成正式发布。需要设置JRELEASER_SONATYPE_USERNAME和JRELEASER_SONATYPE_PASSWORD环境变量。这里要提醒一点发布到中央仓库有很多前置条件比如 groupId 需要通过验证、所有产物必须有 GPG 签名、POM 文件必须完整。JReleaser 只是在最后一步帮你去掉“手工上传到 Nexus”的麻烦前面那些质量门禁还是得在你的构建配置里做好。5.3 在 GitHub Actions 里一键全量发布把 JReleaser 接进 GitHub Actions 是让发布流程自动化的最后一步。我现在的做法是推 tag 触发 workflowworkflow 自动构建产物再运行 JReleaser 完成发布。一个完整可用的 workflow 示例name: Release on: push: tags: - v* jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - uses: actions/setup-javav4 with: distribution: temurin java-version: 17 - name: Build run: ./gradlew clean build - name: Run JReleaser uses: jreleaser/release-actionv2 with: arguments: full-release env: JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}这个 workflow 里有几个细节直接决定成败。第一checkout必须设置fetch-depth: 0否则 JReleaser 生成 changelog 时看不到完整 git 历史变更日志会缺一大截。第二permissions: contents: write必须显式声明否则GITHUB_TOKEN默认只有只读权限Release 创建会失败。第三arguments: full-release会让 JReleaser 先执行 assemble 阶段再执行 release 阶段如果你已经在前面单独跑了构建步骤也可以只跑release。第四JRELEASER_GITHUB_TOKEN使用内置的GITHUB_TOKEN就够了不需要单独创建个人 token只要 workflow 权限设置正确。我在实际使用中把jreleaser/release-actionv2这个 action 固定在仓库里团队其他成员发版时只需要推一个v*格式的 tag后面全部自动化。这比我以前维护一整个 shell 发布脚本爽太多了。5.4 进阶扩展多模块项目、announce 环节与我的总结如果项目是多模块结构JReleaser 也支持通过命令行参数覆盖版本号进行全量发布。比如jreleaser full-release -PJRELEASER_PROJECT_VERSION2.0.0这个参数会把配置里的版本号临时覆盖成2.0.0适合在 CI 里根据 tag 动态发版的场景。announce阶段可以在发布完成后自动发通知到 Twitter、Discord、邮件等渠道。如果团队有发布公告的需求可以额外配置属于锦上添花的功能我在这里不展开。最后分享一点个人体会。我在团队里推广 JReleaser 时很多人问的第一句话是“这不就是 GitHub Actions 里加一个 step 的事吗”。但真正用了两个版本周期之后大家的感受是JReleaser 最大的价值不是省掉了某个点击动作而是把发布流程从“每个人的私有记忆”变成了“一份所有人都能读懂的公开配置”。以前发版依赖某个核心维护者在场现在任何一个人推一个 tag流水线就会按同样的标准跑完所有环节。如果你也想把发布这件小事固定下来我的建议是从最小配置开始本地先 dryrun 看输出再找一个小项目全量跑通最后接进 CI。这套流程走一遍之后你再也不想回到手工发版的日子。
返回列表