将SpringBoot应用从JAR转换为WAR包并部署到外部Tomcat全过程
在现代 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包的资料请关注脚本之家其它相关文章!
相关文章
Spring Security 自动踢掉前一个登录用户的实现代码
这篇文章主要介绍了Spring Security 自动踢掉前一个登录用户的实现代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-05-05
springboot在filter中如何用threadlocal存放用户身份信息
这篇文章主要介绍了springboot中在filter中如何用threadlocal存放用户身份信息,本文章主要描述通过springboot的filter类,在过滤器中设置jwt信息进行身份信息保存的方法,需要的朋友可以参考下2024-07-07
Spring boot详解fastjson过滤字段为null值如何解决
这篇文章主要介绍了解决Spring boot中fastjson过滤字段为null值的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2022-07-07


最新评论