将SpringBoot应用从JAR转换为WAR包并部署到外部Tomcat全过程

 更新时间:2026年03月03日 09:45:01   作者:Jinkxs  
默认情况下,SpringBoot项目被打包为可执行的 JAR 文件,并通过内嵌的Tomcat、Jetty或Undertow启动,然而,在某些企业环境中,我们需要将SpringBoot应用打包为WAR,所以本文将深入探讨如何将一个标准的 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 时,这个内嵌容器不仅多余,还可能引发类路径冲突(如 ClassNotFoundExceptionNoSuchMethodError)。

因此,必须将 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

  1. 访问 Apache Tomcat 官网 下载最新稳定版(如 10.1.x)。
  2. 解压到本地目录,例如 /opt/tomcatC:\tomcat
  3. 确保已安装 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)。你可以通过以下方式自定义:

  1. 重命名 WAR 文件:例如 myapp.war → 访问路径为 /myapp
  2. 配置 application.properties(仅影响内嵌容器,对外部 Tomcat 无效)
  3. 使用 ROOT.war:将 WAR 命名为 ROOT.war,则上下文路径为 /(根路径)

常见问题与解决方案

在 WAR 部署过程中,开发者常遇到以下问题。我们逐一分析并提供解决方法。

1. 启动失败:ClassNotFoundException 或 NoClassDefFoundError

现象:Tomcat 启动时报错,找不到 Spring 或 Tomcat 相关类。

原因:通常是内嵌 Tomcat 未正确排除,或 provided 依赖未正确处理。

解决方案

  • 确认 pom.xmlspring-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(外部容器)
打包方式jarwar
服务器内嵌 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)实现自动化:

  1. 代码提交触发构建
  2. 生成 WAR 包
  3. 通过 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,只需:

  1. 排除 spring-boot-starter-tomcat
  2. 添加对应 starter(如 spring-boot-starter-jetty
  3. 设置为 provided scope

例如 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包的资料请关注脚本之家其它相关文章!

相关文章

  • Mybatis获取参数值和查询功能的案例详解

    Mybatis获取参数值和查询功能的案例详解

    这篇文章主要介绍了Mybatis获取参数值和查询功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Spring Security 自动踢掉前一个登录用户的实现代码

    Spring Security 自动踢掉前一个登录用户的实现代码

    这篇文章主要介绍了Spring Security 自动踢掉前一个登录用户的实现代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • springboot在filter中如何用threadlocal存放用户身份信息

    springboot在filter中如何用threadlocal存放用户身份信息

    这篇文章主要介绍了springboot中在filter中如何用threadlocal存放用户身份信息,本文章主要描述通过springboot的filter类,在过滤器中设置jwt信息进行身份信息保存的方法,需要的朋友可以参考下
    2024-07-07
  • SpringBoot与Kafka整合方案

    SpringBoot与Kafka整合方案

    本文将带你全面掌握SpringBoot与Kafka 的整合方案,从环境搭建到高级特性,从代码实现到性能调优,让你既能理解底层原理,又能解决实际开发中的各种问题,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-10-10
  • Java接口和抽象类有什么区别

    Java接口和抽象类有什么区别

    这篇文章主要介绍了Java接口和抽象类有什么区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • Java继承的实现与继承限制分析

    Java继承的实现与继承限制分析

    这篇文章主要介绍了Java继承的实现与继承限制,结合具体实例形式分析了Java继承的定义、实现以及继承的相关限制,需要的朋友可以参考下
    2019-01-01
  • Spring boot详解fastjson过滤字段为null值如何解决

    Spring boot详解fastjson过滤字段为null值如何解决

    这篇文章主要介绍了解决Spring boot中fastjson过滤字段为null值的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Java Spring集成MapStruct详情

    Java Spring集成MapStruct详情

    这篇文章主要介绍了Java Spring集成MapStruct详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • java适配器模式之万物拟人化

    java适配器模式之万物拟人化

    本文详细介绍了什么是适配器模式,适配器模式的种类以及各种类的详细讲解等,需要学习该知识点的小伙伴可以参考这篇文章
    2021-08-08
  • 关于ArrayList初始化容量的问题

    关于ArrayList初始化容量的问题

    这篇文章主要介绍了关于ArrayList初始化容量的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03

最新评论