乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

packaging

本章承接"GAV 坐标",讲解 Maven 项目坐标的第四个要素——packaging。如果说 GAV 回答了"这个项目是谁、叫什么、第几代",那么 packaging 回答的是"这个项目长什么样"——它是一个可运行的 JAR、一个部署到 Tomcat 的 WAR,还是一个管理其他模块的 POM?


核心机制

这是项目产生的产物类型,默认值是 jar。它直接决定了 Maven 的生命周期绑定和默认文件扩展名。当你把 packaging 从 jar 改成 war 时,Maven 自动调用的插件、生成的文件格式、甚至目录结构要求都会发生变化。

packaging 的本质:决定"项目形态"

packaging 声明了项目构建完成后,应该以什么物理形态存在。Maven 根据这个声明,自动选择对应的生命周期绑定和插件组合。

packaging产物扩展名核心插件绑定适用场景
jar.jarmaven-jar-plugin普通 Java 库、后端服务、工具包
war.warmaven-war-pluginWeb 应用(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 类型的项目通常有两种角色:

  1. 父项目(Parent):定义子模块共享的依赖版本、插件版本、构建规则。例如飞翔科技的根项目 feixiang-parent,packaging = pom,下面聚合了 employee-system、payroll-service 等子模块。
  2. 聚合项目(Aggregator):只是为了方便一次性构建多个模块而存在。例如执行 mvn install 时,聚合项目会递归构建所有子模块。

生活类比:三种不同的"产品形态"

想象飞翔科技是一家食品工厂,packaging 就是产品的包装形态:

  • jar = 真空包装的食品盒。里面只有食物本身(.class 文件),没有餐具、没有调料包。消费者(其他项目)买回去后,需要自己准备锅碗瓢盆(运行环境)来加工。适合卖"原材料"——比如 feixiang-common 工具包。
  • war = 自热火锅套装。里面不仅有食物,还有加热包(WEB-INF/lib/ 里的依赖)、餐具(web.xml 配置)、甚至桌布(静态资源 HTML/CSS)。消费者只需要加水(部署到 Tomcat),就能直接吃。适合卖"即食产品"——比如 employee-system Web 应用。
  • 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-parentpom无安装到仓库供继承mvn install 后仓库中有 .pom 文件
feixiang-commonjar.jar被其他项目依赖JAR 内包含 .class 和 META-INF
employee-systemwar.war部署到 TomcatWAR 内有 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 = jarpackaging = war
compile编译到 target/classes编译到 target/classes
test运行 JUnit 测试运行 JUnit 测试
packagemaven-jar-plugin 打 JARmaven-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 项目。后续章节将进入依赖管理、生命周期、插件体系等更深入的机制,让你能驾驭复杂的企业级项目构建。

上一页
GAV坐标