5月31日 22:22

Maven Assembly Plugin 如何打出可交付分发包?

Maven Assembly Plugin 解决的是“交付物不只是一个 JAR”的问题。很多命令行工具、离线部署包、传统服务程序需要 bin/conf/lib/README、启动脚本一起交付,这时单纯的 mvn package 不够,Assembly 可以按描述符把文件和依赖组织成 zip、tar.gz 或 jar-with-dependencies。它不负责解决依赖冲突,也不负责容器化部署;它负责把已经构建好的东西按你指定的目录结构装箱。

什么时候该用 Assembly

如果你只是做 Spring Boot 应用,优先使用 spring-boot-maven-plugin 打可执行 JAR。Assembly 更适合通用 Java 程序、批处理工具、带脚本和配置文件的服务包,或者需要同时产出二进制包和源码包的项目。最常见目录是:

text
demo-1.0.0/ bin/start.sh bin/stop.sh conf/application.yml lib/demo-1.0.0.jar lib/*.jar README.md

POM 中引用自定义描述符:

xml
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.7.1</version> <configuration> <descriptors> <descriptor>src/assembly/distribution.xml</descriptor> </descriptors> <appendAssemblyId>true</appendAssemblyId> </configuration> <executions> <execution> <id>make-dist</id> <phase>package</phase> <goals><goal>single</goal></goals> </execution> </executions> </plugin>

描述符决定分发包长什么样:

xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 https://maven.apache.org/xsd/assembly-2.1.0.xsd"> <id>dist</id> <formats> <format>zip</format> <format>tar.gz</format> </formats> <includeBaseDirectory>true</includeBaseDirectory> <baseDirectory>${project.artifactId}-${project.version}</baseDirectory> <fileSets> <fileSet> <directory>src/main/scripts</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> </fileSet> <fileSet> <directory>src/main/config</directory> <outputDirectory>conf</outputDirectory> <filtered>true</filtered> </fileSet> </fileSets> <dependencySets> <dependencySet> <outputDirectory>lib</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly>

构建命令是 mvn clean package,产物会出现在 target/demo-1.0.0-dist.zip 一类文件中。CI 里可以把这个 zip 作为 artifact 上传,或者在 release 阶段发布到制品仓库。

yaml
- name: Build distribution run: mvn -B clean package - name: Upload dist uses: actions/upload-artifact@v4 with: name: demo-dist path: target/*-dist.*

分发包最好在构建后做一次“解压即运行”检查。比如 CI 里执行 unzip target/*-dist.zip -d target/check,再检查 bin/start.shconf/lib/ 是否存在,必要时启动 java -cp 'lib/*' com.example.Main --version。这一步看似简单,却能提前发现脚本权限、路径拼接、依赖遗漏等问题。很多团队只验证 Maven 构建成功,真正交付给运维后才发现包结构不符合部署脚本预期。

文件命名也要保持稳定。appendAssemblyId=true 会生成带 dist 后缀的包,适合同时产出多个 assembly;如果只需要一个主分发包,可以根据仓库规范调整。不要让 CI 用模糊路径上传一堆临时包,否则发布页面里会出现 sources、javadoc、dist 混在一起的情况。正式发布时最好对分发包生成校验和,至少保留 SHA-256,方便部署端确认下载完整性。

如果分发包要给客户或离线环境使用,还要提前处理许可证和第三方依赖清单。Assembly 可以把 LICENSENOTICETHIRD-PARTY.txt 放进包里,但清单内容通常要由 license 插件或内部合规流程生成。别等交付前一天再补这些文件,依赖一多,许可证核对会非常耗时。

追问

Assembly 和 Shade Plugin 应该怎么选?

Assembly 擅长做分发包,能保留目录结构,把脚本、配置、依赖分开放。Shade Plugin 擅长做 uber-jar,并且可以重定位包名,缓解依赖冲突。取舍点是交付形式:要 zip/tar.gz 选 Assembly,要一个可运行胖 JAR 且担心依赖冲突选 Shade。常见坑是用 jar-with-dependencies 打包复杂项目,多个依赖的同名资源被覆盖,运行时才发现配置或 SPI 丢失。

dependencySet 的 scope 应该怎么写?

大多数运行包使用 runtime,这样会包含运行必需依赖,不会把 test 依赖打进去。如果是开发调试包,可能需要额外包含源码或文档,但正式分发包应尽量干净。边界在于 provided 依赖是否由目标环境提供,比如 Servlet 容器或应用服务器。坑在于把 provided 也打进 lib,线上类加载顺序变化后出现版本冲突。

配置文件要不要 filtered?

可以过滤构建号、版本号这类非敏感值,但不要把数据库密码、生产密钥过滤进包里。分发包通常应该携带模板配置,真正的环境值由部署系统注入。取舍是开箱即用和安全隔离之间的平衡:内部工具可以多给默认值,生产服务应少塞秘密。常见坑是开启 filtering 后,YAML 里的 ${...} 被 Maven 当占位符处理,Spring 运行时反而拿不到原本想保留的变量。

fileMode 在 Windows 上还有意义吗?

对 zip 来说意义有限,对 tar.gz 在 Linux 部署场景很重要。启动脚本如果没有 0755 权限,解压后第一步就是 chmod,自动化部署会多一个失败点。边界是目标平台全是 Windows 时可以不强调 fileMode,但跨平台包最好同时提供 .sh.bat。坑在于脚本在 Git 里有执行权限,本地测试没问题,CI 重新打包后权限丢了。

多模块项目里 Assembly 应该放在哪个模块?

通常放在专门的 distribution 模块,而不是塞进业务模块。这样它可以依赖多个子模块产物,再统一组装成一个包。取舍是结构稍微多一层,但职责更清楚,避免每个模块都知道最终分发目录。常见坑是在父 POM 里绑定 assembly,结果所有子模块都执行一次,产物重复、路径混乱,还拖慢构建。

小结

Assembly Plugin 的关键是先想清楚交付目录,再写描述符。应用 JAR、脚本、配置、依赖各归其位,CI 只负责稳定产出和上传。不要把它当依赖冲突修复工具,也不要把环境秘密打进包里,这两个边界守住,分发包会好维护很多。

标签:Maven