在现代 java web 开发中,spring boot 凭借其“约定优于配置”的理念和内嵌服务器的便利性,已经成为构建微服务和单体应用的首选框架。默认情况下,spring boot 项目被打包为可执行的 jar 文件,并通过内嵌的 tomcat、jetty 或 undertow 启动。然而,在某些企业环境中,尤其是那些已有传统 java ee 基础设施或强制要求使用外部 servlet 容器(如 apache tomcat)的场景下,我们需要将 spring boot 应用打包为 war(web application archive)格式,以便部署到独立的 tomcat 服务器上。
本文将深入探讨如何将一个标准的 spring boot 应用从 jar 转换为 war 包,并成功部署到外部 tomcat 容器中。我们将涵盖项目结构改造、依赖调整、启动类修改、构建配置、本地测试以及生产部署等完整流程,并辅以详细的代码示例和原理说明。无论你是刚接触 spring boot 的新手,还是需要应对企业级部署需求的开发者,本文都将为你提供清晰、实用的指导。
为什么选择 war 包部署?
尽管 spring boot 的内嵌容器带来了极大的开发便利性,但在实际生产环境中,仍有不少理由促使我们选择传统的 war 包部署方式:
企业合规与标准化
许多大型企业拥有统一的应用服务器管理策略,要求所有 web 应用必须部署在中央管理的 tomcat 或 weblogic 实例上,便于监控、日志集中、安全策略实施和资源隔离。
共享资源与性能调优
在高并发场景下,多个应用共享同一个 jvm 和 servlet 容器实例可以减少内存开销(尽管需谨慎处理类加载冲突)。同时,运维团队可能对 tomcat 有深度调优经验,更倾向于使用熟悉的外部容器。
遗留系统集成
当 spring boot 应用需要与旧版 java ee 应用共存于同一服务器时,war 部署是自然的选择,便于共享会话、上下文或数据库连接池等资源。
ci/cd 流水线兼容
某些企业的持续集成/持续部署流水线已经围绕 war 包构建了完整的自动化流程(如 jenkins + tomcat manager),迁移成本较高。
注意:spring boot 官方文档明确指出,虽然支持 war 部署,但推荐优先使用可执行 jar + 内嵌容器的方式,因为这是 spring boot 设计的核心优势所在。只有在确实需要时才选择 war 方案。
准备工作:创建一个基础 spring boot 项目
在开始改造之前,我们先创建一个最简单的 spring boot web 应用作为起点。你可以使用 spring initializr 快速生成项目骨架。
项目配置(maven)
- project: maven project
- language: java
- spring boot: 3.2.x(或最新稳定版)
- packaging: jar(初始为 jar,后续改为 war)
- java version: 17(或你环境支持的 lts 版本)
- dependencies: spring web, spring boot devtools(可选)
生成并导入 ide 后,你会得到如下核心文件结构:
demo-war-app/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/demowarapp/ │ │ ├── demowarappapplication.java │ │ └── controller/ │ │ └── hellocontroller.java │ └── resources/ │ └── application.properties
核心代码示例
demowarappapplication.java(主启动类):
package com.example.demowarapp;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
@springbootapplication
public class demowarappapplication {
public static void main(string[] args) {
springapplication.run(demowarappapplication.class, args);
}
}
hellocontroller.java(简单 rest 接口):
package com.example.demowarapp.controller;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
public class hellocontroller {
@getmapping("/hello")
public string sayhello() {
return "hello from spring boot war!";
}
}
此时,运行 mvn spring-boot:run 或直接执行主方法,应用将在内嵌 tomcat 上启动,默认端口 8080,访问 http://localhost:8080/hello 可看到返回信息。
第一步:修改打包方式为 war
要生成 war 包,首先需要告诉构建工具(maven 或 gradle)改变默认的打包类型。
maven 配置 (pom.xml)
在 <packaging> 标签中指定为 war:
<packaging>war</packaging>
完整片段如下:
<groupid>com.example</groupid> <artifactid>demo-war-app</artifactid> <version>0.0.1-snapshot</version> <packaging>war</packaging> <!-- 关键修改 --> <name>demo-war-app</name> <description>demo project for spring boot war deployment</description>
gradle 配置 (build.gradle)
如果你使用 gradle,则需应用 war 插件:
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
id 'war' // 添加此行
}
group = 'com.example'
version = '0.0.1-snapshot'
sourcecompatibility = '17'
// 其他配置...
✅ 提示:maven 用户注意,spring-boot-starter-parent 已经预配置了 maven-war-plugin,通常无需额外声明。
第二步:排除内嵌 tomcat 依赖
spring boot web starter 默认包含内嵌的 tomcat 服务器(spring-boot-starter-tomcat)。当我们将应用部署到外部 tomcat 时,这个内嵌容器不仅多余,还可能引发类路径冲突(如 classnotfoundexception 或 nosuchmethoderror)。
因此,必须将 spring-boot-starter-tomcat 设置为 provided scope(maven)或 providedcompile(gradle),表示该依赖在编译时需要,但在运行时由 servlet 容器提供。
maven 配置 (pom.xml)
在 spring-boot-starter-web 依赖中排除 tomcat,并显式声明为 provided:
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<!-- 排除内嵌 tomcat -->
<exclusions>
<exclusion>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
</exclusion>
</exclusions>
</dependency>
<!-- 显式添加 tomcat 依赖,scope 为 provided -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
<scope>provided</scope>
</dependency>
<!-- 其他依赖... -->
</dependencies>
gradle 配置 (build.gradle)
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
providedruntime 'org.springframework.boot:spring-boot-starter-tomcat'
// 其他依赖...
}
⚠️ 重要:不要完全移除 spring-boot-starter-tomcat!spring boot 的自动配置仍需要它提供的类(如 tomcatservletwebserverfactory),只是运行时不加载内嵌服务器实例。
第三步:改造主启动类
spring boot 应用要作为 war 部署,必须实现 springbootservletinitializer 接口。该接口是 spring boot 与 servlet 3.0+ 规范的桥梁,允许应用在 servlet 容器启动时被正确初始化。
修改demowarappapplication.java
package com.example.demowarapp;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
import org.springframework.boot.builder.springapplicationbuilder;
import org.springframework.boot.web.servlet.support.springbootservletinitializer;
@springbootapplication
public class demowarappapplication extends springbootservletinitializer {
@override
protected springapplicationbuilder configure(springapplicationbuilder application) {
return application.sources(demowarappapplication.class);
}
public static void main(string[] args) {
springapplication.run(demowarappapplication.class, args);
}
}
关键点解析:
- 继承
springbootservletinitializer:这是 war 部署的必要条件。 - 重写
configure()方法:该方法在 servlet 容器(如 tomcat)启动 war 时被调用,用于构建springapplication实例。application.sources(...)指定了主配置类。 - 保留
main()方法:这样应用既可以作为 war 部署,也可以通过java -jar(如果仍打包为 jar)或 ide 直接运行,保持开发灵活性。
🔍 原理:servlet 3.0+ 规范引入了 servletcontainerinitializer 机制。spring boot 通过 springbootservletinitializer 实现了该机制,使得 war 包在部署时能自动触发 spring boot 的启动流程,而无需 web.xml。
第四步:验证项目结构与构建 war 包
完成上述修改后,项目应具备正确的 war 结构。让我们检查并构建。
项目结构期望
标准 war 包应包含:
web-inf/classes/:编译后的 java 类和资源文件web-inf/lib/:第三方依赖(不包括 provided 范围的)web-inf/lib-provided/:spring boot 特有的目录,存放 provided 依赖(仅当使用 spring boot maven plugin 时)meta-inf/:元数据
spring boot 的 war 包略有不同:它既是标准 war(可部署到 tomcat),又是可执行 jar(如果保留内嵌容器)。但在我们的配置下,由于排除了内嵌 tomcat,生成的 war 不可执行,只能部署到外部容器。
构建命令
maven:
mvn clean package
gradle:
./gradlew clean bootwar # 注意:spring boot gradle plugin 使用 bootwar 任务
📌 注意:gradle 用户应使用 bootwar 而非 war 任务,因为 bootwar 会正确处理 spring boot 的特殊需求(如启动类、依赖范围等)。
构建成功后,war 文件位于:
- maven:
target/demo-war-app-0.0.1-snapshot.war - gradle:
build/libs/demo-war-app-0.0.1-snapshot.war
你可以用解压工具打开 war 文件,确认 web-inf/lib/ 中不包含 tomcat-embed-core 等内嵌 tomcat jar。
第五步:本地 tomcat 部署与测试
现在,我们将 war 包部署到本地 tomcat 实例进行测试。
下载并安装 tomcat
- 访问 apache tomcat 官网 下载最新稳定版(如 10.1.x)。
- 解压到本地目录,例如
/opt/tomcat或c:\tomcat。 - 确保已安装 jdk 17+ 并配置好
java_home。
💡 提示:tomcat 10+ 默认使用 jakarta ee 9 命名空间(jakarta.*),而 spring boot 3.x 也已迁移到 jakarta ee 9。因此务必使用 tomcat 10 或更高版本。若使用 spring boot 2.x,则需 tomcat 9(支持 javax.*)。
部署 war 包
将生成的 war 文件复制到 tomcat 的 webapps/ 目录:
cp target/demo-war-app-0.0.1-snapshot.war $tomcat_home/webapps/
启动 tomcat
进入 tomcat 的 bin/ 目录,执行启动脚本:
- linux/macos:
./catalina.sh run - windows:
catalina.bat run
观察控制台日志,应能看到 spring boot 应用的启动信息,类似:
info o.s.b.w.e.tomcat.tomcatwebserver - tomcat initialized with port(s): 0 (http) info o.s.b.startupinfologger - starting demowarappapplication using java 17... ... info o.s.b.w.e.tomcat.tomcatwebserver - tomcat started on port(s): ... (http) with context path '/demo-war-app-0.0.1-snapshot'
访问应用
打开浏览器,访问:
http://localhost:8080/demo-war-app-0.0.1-snapshot/hello
你应该看到返回:
hello from spring boot war!
成功!这表明 war 包已正确部署并运行。
自定义 context path(可选)
默认情况下,war 文件名(不含 .war)即为上下文路径(context path)。你可以通过以下方式自定义:
- 重命名 war 文件:例如
myapp.war→ 访问路径为/myapp - 配置
application.properties(仅影响内嵌容器,对外部 tomcat 无效) - 使用
root.war:将 war 命名为root.war,则上下文路径为/(根路径)
常见问题与解决方案
在 war 部署过程中,开发者常遇到以下问题。我们逐一分析并提供解决方法。
1. 启动失败:classnotfoundexception 或 noclassdeffounderror
现象:tomcat 启动时报错,找不到 spring 或 tomcat 相关类。
原因:通常是内嵌 tomcat 未正确排除,或 provided 依赖未正确处理。
解决方案:
- 确认
pom.xml中spring-boot-starter-tomcat的 scope 为provided - 检查 war 包的
web-inf/lib/目录,确保无tomcat-embed-*jar - 若使用 gradle,确认使用
bootwar任务而非war
2. 应用无法访问,返回 404
现象:tomcat 启动成功,但访问 /context-path/hello 返回 404。
原因:
- 上下文路径错误(检查 war 文件名)
- controller 未被扫描到
- spring boot 未正确初始化
解决方案:
- 查看 tomcat 日志,确认 spring boot 是否启动成功
- 确保
@springbootapplication注解的类在正确包路径下(默认扫描子包) - 尝试访问根路径
/context-path/,看是否返回 whitelabel error page(说明 spring mvc 已工作)
3. 静态资源(css/js)无法加载
现象:html 页面能访问,但样式和脚本 404。
原因:spring boot 默认将静态资源放在 src/main/resources/static/,war 部署时这些资源会被正确打包到 web-inf/classes/static/,应能正常访问。
解决方案:
- 确认资源文件位置正确
- 检查浏览器开发者工具中的网络请求路径
- 避免在 controller 中拦截所有路径(如
/**)
4. 数据库连接池问题
现象:应用启动时无法连接数据库。
原因:hikaricp 等连接池在外部容器中可能因类加载器问题无法初始化。
解决方案:
- 确保数据库驱动 jar 放在 tomcat 的
lib/目录(全局共享),或打包进 war 的web-inf/lib/ - 在
application.properties中显式配置连接池参数
高级配置:优化 war 部署体验
除了基本部署,我们还可以进行一些优化,提升可维护性和性能。
1. 自定义启动日志
在 application.properties 中配置日志级别:
logging.level.org.springframework=info logging.level.com.example=debug
日志文件默认输出到 tomcat 的 logs/catalina.out。如需独立日志文件,可配置 logback:
src/main/resources/logback-spring.xml:
<configuration>
<appender name="file" class="ch.qos.logback.core.rolling.rollingfileappender">
<file>logs/demo-app.log</file>
<rollingpolicy class="ch.qos.logback.core.rolling.timebasedrollingpolicy">
<filenamepattern>logs/demo-app.%d{yyyy-mm-dd}.log</filenamepattern>
</rollingpolicy>
<encoder>
<pattern>%d{hh:mm:ss.sss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="file" />
</root>
</configuration>
注意:确保 tomcat 进程对 logs/ 目录有写权限。
2. 外部化配置
将 application.properties 外部化,便于不同环境部署:
- 将配置文件放在 tomcat 的
conf/目录 - 通过 jvm 参数指定配置路径:
export catalina_opts="-dspring.config.location=file:/opt/tomcat/conf/demo-app.properties"
3. 健康检查与监控
启用 spring boot actuator:
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-actuator</artifactid>
</dependency>
在 application.properties 中暴露端点:
management.endpoints.web.exposure.include=health,info,metrics management.endpoint.health.show-details=always
部署后访问 http://localhost:8080/context-path/actuator/health 可查看健康状态。
war 与 jar 部署对比
关键差异总结:
| 特性 | jar(内嵌容器) | war(外部容器) |
|---|---|---|
| 打包方式 | jar | war |
| 服务器 | 内嵌 tomcat/jetty | 外部 tomcat/weblogic |
| 启动命令 | java -jar app.jar | 复制到 webapps/ |
| 进程模型 | 独立 java 进程 | 共享 tomcat 进程 |
| 配置复杂度 | 低(开箱即用) | 中(需排除依赖、改启动类) |
| 适用场景 | 微服务、云原生、快速原型 | 传统企业、合规要求、遗留集成 |
生产环境最佳实践
在将 war 包投入生产前,请遵循以下建议:
1. 使用 profile 管理环境配置
通过 spring profiles 区分开发、测试、生产环境:
application-prod.properties:
spring.datasource.url=jdbc:mysql://prod-db:3306/mydb spring.datasource.username=prod_user spring.jpa.hibernate.ddl-auto=validate
启动时激活 profile:
export catalina_opts="-dspring.profiles.active=prod"
2. 安全加固
- 禁用不必要的 actuator 端点
- 配置 https(在 tomcat 层面)
- 使用强密码和加密配置
3. 性能调优
- 调整 tomcat 线程池大小(
server.xml中的maxthreads) - 优化 jvm 参数(堆内存、gc 策略)
- 启用 gzip 压缩(tomcat 的
compression属性)
4. 自动化部署
结合 ci/cd 工具(如 jenkins)实现自动化:
- 代码提交触发构建
- 生成 war 包
- 通过 tomcat manager api 部署(需配置用户权限)
tomcat-users.xml 示例:
<tomcat-users>
<role rolename="manager-script"/>
<user username="deployer" password="secure_password" roles="manager-script"/>
</tomcat-users>
jenkins 部署脚本片段:
curl -u deployer:secure_password \ http://localhost:8080/manager/text/deploy?path=/myapp \ --upload-file target/myapp.war
替代方案:使用 undertow 或 jetty
虽然本文聚焦 tomcat,但 spring boot 也支持其他 servlet 容器。若企业使用 jetty 或 undertow,只需:
- 排除
spring-boot-starter-tomcat - 添加对应 starter(如
spring-boot-starter-jetty) - 设置为
providedscope
例如 jetty(maven):
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<exclusions>
<exclusion>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-jetty</artifactid>
<scope>provided</scope>
</dependency>
总结:何时选择 war 部署?
将 spring boot 应用打包为 war 并部署到外部 tomcat,是一种特定场景下的折中方案。它牺牲了 spring boot “开箱即用”的部分便利性,换取了与传统 java ee 生态的兼容性。
推荐使用 war 部署的情况:
- 企业强制要求使用中央管理的 tomcat 集群
- 需要与旧版 war 应用共享会话或资源
- 运维团队熟悉 tomcat 调优且不愿管理多个内嵌容器实例
应避免 war 部署的情况:
- 新建微服务项目
- 云原生环境(kubernetes/docker)
- 追求快速迭代和简化部署流程
无论选择哪种方式,理解其背后的原理(servlet 规范、类加载机制、依赖管理)都是关键。希望本文的详细步骤和示例能帮助你在需要时顺利实现 spring boot 的 war 部署。
以上就是将springboot应用从jar转换为war包并部署到外部tomcat全过程的详细内容,更多关于springboot应用jar转war包的资料请关注代码网其它相关文章!
发表评论