packaging
本章承接"GAV 坐标",讲解 Maven 项目坐标的第四个要素——
packaging。如果说 GAV 回答了"这个项目是谁、叫什么、第几代",那么packaging回答的是"这个项目长什么样"——它是一个可运行的 JAR、一个部署到 Tomcat 的 WAR,还是一个管理其他模块的 POM?
核心机制
这是项目产生的产物类型,默认值是 jar。它直接决定了 Maven 的生命周期绑定和默认文件扩展名。当你把 packaging 从 jar 改成 war 时,Maven 自动调用的插件、生成的文件格式、甚至目录结构要求都会发生变化。
packaging 的本质:决定"项目形态"
packaging 声明了项目构建完成后,应该以什么物理形态存在。Maven 根据这个声明,自动选择对应的生命周期绑定和插件组合。
| packaging | 产物扩展名 | 核心插件绑定 | 适用场景 |
|---|---|---|---|
| jar | .jar | maven-jar-plugin | 普通 Java 库、后端服务、工具包 |
| war | .war | maven-war-plugin | Web 应用(Servlet/JSP/Spring MVC) |
| pom | 无直接产物 | 无特殊插件 | 多模块父项目、聚合项目、依赖管理项目 |
jar:Java 世界的"标准集装箱"
jar(Java ARchive)是 Java 生态的标准打包格式。它本质上是一个 ZIP 文件,里面包含编译后的 .class 文件、资源文件和 META-INF/MANIFEST.MF 清单。
当 packaging = jar 时:
mvn package会生成target/<artifactId>-<version>.jar- Maven 自动绑定
maven-jar-plugin,负责把target/classes/下的内容打成 JAR - 如果项目有
main方法,可以通过maven-jar-plugin配置MANIFEST.MF中的Main-Class入口
war:Web 应用的"部署包"
war(Web Application ARchive)是 Java EE / Jakarta EE 定义的Web 应用标准格式。它也是一个 ZIP 文件,但有固定的内部目录结构:
myapp.war
├── WEB-INF/
│ ├── classes/ # 编译后的 .class 文件
│ ├── lib/ # 项目依赖的 JAR
│ └── web.xml # Web 应用配置(Servlet 3.0+ 可省略)
├── META-INF/
│ └── MANIFEST.MF
└── index.html # 静态资源(可直接放根目录)
当 packaging = war 时:
mvn package会生成target/<artifactId>-<version>.war- Maven 自动绑定
maven-war-plugin,负责按 WAR 规范打包 - 依赖的 JAR 不会打进
classes/,而是放进WEB-INF/lib/ - 部署时,把 WAR 文件丢进 Tomcat 的
webapps/目录即可运行
pom:项目的"管理形态"
pom 是最特殊的 packaging——它不产生任何可直接运行的产物。当 packaging = pom 时,Maven 不会执行编译、打包等步骤,因为这类项目的存在意义是管理其他项目。
pom 类型的项目通常有两种角色:
- 父项目(Parent):定义子模块共享的依赖版本、插件版本、构建规则。例如飞翔科技的根项目
feixiang-parent,packaging = pom,下面聚合了employee-system、payroll-service等子模块。 - 聚合项目(Aggregator):只是为了方便一次性构建多个模块而存在。例如执行
mvn install时,聚合项目会递归构建所有子模块。
生活类比:三种不同的"产品形态"
想象飞翔科技是一家食品工厂,packaging 就是产品的包装形态:
jar= 真空包装的食品盒。里面只有食物本身(.class文件),没有餐具、没有调料包。消费者(其他项目)买回去后,需要自己准备锅碗瓢盆(运行环境)来加工。适合卖"原材料"——比如feixiang-common工具包。war= 自热火锅套装。里面不仅有食物,还有加热包(WEB-INF/lib/里的依赖)、餐具(web.xml配置)、甚至桌布(静态资源 HTML/CSS)。消费者只需要加水(部署到 Tomcat),就能直接吃。适合卖"即食产品"——比如employee-systemWeb 应用。pom= 产品目录手册。手册本身不能吃,但它规定了"食品盒里该放什么""自热火锅的调料配比"。没有手册,工厂(多模块项目)会乱套。适合"管理层"——比如feixiang-parent父项目。
图示
上图展示了三种 packaging 的构建产物差异:
jar项目:产物就是一个 JAR,里面是编译后的类和资源war项目:产物是一个 WAR,内部有WEB-INF/classes/(自己的类)、WEB-INF/lib/(依赖的 JAR)、以及根目录的静态资源pom项目:没有编译和打包步骤,产物只有一个.pom文件(项目的元数据),用于被其他项目继承或聚合
完整示例
场景
飞翔科技有三个项目,分别承担不同职责。CTO 大翔要求架构师白歌为每个项目选择正确的 packaging,后端小崔负责落实配置。
| 项目 | 职责 | 应该用什么 packaging |
|---|---|---|
feixiang-parent | 父项目,统一管理依赖版本和构建规则 | pom |
feixiang-common | 公共工具类(字符串处理、日期工具、加密工具) | jar |
employee-system | 员工管理系统(Spring MVC + JSP,部署到 Tomcat) | war |
操作前:packaging 选错的灾难
假设白歌一时疏忽,把三个项目的 packaging 都写成了 jar:
灾难 1:employee-system 是 jar
<packaging>jar</packaging> <!-- ❌ Web 应用不能用 jar -->
后果:
mvn package生成的是employee-system-1.0.0.jar- 李眉把 JAR 丢进 Tomcat 的
webapps/,Tomcat 不认识,拒绝部署 - 静态资源(HTML、JS、CSS)没有按 WAR 规范放入正确目录,前端黄俪访问页面 404
灾难 2:feixiang-parent 是 jar
<packaging>jar</packaging> <!-- ❌ 父项目不能用 jar -->
后果:
mvn package试图编译父项目,但父项目没有src/main/java- 构建报错
No sources to compile - 更严重的是,子模块无法正确继承父 POM 的
dependencyManagement,导致依赖版本混乱
操作步骤
步骤 1:配置 feixiang-parent(pom)
<!-- feixiang-parent/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- ✅ 父项目必须是 pom -->
<!-- 统一管理依赖版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 聚合子模块 -->
<modules>
<module>feixiang-common</module>
<module>employee-system</module>
<module>payroll-service</module>
</modules>
</project>
步骤 2:配置 feixiang-common(jar)
<!-- feixiang-common/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>feixiang-common</artifactId>
<!-- packaging 省略,默认就是 jar -->
<!-- <packaging>jar</packaging> -->
</project>
构建产物:
cd feixiang-common
mvn package
target/
└── feixiang-common-1.0.0-SNAPSHOT.jar
步骤 3:配置 employee-system(war)
<!-- employee-system/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>employee-system</artifactId>
<packaging>war</packaging> <!-- ✅ Web 应用必须是 war -->
<dependencies>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
构建产物:
cd employee-system
mvn package
target/
└── employee-system-1.0.0-SNAPSHOT.war
小崔用压缩软件打开 WAR 文件,验证内部结构:
employee-system-1.0.0-SNAPSHOT.war
├── META-INF/
│ └── MANIFEST.MF
├── WEB-INF/
│ ├── classes/
│ │ └── com/feixiang/employee/... # 自己的编译类
│ ├── lib/
│ │ ├── feixiang-common-1.0.0-SNAPSHOT.jar
│ │ ├── spring-webmvc-5.3.21.jar
│ │ └── spring-context-5.3.21.jar
│ └── web.xml
└── index.html
操作结果及分析
| 项目 | packaging | 产物 | 部署方式 | 关键验证点 |
|---|---|---|---|---|
feixiang-parent | pom | 无 | 安装到仓库供继承 | mvn install 后仓库中有 .pom 文件 |
feixiang-common | jar | .jar | 被其他项目依赖 | JAR 内包含 .class 和 META-INF |
employee-system | war | .war | 部署到 Tomcat | WAR 内有 WEB-INF/classes/ 和 WEB-INF/lib/ |
易错点与常见问题
误区一:Web 项目用 jar,然后手动改扩展名
错误做法:小崔把 employee-system 的 packaging 写成 jar,构建后手动把 .jar 重命名为 .war,丢进 Tomcat。
后果:
- Tomcat 解压 WAR 后,发现里面没有
WEB-INF/classes/和WEB-INF/lib/,所有类文件都在根目录 - 类加载器找不到 Servlet 类,应用启动失败
- 依赖 JAR 没有被打包进来,
ClassNotFoundException满天飞
纠正:Web 项目必须声明 packaging = war,让 maven-war-plugin 按规范打包:
<packaging>war</packaging> <!-- ✅ 让 Maven 自动处理 WEB-INF 结构 -->
误区二:父项目忘记设 pom
错误配置:
<project ...>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<!-- packaging 省略,默认 jar ❌ -->
</project>
后果:
mvn compile报错No sources to compile,因为父项目没有源代码- 子模块继承父 POM 时,某些插件配置传递异常
- 父项目被发布到仓库时,附带一个空的 JAR 文件,占用空间且无意义
纠正:任何作为父项目或聚合项目的 POM,必须显式声明 packaging = pom:
<packaging>pom</packaging> <!-- ✅ 父项目的强制要求 -->
误区三:认为 packaging 只是"输出文件扩展名"
错误认知:"packaging = war 和 packaging = jar 的区别,就是输出文件后缀不同嘛。"
纠正:packaging 改变的远不止扩展名。它通过生命周期绑定改变了整个构建流程:
| 生命周期阶段 | packaging = jar | packaging = war |
|---|---|---|
compile | 编译到 target/classes | 编译到 target/classes |
test | 运行 JUnit 测试 | 运行 JUnit 测试 |
package | maven-jar-plugin 打 JAR | maven-war-plugin 打 WAR |
install | 安装 JAR + POM 到本地仓库 | 安装 WAR + POM 到本地仓库 |
maven-war-plugin 还会额外做很多事:
- 把
src/main/webapp/下的静态资源复制到 WAR 根目录 - 把依赖的 JAR 收集到
WEB-INF/lib/ - 验证
web.xml的合法性(如果存在) - 生成 WAR 特有的
META-INF/context.xml(可选)
packaging 是"构建策略"的选择,不是"文件后缀"的选择。
小结
packaging 是 Maven 项目的产物形态声明,默认值 jar,常用值还有 war 和 pom。jar 适用于普通 Java 库和服务,war 适用于 Web 应用(由 Servlet 容器部署),pom 适用于父项目和聚合项目(不产生直接产物,只管理其他模块)。packaging 通过生命周期绑定影响构建流程,选择错误会导致部署失败或构建异常。
本章与全局的关系:本章完成了对 POM 核心四要素(GAV + packaging)的讲解。理解 packaging 后,你已经能正确创建和配置一个 Maven 项目。后续章节将进入依赖管理、生命周期、插件体系等更深入的机制,让你能驾驭复杂的企业级项目构建。