资讯动态

Java Web项目war包打包指南:Maven、Gradle与IDE三种方式详解

发布时间:2026/9/16 18:27:05 来源:尧图企业网站定制
Java Web 项目交付给运维或者部署到 Tomcat绕不开一个动作打成 war 包。war 包这东西看着简单但真到了要手工配置、多环境部署、或者跟 Maven/Gradle 较劲的时候不少新手会卡很久。这篇文章就把最常用的三种 war 包打包方式拆开讲清楚——IDE 导出、Maven 构建、Gradle 构建会给你可以直接抄的步骤也会把原理和踩坑点一起交代明白。适合谁看正在上 Java Web 课程的在校生、刚转 Java 后端的新人、以及手里维护着老项目的同学。对老手来说重点可以看第六节的排查部分很多问题都是换环境、换容器之后才会冒出来的。1. 先理清 war 包到底是什么很多新同学第一次接触 war 包只知道“双击能部署”“Tomcat 能用”一旦自己动手打包就蒙了。war 包的全称是 Web Application Archive从名字就能看出来它不只是一个压缩文件而是 Java Web 应用的标准交付单元。war 包的本质是一个 zip 格式的压缩包里面固定按 Servlet 规范来组织目录结构。Servlet 容器比如 Tomcat、Jetty、Resin拿到 war 包后不需要任何额外配置就能识别里面的代码、依赖和配置文件。这就是 war 包跟普通 zip 最大的区别不是“能解压就行”而是必须符合规范。先说清楚 war 包里面大概长什么样。以我最近维护的一个小型 CMS 项目为例打成 war 后解压出来大概是这样的my-web-app.war ├── META-INF/ │ └── MANIFEST.MF ├── WEB-INF/ │ ├── web.xml │ ├── classes/ │ │ └── com/example/demo/ │ │ ├── controller/ │ │ ├── service/ │ │ └── mapper/ │ └── lib/ │ ├── spring-webmvc-5.3.39.jar │ ├── jackson-databind-2.15.2.jar │ └── ... ├── index.jsp可选 └── static/ ├── css/ ├── js/ └── images/1.1 war 包的目录结构WEB-INF目录是整个 war 包的核心。WEB-INF/classes放的是项目编译后的 class 文件包括自己写的 Controller、Service、Dao 和各种配置资源WEB-INF/lib放的是第三方依赖 jar 包。放到WEB-INF/classes和WEB-INF/lib里的类在运行时都会出现在 classpath 里。区别在于你自己写的类会被覆盖重编而外面这些 jar 一般是锁版本不变的。浏览器地址栏直接访问/WEB-INF/下面的文件是访问不到的这是规范里明确规定的防止用户直接下载到配置文件和源码。WEB-INF/web.xml是部署描述符。Servlet 3.0 以后支持注解和代码配置项目里没有 web.xml 也能跑但打包的时候很多工具默认要求有这个文件后面我会说怎么处理。META-INF/MANIFEST.MF主要记录一些与 jar 包类似的基础信息比如 Manifest-Version、Created-By。对于普通 Web 项目这里面的内容对运行没有决定性的影响但有些工具或容器会读取配置最好也保留。1.2 为什么要特意打成 war 包有同学会问直接把整个项目目录拷到 Tomcat 里是不是也一样答案是可以但很脆弱。如果少带一个 lib 包或者目录命名错了Tomcat 根本起不来。war 包把代码、配置、依赖全部按规范固化在一起部署时只需要一个文件谁拿到都能复制到任意一个兼容容器上跑这是它的核心价值。Tomcat 对 war 包的默认处理方式是自动解压。你把myapp.war放到webapps目录下Tomcat 启动时会自动解压出一个myapp目录再从这个目录加载应用。后面排问题的时候这个自动解压的机制既是方便也是坑源因为旧目录如果没删干净经常会把老版本代码“复活”出来。2. 把 web 项目打成 war 包的方式一Maven 构建Maven 是 Java 后端项目最常用的构建工具也是我推荐优先掌握的方式。它最大的优势是“可重复”不管谁在哪个环境上执行同一条命令得到的 war 包内容都是一致的。这一点是 IDE 手动导出做不到的。2.1 先确认 packaging 类型是 war用 Maven 打包第一件事是检查pom.xml根部的packaging配置。如果没有写Maven 默认按jar处理这样就算执行mvn package产出的也只是一个普通 jar 包Tomcat 无法直接识别。project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdmy-web-app/artifactId version1.0.0/version packagingwar/packaging /project把这个标签设置成war之后Maven 的默认生命周期里才会触发maven-war-plugin的war目标。这个目标会按规范生成目录结构把src/main/webapp下的静态资源、jsp 文件放到 war 根部把target/classes下的 class 放到WEB-INF/classes把依赖 jar 放到WEB-INF/lib。2.2 配置 maven-war-plugin 里的几个关键参数纯 Java Web 项目依赖较简单的时候只声明packagingwar也能打出包。但实际项目里我一般会额外配置maven-war-plugin主要控制三件事是否检查 web.xml、war 包文件名、输出目录。build finalName${project.artifactId}/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.4.0/version configuration failOnMissingWebXmlfalse/failOnMissingWebXml warName${project.artifactId}/warName outputDirectory${project.build.directory}/deploy/outputDirectory /configuration /plugin /plugins /buildfailOnMissingWebXml这个参数很关键。Servlet 3.0 之后项目完全可以用注解和类配置替代 web.xml但有些人机器上的插件版本比较老默认会强制要求有 web.xml。新项目没有这个文件执行mvn package就报错。设为false就是告诉插件没有 web.xml 也可以正常打包。warName控制生成的 war 包文件名。如果不设置默认用${artifactId}-${version}比如my-web-app-1.0.0.war。这个文件名会直接影响 Tomcat 部署后的上下文路径后面我会专门讲。outputDirectory是输出目录。默认输出在target/下面。我在做自动化发布时喜欢单独放一个target/deploy目录方便脚本去捞包避免跟其它产物混在一起。2.3 依赖 scope 决定哪些 jar 不会进 war用 Maven 打包时依赖的scope会直接决定它进不进WEB-INF/lib。这个点很多新手会忽略。比如javax.servlet-api、jsp-api这类由 Servlet 容器提供的 jar如果按默认的compile依赖打进 war 包部署到 Tomcat 后容易和容器自带的 class 冲突。正确做法是把 scope 设成provided表示“编译时需要运行时容器已经提供”。dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependencyprovided依赖在编译和测试阶段都在 classpath 中但maven-war-plugin在打包时不会把它复制到WEB-INF/lib。同理如果你用system scope引本机的一些 jar默认也不会进 war 包这个要特别注意。我见过项目里图省事用 system scope 关联某个 jar结果换了一台机器编译报错打出来的 war 也缺依赖。2.4 一条命令完成打包配置没问题之后打包动作就变得非常机械mvn clean package -DskipTestsclean会先把上一次的target目录删掉避免旧 class 残留。-DskipTests是跳过测试执行但会继续编译测试代码。如果你连测试代码编译都跳过可以用-Dmaven.test.skiptrue。实际发布时我一般用-DskipTests保留测试代码编译能帮我在打包前多发现一层编译问题。打包完成后war 包会出现在target目录下。可以用jar tf查看 war 包内部结构确认编译产物和依赖都在jar tf target/my-web-app.war输出里应该能看到WEB-INF/classes/com/example/...和WEB-INF/lib/spring-webmvc-...jar。这一步虽然简单但能在部署前提前发现依赖漏打问题。2.5 补充Spring Boot 项目想打成标准 war如果你用的是 Spring Boot默认打出来的是可执行 jar不是标准 war。非要打到外部 Tomcat 部署需要改三处。第一pom.xml里的 packaging 改成war。第二启动类继承SpringBootServletInitializer并重写configure方法。第三把内置 Tomcat 的依赖改成provided或者在依赖中排除掉。我用 Spring Boot 2.7 时通常会这样处理dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency如果不想排除 Tomcat也可以直接用providedRuntimescope。关键是让 war 包里不携带 Tomcat 的类否则外部 Tomcat 加载时会出现一堆奇怪的冲突。3. 把 web 项目打成 war 包的方式二Gradle 构建现在越来越多的新团队转向 Gradle尤其是多模块项目Gradle 的依赖缓存和增量构建确实香。虽然命令和 Maven 不一样但打 war 包的思路是相通的。3.1 启用 Gradle 的 war 插件Gradle 没有在默认 Java 插件里提供 war 打包任务需要显式声明war插件。最简单的最小配置是这个plugins { id java id war } group com.example version 1.0.0 war { archiveFileName my-web-app.war }war插件会自动关联java插件的compileJava任务执行 war 打包前会先编译代码。所有在compileClasspath里的依赖默认会被复制到最终包的WEB-INF/lib目录。执行打包只要一条命令gradle clean war或者用项目里的 Gradle Wrapper./gradlew clean war产物默认在build/libs/my-web-app.war。在 IDEA 里也可以直接打开右侧 Gradle 面板展开build任务列表双击war或clean。3.2 Gradle 里的 provided 依赖怎么处理Gradle 的war插件提供了两个非常有用的依赖配置providedCompile和providedRuntime。providedCompile表示编译时需要但不会进入 war 包providedRuntime表示运行时需要但不会进入 war 包。使用方式dependencies { providedCompile javax.servlet:javax.servlet-api:4.0.1 implementation org.springframework:spring-webmvc:5.3.39 implementation com.fasterxml.jackson.core:jackson-databind:2.15.2 }如果你的项目继续用老的compile写法Gradle 会有提示建议尽快迁移到implementation。对于 web 开发记住一句话容器给的 jar 用providedCompile业务依赖用implementation。3.3 Spring Boot Gradle 打成 war 的配置Spring Boot 项目如果使用 Gradle处理逻辑和 Maven 类似但语法有差异。要在外部 Tomcat 部署首先要在build.gradle中加上war插件然后添加providedRuntime依赖plugins { id java id org.springframework.boot version 2.7.18 id war }dependencies { implementation org.springframework.boot:spring-boot-starter-web providedRuntime org.springframework.boot:spring-boot-starter-tomcat }同时启动类仍然要继承SpringBootServletInitializer这一步很多人会漏掉。漏掉后本地内嵌 Tomcat 一切正常但丢到外部 Tomcat 会直接 404。3.4 Gradle 打出来的 war“变体”问题需要注意Spring Boot Gradle 插件会额外生成可执行 war 和普通 war 两类产物有时会出现xxx.war和xxx.war.original。xxxx.war.original是交给外部容器部署的版本可执行 war 则继续保留了内嵌 Tomcat可以直接java -jar启动。所以如果你的目标是“部署到外部 Tomcat”用 grep 或查看build/libs目录时记得优先找没有内嵌容器的那个版本。如果不确定可以解压后检查WEB-INF/lib里有没有tomcat-embed-*.jar有就是可执行版本没有才是标准 war 包。4. 把 web 项目打成 war 包的方式三IDE 导出IDE 导出是最“简单粗暴”的方式也是很多从老项目转过来的人经常用的方式。它不需要理解 Maven也不需要在命令行敲命令直接点几步鼠标就能出包。但它最大的缺点是太依赖当前机器环境而且很容易漏依赖。4.1 什么时候才需要用 IDE 导出我一般只在两种场景下用 IDE 导出。第一种是项目非常老完全没有接入 Maven 或者 Gradle还是最原始的手动添加 jar 包结构。第二种是临时需要给同事打一个包验证问题不想触发完整构建直接把当前编译状态导出去。如果你的项目已经接入了 Maven 或 Gradle就没必要手工导了。手工导出不但效率低还容易把本地没提交的临时文件打进去。4.2 IDEA 手动打包的完整流程IDEA 打包 war 的核心是 Artifacts。很多人每次打包都要卡在 Artifacts 配置上我按 2024 版本界面重新梳理一遍路径基本都是这样。第一步打开项目结构。快捷键是CtrlShiftAltS也可以在菜单栏找File - Project Structure...。第二步左侧选Artifacts点加号选择Web Application: Archive。然后它会问你基于哪个 Exploded 产物生成 Archive通常选择For xxx:war exploded。这里有个细节如果你之前没有创建过 Exploded 产物Archive 选项会很少。你需要先通过Web Application: Exploded创建一个再把编译输出和依赖添加进去。第三步在 Output Layout 里确认内容。正常一条链是aaa_war_exploded/下有WEB-INF/classes、WEB-INF/lib、静态资源目录WEB-INF/classes里包含Module Output也就是当前模块编译后的 classWEB-INF/lib里包含项目依赖的第三方 jar。如果lib里是空的说明依赖没有加进来。可以右键WEB-INF/lib选择Add Copy of - Library Files把你依赖的库加进去。这一步新手最容易丢打出来 war 一部署就报ClassNotFoundException。第四步配置输出目录。在 Artifacts 面板下方有Output directory默认可能是out/artifacts/xxx_war_exploded。建议单独指定一个目录比如D:/deploy方便你后续找包。第五步开始构建。点击 IDEA 顶部菜单Build - Build Artifacts...选择刚才配置的xxx:war再点Build。构建完成后war 包就会出现在第四步设置的输出目录里。4.3 顺手说一下 Eclipse 的导出方式Eclipse 不太一样如果你的项目是 Dynamic Web Project右键项目名选Export - WAR file在弹出的窗口里选择导出路径点击 Finish 就行。Eclipse 的导出窗口有一个Export source files选项勾选后会把.java源码也打进去一般不勾选。同时要注意 Eclipse 导出 war 时依赖 jar 是否完整取决于项目构建路径里Web App Libraries是否配置正确。它不像 Maven 一样自动拷贝所有依赖我见过很多次把 jar 放在某个自定义目录导致漏包的情况。5. 三种方式对比和适用场景三种方式都能产出标准 war 包但使用体验差异挺大。我整理了一张表方便你按项目现状选择对比维度Maven 方式Gradle 方式IDE 导出方式上手难度低中最低可重复性好同一代码任一环境结果一致好但需要掌握 Groovy 基础差依赖本机漏包概率高依赖管理强可见 scope 控制强providedCompile 明确弱主要看构建路径CI/CD 集成很成熟很成熟基本不适合多环境打包配合 profile 很方便配合不同 task 也可以每次手工切换适用场景新项目、规范团队、主推多模块、构建逻辑复杂老项目临时救急5.1 对不同场景的一些个人看法如果你的目标只是“把本地代码发出去给别人跑”IDE 导出确实是最快的方式。但如果你稍微有一点自动化意识都应该选择 Maven 或 Gradle。CI 服务器上不可能有人帮你点 IDEA 的 Build Artifacts更不可能在没装 IDEA 的 Linux 机器上打开 Eclipse。从团队协作角度看Maven 方式是目前 Java Web 项目默认的“通用语言”。pom 文件记录的就是构建过程新同事拉下来直接mvn package就能出包不用每个人都在本机多配一遍 Artifacts。5.2 我的选择建议我个人的选择顺序是默认用 Maven。团队大部分人熟悉资料多排坑经验多如果项目已经用 Gradle那就继续用 Gradle没必要强制切 Maven只有遇到连构建工具都没有的老项目才考虑 IDE 手动导出而且导出后一定要检查WEB-INF/lib。这个顺序不是看谁更“高级”而是看谁在团队协作中出错概率最低。6. 部署 war 包的细节和常见问题排查war 包最终要进容器很多问题不是打包阶段冒出来的而是放到 Tomcat 上才暴露的。这一节把部署流程和几个高频问题一起讲掉。6.1 Tomcat 部署 war 包标准流程先讲一个最完整的部署路径本地和服务器都能用。第一步确认 war 包文件名。如果你想通过http://ip:8080/直接访问应用不要带版本号把 war 文件命名为ROOT.warTomcat 会把上下文路径映射到根路径。如果叫myapp.war访问路径就是http://ip:8080/myapp/。第二步停止 Tomcat或者确保 Tomcat 没有起着时再操作。把 war 包复制到 Tomcat 的webapps目录。第三步如果之前部署过同名应用先删除同名的解压目录比如rm -rf webapps/myapp再复制新 war。否则 Tomcat 启动时可能沿用旧的解压目录。第四步启动 Tomcat。可以前台运行bin/catalina.sh run方便看日志也可以后台运行bin/startup.sh。启动完成后访问项目验证。第五步查看日志。Tomcat 的日志主要在logs/catalina.out、logs/localhost.日期.log、logs/localhost_access_log.日期.txt。部署失败时优先看localhost日志里面通常有详细的类加载和初始化异常。6.2 常见问题速查表现象可能原因解决办法没写packagingwar打出来是 jarMaven 默认 packaging 是 jar检查 pom.xml补上packagingwar/packaging报错webxml attribute is required项目没有 web.xml插件版本较老在 maven-war-plugin 或 gradle 配置中跳过 web.xml 检查部署后ClassNotFoundExceptionWEB-INF/lib缺依赖 jar检查依赖 scope确认 compile/implementation 依赖会进包IDE 导出时确认 lib 已加入页面还是旧代码Tomcat 未删除旧解压目录rm -rf webapps/项目名清理work/Catalina缓存访问 404上下文路径不对或没有默认首页确认 war 文件名、看 web.xml 里的 welcome-file中文乱码URL 编码、响应编码不一致在 server.xml 增加URIEncodingUTF-8统一页面和接口编码Spring Boot 项目本地能跑外部 Tomcat 404启动类没继承 SpringBootServletInitializer或 war 里带了内嵌 Tomcat修改启动类订正依赖范围重新打包Servlet 依赖冲突servlet-api打进了 war把下发给容器的依赖 scope 改成provided或providedCompile6.3 很多老手也会踩的几个坑先说“增量包”的问题。有些团队为了省时间不会每次打全量 war而是只替换WEB-INF/classes里改动的 class。这个方法不是不行但非常容易因为新旧 class 混在一起出现“本地没问题、线上有问题”。我的建议是除非你对整个应用结构非常清楚否则还是老老实实打全量包。再说上下文路径。文件名my-web-app-1.0.0.war部署后访问路径会变成http://ip:8080/my-web-app-1.0.0/。如果你用 Nginx 做反向代理路径带版本号会让后续升级非常麻烦。所以我部署时常会把 war 名固定为ROOT.war或myapp.war不带版本号然后由外部版本管理工具记录具体版本。还有一个容易忽略的问题war 包里的资源静态文件路径大小写。Linux 文件系统区分大小写本地 Windows 开发时不区分。你在本地写hrefCSS/style.cssWindows 能正常打开Linux 上可能 404。这种问题在排查时最容易让人崩溃因为它不是打包配置错了而是文件名大小写不一致。6.4 部署到 Docker 或其他服务器时的注意事项现在很多项目用 Docker 部署 Tomcatwar 包直接挂载进镜像或 volume。标准 Tomcat 镜像里的部署目录是/usr/local/tomcat/webapps把宿主机上的 war 包映射进去即可docker run -d -p 8080:8080 \ -v /opt/wars/myapp.war:/usr/local/tomcat/webapps/myapp.war \ tomcat:9.0用这种方式时war 包的文件名同样决定上下文路径。改名后重新映射即可不需要改代码再打包。但要注意如果宿主机上已经有同名的旧目录没有清掉也会出现旧代码问题。我习惯在挂载时连同work目录一起清掉避免 Jasper 编译 jsp 的缓存干扰判断。在 Kubernetes 这类环境里war 包一般会通过构建镜像时塞进 webapps本质不变只要镜像里的 war 包是完整、可重复构建的滚动更新时才不会出幺蛾子。7. 最后再给几条长期建议打了这么多年 war 包我最深的感触是war 包本身不复杂复杂的是“你以为你打对了”。很多问题都不是打包这一步产生的而是环境、依赖范围、上下文路径这些细节叠加出来的。如果你现在还在用 IDE 手动导出我强烈建议至少抽出半天时间把项目接上 Maven。哪怕是一个很简单的老项目先把目录结构按 Maven 整理好再配好maven-war-plugin之后所有环境就都能用同一条命令出包这条路走得越早越省心。最后分享一个小技巧不管用哪种方式打出 war 包发布前都执行一下mvn clean或者gradle clean然后用命令检查一下包内的WEB-INF/classes和WEB-INF/lib确认没有多余或缺失的内容。这一步大概花半分钟却能在部署前拦住大部分“远程抓狂”。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价