java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot应用JAR转WAR包

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

作者: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)

生成并导入 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);
    }
}

关键点解析:

🔍 原理:Servlet 3.0+ 规范引入了 ServletContainerInitializer 机制。Spring Boot 通过 SpringBootServletInitializer 实现了该机制,使得 WAR 包在部署时能自动触发 Spring Boot 的启动流程,而无需 web.xml

第四步:验证项目结构与构建 WAR 包

完成上述修改后,项目应具备正确的 WAR 结构。让我们检查并构建。

项目结构期望

标准 WAR 包应包含:

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 文件位于:

你可以用解压工具打开 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/ 目录,执行启动脚本:

观察控制台日志,应能看到 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 依赖未正确处理。

解决方案

2. 应用无法访问,返回 404

现象:Tomcat 启动成功,但访问 /context-path/hello 返回 404。

原因

解决方案

3. 静态资源(CSS/JS)无法加载

现象:HTML 页面能访问,但样式和脚本 404。

原因:Spring Boot 默认将静态资源放在 src/main/resources/static/,WAR 部署时这些资源会被正确打包到 WEB-INF/classes/static/,应能正常访问。

解决方案

4. 数据库连接池问题

现象:应用启动时无法连接数据库。

原因:HikariCP 等连接池在外部容器中可能因类加载器问题无法初始化。

解决方案

高级配置:优化 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 外部化,便于不同环境部署:

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. 安全加固

3. 性能调优

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 部署的情况

应避免 WAR 部署的情况

无论选择哪种方式,理解其背后的原理(Servlet 规范、类加载机制、依赖管理)都是关键。希望本文的详细步骤和示例能帮助你在需要时顺利实现 Spring Boot 的 WAR 部署。

以上就是将SpringBoot应用从JAR转换为WAR包并部署到外部Tomcat全过程的详细内容,更多关于SpringBoot应用JAR转WAR包的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文