写点什么

Maven 实战(九)——打包的技巧

  • 2011-06-20
  • 本文字数:4667 字

    阅读完需:约 15 分钟

AI 大模型超全落地场景&金融应用实践,8 月 16 - 19 日 FCon x AICon 大会联诀来袭、干货翻倍!

“打包“这个词听起来比较土,比较正式的说法应该是”构建项目软件包“,具体说就是将项目中的各种文件,比如源代码、编译生成的字节码、配置文件、文档,按照规范的格式生成归档,最常见的当然就是 JAR 包和 WAR 包了,复杂点的例子是 Maven 官方下载页面的分发包,它有自定义的格式,方便用户直接解压后就在命令行使用。作为一款”打包工具“,Maven 自然有义务帮助用户创建各种各样的包,规范的 JAR 包和 WAR 包自然不再话下,略微复杂的自定义打包格式也必须支持,本文就介绍一些常用的打包案例以及相关的实现方式,除了前面提到的一些包以外,你还能看到如何生成源码包、Javadoc 包、以及从命令行可直接运行的 CLI 包。

Packaging 的含义

任何一个 Maven 项目都需要定义 POM 元素 packaging(如果不写则默认值为 jar)。顾名思义,该元素决定了项目的打包方式。实际的情形中,如果你不声明该元素,Maven 会帮你生成一个 JAR 包;如果你定义该元素的值为 war,那你会得到一个 WAR 包;如果定义其值为 POM(比如是一个父模块),那什么包都不会生成。除此之外,Maven 默认还支持一些其他的流行打包格式,例如 ejb3 和 ear。你不需要了解具体的打包细节,你所需要做的就是告诉 Maven,”我是个什么类型的项目“,这就是约定优于配置的力量。

为了更好的理解 Maven 的默认打包方式,我们不妨来看看简单的声明背后发生了什么,对一个 jar 项目执行 mvn package 操作,会看到如下的输出:

复制代码
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ git-demo ---
[INFO] Building jar: /home/juven/git_juven/git-demo/target/git-demo-1.2-SNAPSHOT.jar

相比之下,对一个 war 项目执行 mvn package 操作,输出是这样的:

复制代码
[INFO] --- maven-war-plugin:2.1:war (default-war) @ webapp-demo ---
[INFO] Packaging webapp
[INFO] Assembling webapp [webapp-demo] in [/home/juven/git_juven/webapp-demo/target/webapp-demo-1.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Copying webapp resources [/home/juven/git_juven/webapp-demo/src/main/webapp]
[INFO] Webapp assembled in [90 msecs]
[INFO] Building war: /home/juven/git_juven/webapp-demo/target/webapp-demo-1.0-SNAPSHOT.war

对应于同样的 package 生命周期阶段,Maven 为 jar 项目调用了 maven-jar-plugin,为 war 项目调用了 maven-war-plugin,换言之,packaging 直接影响 Maven 的构建生命周期。了解这一点非常重要,特别是当你需要自定义打包行为的时候,你就必须知道去配置哪个插件。一个常见的例子就是在打包 war 项目的时候排除某些 web 资源文件,这时就应该配置 maven-war-plugin 如下:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<webResources>
<resource>
<directory>src/main/webapp</directory>
<excludes>
<exclude>**/*.jpg</exclude>
</excludes>
</resource>
</webResources>
</configuration>
</plugin>

源码包和 Javadoc 包

本专栏的《坐标规划》一文中曾解释过,一个 Maven 项目只生成一个主构件,当需要生成其他附属构件的时候,就需要用上 classifier。源码包和 Javadoc 包就是附属构件的极佳例子。它们有着广泛的用途,尤其是源码包,当你使用一个第三方依赖的时候,有时候会希望在 IDE 中直接进入该依赖的源码查看其实现的细节,如果该依赖将源码包发布到了 Maven 仓库,那么像 Eclipse 就能通过 m2eclipse 插件解析下载源码包并关联到你的项目中,十分方便。由于生成源码包是极其常见的需求,因此 Maven 官方提供了一个插件来帮助用户完成这个任务:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>

类似的,生成 Javadoc 包只需要配置插件如下:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>

为了帮助所有 Maven 用户更方便的使用 Maven 中央库中海量的资源,中央仓库的维护者强制要求开源项目提交构件的时候同时提供源码包和 Javadoc 包。这是个很好的实践,读者也可以尝试在自己所处的公司内部实行,以促进不同项目之间的交流。

可执行 CLI 包

除了前面提到了常规 JAR 包、WAR 包,源码包和 Javadoc 包,另一种常被用到的包是在命令行可直接运行的 CLI(Command Line)包。默认 Maven 生成的 JAR 包只包含了编译生成的.class 文件和项目资源文件,而要得到一个可以直接在命令行通过 java 命令运行的 JAR 文件,还要满足两个条件:

  • JAR 包中的 /META-INF/MANIFEST.MF 元数据文件必须包含 Main-Class 信息。
  • 项目所有的依赖都必须在 Classpath 中。

Maven 有好几个插件能帮助用户完成上述任务,不过用起来最方便的还是 maven-shade-plugin ,它可以让用户配置 Main-Class 的值,然后在打包的时候将值填入 /META-INF/MANIFEST.MF 文件。关于项目的依赖,它很聪明地将依赖 JAR 文件全部解压后,再将得到的.class 文件连同当前项目的.class 文件一起合并到最终的 CLI 包中,这样,在执行 CLI JAR 文件的时候,所有需要的类就都在 Classpath 中了。下面是一个配置样例:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.juvenxu.mavenbook.HelloWorldCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

上述例子中的,我的 Main-Class 是 com.juvenxu.mavenbook.HelloWorldCli,构建完成后,对应于一个常规的 hello-world-1.0.jar 文件,我还得到了一个 hello-world-1.0-cli.jar 文件。细心的读者可能已经注意到了,这里用的是 cli 这个 classifier。最后,我可以通过java -jar hello-world-1.0-cli.jar命令运行程序。

自定义格式包

实际的软件项目常常会有更复杂的打包需求,例如我们可能需要为客户提供一份产品的分发包,这个包不仅仅包含项目的字节码文件,还得包含依赖以及相关脚本文件以方便客户解压后就能运行,此外分发包还得包含一些必要的文档。这时项目的源码目录结构大致是这样的:

复制代码
pom.xml
src/main/java/
src/main/resources/
src/test/java/
src/test/resources/
src/main/scripts/
src/main/assembly/
README.txt

除了基本的 pom.xml 和一般 Maven 目录之外,这里还有一个 src/main/scripts/ 目录,该目录会包含一些脚本文件如 run.sh 和 run.bat,src/main/assembly/ 会包含一个 assembly.xml,这是打包的描述文件,稍后介绍,最后的 README.txt 是份简单的文档。

我们希望最终生成一个 zip 格式的分发包,它包含如下的一个结构:

复制代码
bin/
lib/
README.txt

其中 bin/ 目录包含了可执行脚本 run.sh 和 run.bat,lib/ 目录包含了项目 JAR 包和所有依赖 JAR,README.txt 就是前面提到的文档。

描述清楚需求后,我们就要搬出 Maven 最强大的打包插件: maven-assembly-plugin 。它支持各种打包文件格式,包括 zip、tar.gz、tar.bz2 等等,通过一个打包描述文件(该例中是 src/main/assembly.xml),它能够帮助用户选择具体打包哪些文件集合、依赖、模块、和甚至本地仓库文件,每个项的具体打包路径用户也能自由控制。如下就是对应上述需求的打包描述文件 src/main/assembly.xml:

复制代码
<assembly>
<id>bin</id>
<formats>
<format>zip</format>
</formats>
<dependencySets>
<dependencySet>
<useProjectArtifact>true</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<outputDirectory>/</outputDirectory>
<includes>
<include>README.txt</include>
</includes>
</fileSet>
<fileSet>
<directory>src/main/scripts</directory>
<outputDirectory>/bin</outputDirectory>
<includes>
<include>run.sh</include>
<include>run.bat</include>
</includes>
</fileSet>
</fileSets>
</assembly>
  • 首先这个 assembly.xml 文件的 id 对应了其最终生成文件的 classifier。
  • 其次 formats 定义打包生成的文件格式,这里是 zip。因此结合 id 我们会得到一个名为 hello-world-1.0-bin.zip 的文件。(假设 artifactId 为 hello-world,version 为 1.0)
  • dependencySets 用来定义选择依赖并定义最终打包到什么目录,这里我们声明的一个 depenencySet 默认包含所有所有依赖,而 useProjectArtifact 表示将项目本身生成的构件也包含在内,最终打包至输出包内的 lib 路径下(由 outputDirectory 指定)。
  • fileSets 允许用户通过文件或目录的粒度来控制打包。这里的第一个 fileSet 打包 README.txt 文件至包的根目录下,第二个 fileSet 则将 src/main/scripts 下的 run.sh 和 run.bat 文件打包至输出包的 bin 目录下。

打包描述文件所支持的配置远超出本文所能覆盖的范围,为了避免读者被过多细节扰乱思维,这里不再展开,读者若有需要可以去参考这份文档

最后,我们需要配置 maven-assembly-plugin 使用打包描述文件,并绑定生命周期阶段使其自动执行打包操作:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

运行mvn clean package之后,我们就能在 target/ 目录下得到名为 hello-world-1.0-bin.zip 的分发包了。

小结

打包是项目构建最重要的组成部分之一,本文介绍了主流 Maven 打包技巧,包括默认打包方式的原理、如何制作源码包和 Javadoc 包、如何制作命令行可运行的 CLI 包、以及进一步的,如何基于个性化需求自定义打包格式。这其中涉及了很多的 Maven 插件,当然最重要,也是最为复杂和强大的打包插件就是 maven-assembly-plugin。事实上 Maven 本身的分发包就是通过 maven-assembly-plugin 制作的,感兴趣的读者可以直接查看源码一窥究竟。

关注 IT 趋势,承载前沿、深入、有温度的内容。感兴趣的读者可以搜索 ID:laocuixiabian,或者扫描下方二维码加关注。

2011-06-20 08:38179719

评论 1 条评论

发布
用户头像
maven souece plugin,maven javadoc plugin,maven shade plugin,maven assembly plugin
2021-12-15 21:48
回复
没有更多了
发现更多内容

iMile 利用 Zadig 多云环境周部署千次,跨云跨地域持续交付全球业务

Zadig

DevOps 微服务架构 CI/CD 持续交付 国际化

TTChat x Zadig 开源共创 Helm 接入场景,环境治理搞得定!

Zadig

DevOps 微服务 音视频 测试环境治理

揭秘百度智能测试在测试自动执行领域实践

百度Geek说

测试

智能指标驱动的管理和决策平台 Kyligence Zen 全新上线,限量内测中

Kyligence

web前端培训 | 34 道 Vue 高频面试题

@零度

Vue 前端开发

技术分享| 融合调度中的广播功能设计

anyRTC开发者

音视频 调度 实时消息 快对讲 广播

java就业培训 | 怎么实现 SpringBoot 并行任务

@零度

JAVA开发 springboot

年轻就要醒着拼,年轻就要勇于尝试

Zadig

DevOps 微服务治理 自动化运维 企业案例

架构实战营第五模块课后作业

Geek_53787a

架构实战营

超级详细的 Maven 教程(基础+高级)

Ayue、

maven

新朝旧将 vite和webpack煮酒论英雄

转转技术团队

vite webpack

龙书虎书鲸书啃不动?试试豆瓣评分9.5的猴书

图灵教育

编译原理 go语言

高校如何基于云原生构建面向未来的智慧校园?全栈云原生架构VS传统IT架构

York

云原生 数字化转型 智慧校园 教育科技

揭秘!付费会员制下的那些小心机!

CRMEB

云创平台+播放器SDK,腾讯云音视频新工具助力视频生产、终端能力全面升级

科技热闻

助力极致体验,火山引擎边缘计算最佳实践

火山引擎边缘云

云计算 边缘计算 低时延 边缘云原生 边缘网络

大数据培训 | Flink SQL窗口表值函数聚合实现原理

@零度

flink 大数据开发

InfoQ百位优质创作者签约计划第三季,终于等到了!!!

InfoQ写作社区官方

热门活动 签约计划第三季

图像边缘检测的新方向——量子算法

启科量子开发者官方号

tensorflow 边缘计算 图像处理 量子计算 量子算法

亲测!Centos7部署PHP + Swoole

迷彩

Apache Linux 微服务 swoole 6月月更

易快报:我们用 Zadig 实现万次构建部署,聪明运维,释放开发生产力

Zadig

DevOps 微服务架构 CI/CD 容器化 Zadig

基于字节码的统一异常上报实践

转转技术团队

异常机制 Java’

面试突击61:说一下MySQL事务隔离级别?

王磊

Java java面试

自主可控再下一城!首套国产ARTIQ架构量子计算测控系统发布

启科量子开发者官方号

算力 量子计算机 量子计算 离子阱 启科量子

什么是外链和内链?

源字节1号

前端开发 后端开发 网站开发

钛动科技:我们的 Zadig 落地之路

Zadig

DevOps 持续交付 企业出海 研发效率

ONES 创始人王颖奇对话《财富》(中文版):中国有没有优秀的软件?

万事ONES

Vue3中如何使用异步请求?

Python研究所

6月月更

影响LED封装散热主要因素有哪些?

Dylan

LED LED显示屏 led显示屏厂家

SOFARegistry 源码|数据同步模块解析

SOFAStack

源码解析 注册中心 数据同步 开源软件

妙!妙盈科技全面实施 Zadig 助力容器化建设,全面拥抱 Kubernetes 和云原生

Zadig

DevOps CI/CD 容器化 自动化运维 Zadig

Maven实战(九)——打包的技巧_Java_许晓斌_InfoQ精选文章