maven-war-plugin
本章是"插件"章节的最后一个具体插件讲解。
maven-war-plugin负责将 Web 应用打包为 WAR(Web Application Archive)格式,是 Java Web 项目部署到 Tomcat、Jetty 等 Servlet 容器的必经之路。理解 WAR 包的内部结构和打包过程,是前后端协作部署的基础。
核心机制
一个 WAR 包需要收集三类内容:
- artifact dependencies:项目依赖的 JAR(如 Spring MVC、MySQL 驱动)
- classes:本项目编译生成的
.class文件 - resources:静态资源(HTML、JS、CSS)和配置文件
这三者共同构成了一个可部署的 Web 应用。
WAR 包的内部结构
一个标准的 WAR 包本质上是一个 ZIP 压缩文件,内部有严格的目录约定:
example.war
├── META-INF/
│ └── MANIFEST.MF # 清单文件
├── WEB-INF/
│ ├── classes/ # 本项目的 .class 文件和配置文件
│ ├── lib/ # 第三方依赖 JAR
│ └── web.xml # Servlet 配置(传统方式)
├── index.html # 静态资源(可直接访问)
├── css/
├── js/
└── images/
关键规则:
WEB-INF/下的内容不能被浏览器直接访问,是 Web 应用的安全区域WEB-INF/classes/放本项目的编译输出,优先级高于WEB-INF/lib/中的 JARWEB-INF/lib/放所有第三方依赖,Servlet 容器会自动将其加入 classpath
finalName 配置
默认情况下,WAR 包的文件名是 ${artifactId}-${version}.war。例如 employee-web-1.0.0.war。
通过 finalName 可以自定义打包后的文件名:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<finalName>employee-web</finalName>
</configuration>
</plugin>
这样生成的文件名就是 employee-web.war,去掉了版本号,方便部署脚本固定引用。
打包过程
maven-war-plugin 的打包流程:
- 收集 classes:将
target/classes复制到WEB-INF/classes/ - 收集依赖:将
<dependencies>中scope为compile和runtime的 JAR 复制到WEB-INF/lib/ - 收集资源:将
src/main/webapp/下的静态资源复制到 WAR 根目录 - 生成 WAR:按标准结构压缩为
.war文件
生活类比:搬家打包
想象你要搬家(部署 Web 应用):
- 你的衣物和证件 =
WEB-INF/classes/。这是你个人的东西,最重要,要放在保险箱里(安全区域,外部不能直接访问)。 - 你买的家具和电器 =
WEB-INF/lib/。这些是第三方品牌的产品,你买了使用权,搬家时一起带走。 - 门口的地毯和门牌 = WAR 根目录的
index.html、css/、js/。这些是门面,访客可以直接看到。
maven-war-plugin 就是那个专业的搬家打包师傅,他知道:证件放保险箱、家具装箱贴标签、门牌挂门口——一切按标准流程来,到了新家(Tomcat)直接拎包入住。
图示
上图展示了 WAR 包的组装逻辑:三类输入资源按固定规则放入 WAR 的标准目录结构中。Servlet 容器(如 Tomcat)解压 WAR 后,正是依赖这个标准结构来加载类、资源和配置。
完整示例
场景
飞翔科技要开发一个员工信息管理的 Web 应用 employee-web。前端黄俪负责页面,后端小崔负责接口,运维李眉要求部署时 WAR 包文件名固定为 employee-web.war,不要带版本号。
操作前:项目状态
当前是普通的 JAR 项目结构:
employee-web/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ └── resources/
│ └── test/
└── target/
pom.xml 的 packaging 是 jar:
<packaging>jar</packaging>
操作步骤:改为 WAR 项目
第一步:修改 packaging 为 war
<packaging>war</packaging>
第二步:添加 servlet-api 依赖(provided,因为容器自带)
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
第三步:创建 src/main/webapp/ 目录并添加静态资源
employee-web/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ ├── resources/
│ │ └── webapp/ # 新增
│ │ ├── index.html
│ │ ├── css/
│ │ │ └── style.css
│ │ └── WEB-INF/
│ │ └── web.xml
│ └── test/
└── target/
第四步:显式声明 maven-war-plugin,配置 finalName
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<finalName>employee-web</finalName>
</configuration>
</plugin>
</plugins>
</build>
操作结果:打包前后的目录变化
执行打包:
$ mvn clean package
[INFO] --- maven-war-plugin:3.4.0:war (default-war) @ employee-web ---
[INFO] Packaging webapp
[INFO] Assembling webapp [employee-web] in [target\employee-web]
[INFO] Processing war project
[INFO] Copying webapp resources [src\main\webapp]
[INFO] Webapp assembled in [30 msecs]
[INFO] Building war: target\employee-web.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
打包前 target/ 目录:
target/
├── classes/
│ └── com/feixiang/...
└── generated-sources/
打包后 target/ 目录:
target/
├── classes/
├── employee-web/ # 展开的 WAR 目录(中间产物)
│ ├── index.html
│ ├── css/
│ ├── WEB-INF/
│ │ ├── classes/ # 本项目的 .class
│ │ ├── lib/ # 第三方 JAR
│ │ └── web.xml
│ └── META-INF/
└── employee-web.war # 最终产物
变化分析:
- 黄俪的
index.html和css/style.css被复制到了 WAR 根目录,部署后用户可以直接访问 - 小崔的后端代码编译后进入了
WEB-INF/classes/,Servlet 容器会加载这些类 scope=provided的servlet-api没有被打包进WEB-INF/lib/,因为 Tomcat 自带这个 JAR,避免版本冲突- 李眉的部署脚本可以固定引用
employee-web.war,不受版本号变化影响
易错点与常见问题
误区一:packaging 忘了改成 war
错误配置:
<packaging>jar</packaging>
后果:mvn package 会调用 maven-jar-plugin 生成 JAR 文件,而不是 WAR。JAR 文件没有 WEB-INF/ 结构,部署到 Tomcat 时会被拒绝或无法识别。Java Web 项目必须显式声明 <packaging>war</packaging>。
误区二:servlet-api 的 scope 不是 provided
错误配置:
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<!-- 漏写 <scope>provided</scope> -->
</dependency>
后果:servlet-api 会被打包进 WEB-INF/lib/。而 Tomcat 本身也自带 servlet-api.jar,运行时会出现类冲突(ClassCastException 或 LinkageError),因为同一个类被两个不同的类加载器加载。Web 容器提供的 API 一律用 provided。
误区三:静态资源放在 src/main/resources
错误认知:"我的 index.html 放在 src/main/resources 里,打包后应该能访问到吧?"
纠正:src/main/resources 的内容会被复制到 WEB-INF/classes/,而 WEB-INF/ 下的内容不能被浏览器直接访问。静态页面、JS、CSS 必须放在 src/main/webapp/ 下,才能出现在 WAR 根目录,供外部访问。
小结
maven-war-plugin 是 Java Web 项目的打包核心,它将编译输出、依赖 JAR 和静态资源按标准 WAR 结构组装为可部署包。finalName 可以简化部署脚本对文件名版本号的处理。记住三个关键规则:packaging 必须是 war、容器 API 必须是 provided、静态资源必须放在 src/main/webapp/。
本章与全局的关系:本章结束了"插件"章节。下一章"继承与聚合"将讲解如何用 parent 和 modules 管理多模块项目,让飞翔科技的多个服务共享统一的配置和依赖版本。