Spring Boot 打包部署全攻略:Jar vs War


✨哈喽!我是 我不是呆头呀!

📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记!

🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流!

  • 目录
    • 一、引言
    • [二、Jar 打包详解](#二、Jar 打包详解)
      • [2.1 默认打包机制(spring-boot-maven-plugin)](#2.1 默认打包机制(spring-boot-maven-plugin))
      • [2.2 可执行 Jar 的结构与启动原理](#2.2 可执行 Jar 的结构与启动原理)
      • [2.3 配置示例(pom.xml / build.gradle)](#2.3 配置示例(pom.xml / build.gradle))
        • [示例1:Maven 项目配置(pom.xml)](#示例1:Maven 项目配置(pom.xml))
        • [示例2:Gradle 项目配置(build.gradle / build.gradle.kts)](#示例2:Gradle 项目配置(build.gradle / build.gradle.kts))
      • [2.4 启动与调试命令](#2.4 启动与调试命令)
        • [2.4.1 常规启动命令](#2.4.1 常规启动命令)
        • [2.4.2 调试命令(开发/排障场景)](#2.4.2 调试命令(开发/排障场景))
        • [2.4.3 停止命令](#2.4.3 停止命令)
      • [2.5 Jar 打包部署流程图(Mermaid 格式)](#2.5 Jar 打包部署流程图(Mermaid 格式))
    • [三、War 打包详解](#三、War 打包详解)
      • [3.1 为何需要 War(如部署到传统 Servlet 容器)](#3.1 为何需要 War(如部署到传统 Servlet 容器))
      • [3.2 修改主类继承 SpringBootServletInitializer](#3.2 修改主类继承 SpringBootServletInitializer)
        • [示例3:修改后的主启动类(Spring Boot 3.x)](#示例3:修改后的主启动类(Spring Boot 3.x))
      • [3.3 Maven/Gradle 配置调整](#3.3 Maven/Gradle 配置调整)
        • [示例4:Maven 项目 WAR 打包配置(pom.xml)](#示例4:Maven 项目 WAR 打包配置(pom.xml))
        • [示例5:Gradle 项目 WAR 打包配置(build.gradle)](#示例5:Gradle 项目 WAR 打包配置(build.gradle))
      • [3.4 常见兼容性问题](#3.4 常见兼容性问题)
      • [3.5 War 打包部署流程图(Mermaid 格式)](#3.5 War 打包部署流程图(Mermaid 格式))
    • 四、核心对比分析
      • [4.1 启动速度、资源占用、部署灵活性](#4.1 启动速度、资源占用、部署灵活性)
      • [4.2 安全性与运维管理差异](#4.2 安全性与运维管理差异)
      • [4.3 微服务架构下的推荐选择](#4.3 微服务架构下的推荐选择)
    • 五、实战部署流程
      • [5.1 Jar 方式部署到 Linux 服务器(含 systemd 服务配置)](#5.1 Jar 方式部署到 Linux 服务器(含 systemd 服务配置))
      • [5.2 War 方式部署到 Tomcat 示例](#5.2 War 方式部署到 Tomcat 示例)
        • [步骤1:本地打包并上传 WAR 包](#步骤1:本地打包并上传 WAR 包)
        • [步骤2:准备 Tomcat 环境(兼容 Spring Boot 3.x)](#步骤2:准备 Tomcat 环境(兼容 Spring Boot 3.x))
        • [步骤3:配置 Tomcat(可选,修改端口、上下文路径)](#步骤3:配置 Tomcat(可选,修改端口、上下文路径))
        • [步骤4:启动 Tomcat 并验证部署结果](#步骤4:启动 Tomcat 并验证部署结果)
    • 六、故障排查与最佳实践
      • [6.1 常见故障排查](#6.1 常见故障排查)
        • [6.1.1 Jar 包部署故障排查](#6.1.1 Jar 包部署故障排查)
        • [6.1.2 War 包部署故障排查](#6.1.2 War 包部署故障排查)
      • [6.2 最佳实践](#6.2 最佳实践)
    • 七、结论与选型建议
      • [7.1 结论](#7.1 结论)
      • [7.2 选型建议](#7.2 选型建议)
      • [7.3 FAQ(常见问题解答)](#7.3 FAQ(常见问题解答))

目录

一、引言

Spring Boot 作为简化 Java 开发的一站式框架,其核心优势之一便是内嵌 Servlet 容器(Tomcat、Jetty、Undertow 默认可选),这一特性彻底颠覆了传统 Java Web 项目的部署模式------开发者无需手动配置外部容器,只需编写业务代码,即可实现项目的快速启动、测试与部署。

在传统 Java Web 开发中,项目最终通常打包为 WAR(Web Application Archive)格式,必须部署到独立的 Servlet 容器(如 Tomcat、JBoss)中才能运行,容器负责管理 Servlet 生命周期、请求转发等核心功能。而 Spring Boot 凭借内嵌容器,默认支持将项目打包为可执行 JAR(Java Archive)格式,该 JAR 不仅包含项目的业务代码、依赖包,还内嵌了完整的 Servlet 容器环境,无需额外配置外部容器,直接通过 java -jar 命令即可启动运行,极大地简化了部署流程,降低了运维成本。

然而,在实际生产环境中,并非所有场景都适合使用内嵌容器的 JAR 包部署:例如企业已有成熟的外部容器集群、需要对多个 Web 应用进行统一管理和资源隔离、需要利用外部容器的高级功能(如集群部署、虚拟主机配置)等。此时,将 Spring Boot 项目打包为 WAR 格式,部署到传统外部 Servlet 容器中,就成为了必要的选择。

本文将围绕 Spring Boot 3.x 版本,系统性地对比 Jar 与 War 两种打包方式的原理、适用场景、配置差异及完整部署流程,帮助中级 Java 开发者在实际项目中做出合理的选型,规避部署过程中的各类坑点。

二、Jar 打包详解

Spring Boot 的默认打包方式为 JAR 打包,其核心依赖 spring-boot-maven-plugin(Maven 项目)或 spring-boot-gradle-plugin(Gradle 项目),该插件能够将项目及其所有依赖打包为一个可执行的"胖 JAR"(Fat JAR),并提供了内嵌容器的启动、运行支持。

2.1 默认打包机制(spring-boot-maven-plugin)

Spring Boot 项目的 JAR 打包核心是 spring-boot-maven-plugin 插件,该插件并非 Maven 原生插件,而是 Spring Boot 官方提供的专用插件,其核心功能包括:

  1. 收集项目编译后的 class 文件、资源文件,以及所有第三方依赖包(如 Spring Core、MyBatis 等);
  2. 将内嵌 Servlet 容器(默认 Tomcat)的核心包一同打包进入 JAR;
  3. 生成特殊的目录结构和清单文件(MANIFEST.MF),指定主启动类和类加载器配置;
  4. 提供 repackage 目标,将普通 JAR 重新打包为可执行 JAR,同时保留原始 JAR 供依赖引用。

在 Spring Boot 3.x 项目中,该插件已通过 spring-boot-starter-parent 进行了默认配置,开发者无需额外指定插件版本,只需在 pom.xml 中简单声明即可启用。

2.2 可执行 Jar 的结构与启动原理

Spring Boot 生成的可执行 JAR 并非传统意义上的普通 JAR,其目录结构具有特殊性,我们可以通过解压 xxx.jar 查看其核心结构:

复制代码
xxx.jar
├── META-INF
│   ├── MANIFEST.MF  // 清单文件,指定主类、类加载器等核心配置
│   └── maven        // Maven 项目坐标信息
├── BOOT-INF
│   ├── classes      // 项目自身的编译产物(class文件、application.yml等资源)
│   └── lib          // 项目所有第三方依赖包(spring-boot-starter、mybatis等)
└── org
    └── springframework
        └── boot
            └── loader  // Spring Boot 自定义类加载器(LaunchedURLClassLoader)及启动逻辑
核心结构说明:
  1. MANIFEST.MF 清单文件 :核心配置项包括 Main-Class(Spring Boot 启动器类 org.springframework.boot.loader.JarLauncher)和 Start-Class(项目自身的主启动类,如 com.example.demo.DemoApplication)。
  2. BOOT-INF/classes :对应普通 Maven 项目的 target/classes 目录,存放项目自身的业务代码和配置文件。
  3. BOOT-INF/lib:存放项目所有的第三方依赖,解决了传统 JAR 无法包含依赖的问题。
  4. org/springframework/boot/loader :Spring Boot 自定义的类加载器核心,负责加载 BOOT-INF 目录下的类和依赖包,这是可执行 JAR 能够正常启动的关键。
启动原理:
  1. 当执行 java -jar xxx.jar 命令时,JVM 会读取 MANIFEST.MF 文件中的 Main-Class,即 JarLauncher,并启动该类。
  2. JarLauncher 初始化 Spring Boot 自定义的 LaunchedURLClassLoader 类加载器,该类加载器能够识别 BOOT-INF/classesBOOT-INF/lib 目录的结构,实现对项目类和依赖类的加载。
  3. JarLauncher 读取 MANIFEST.MF 文件中的 Start-Class,即项目的主启动类(如 DemoApplication)。
  4. 调用主启动类的 main() 方法,启动 Spring Boot 应用,内嵌容器随之初始化,完成项目的启动流程。

2.3 配置示例(pom.xml / build.gradle)

示例1:Maven 项目配置(pom.xml)

Spring Boot 3.x 项目中,只需继承 spring-boot-starter-parent,并声明 spring-boot-maven-plugin 插件即可,无需额外配置版本(由 parent 统一管理)。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 继承 Spring Boot 父工程,统一管理依赖版本和插件配置 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-boot-jar-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-jar-demo</name>
    <description>Spring Boot Jar 打包示例项目</description>

    <properties>
        <java.version>17</java.version> <!-- Spring Boot 3.x 要求 JDK 17+ -->
    </properties>

    <dependencies>
        <!-- Web 核心依赖,默认内嵌 Tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-boot-jar-demo</finalName> <!-- 最终生成的 JAR 包名称 -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 可选:开启打包时跳过测试 -->
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal> <!-- 核心目标:将普通 JAR 重新打包为可执行 JAR -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
示例2:Gradle 项目配置(build.gradle / build.gradle.kts)
groovy 复制代码
// build.gradle(Groovy 语法)
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0' // Spring Boot 插件
    id 'io.spring.dependency-management' version '1.1.4' // 依赖管理插件
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
name = 'spring-boot-jar-demo'
description = 'Spring Boot Jar 打包示例项目'

java {
    sourceCompatibility = JavaVersion.VERSION_17 // JDK 17+
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

// 打包配置:生成可执行 JAR
bootJar {
    archiveName = 'spring-boot-jar-demo.jar' // 最终 JAR 包名称
    excludeDevtools = true // 排除 devtools 依赖(生产环境无需)
}

2.4 启动与调试命令

2.4.1 常规启动命令
  1. 基础启动命令(前台运行,关闭终端则进程终止):
bash 复制代码
java -jar spring-boot-jar-demo.jar
  1. 指定端口启动(覆盖 application.yml 中的端口配置):
bash 复制代码
java -jar spring-boot-jar-demo.jar --server.port=8081
  1. 指定配置文件启动(加载自定义配置文件):
bash 复制代码
# 加载 application-prod.yml 配置(激活 prod 环境)
java -jar spring-boot-jar-demo.jar --spring.profiles.active=prod

# 加载外部自定义配置文件
java -jar spring-boot-jar-demo.jar --spring.config.location=/opt/config/application.yml
  1. 后台运行(Linux/Mac 环境,输出日志到指定文件):
bash 复制代码
# nohup 忽略挂断信号,& 后台运行
nohup java -jar spring-boot-jar-demo.jar --spring.profiles.active=prod > /opt/logs/demo.log 2>&1 &
2.4.2 调试命令(开发/排障场景)
  1. 远程调试启动(允许 IDE 连接调试,端口 5005):
bash 复制代码
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar spring-boot-jar-demo.jar
  • 配置说明:suspend=n 表示项目启动后不阻塞,直接运行;若设置为 suspend=y,则项目启动后阻塞,等待 IDE 连接后再继续运行。
  1. 查看 JVM 参数启动(打印 JVM 运行参数,排查内存溢出等问题):
bash 复制代码
java -Xms512m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar spring-boot-jar-demo.jar
  • 配置说明:-Xms512m 初始堆内存,-Xmx1024m 最大堆内存。
2.4.3 停止命令
  1. 查找进程并终止(Linux 环境):
bash 复制代码
# 查找 JAR 包对应的进程 ID
ps -ef | grep spring-boot-jar-demo.jar | grep -v grep | awk '{print $2}'

# 终止进程(替换 <pid> 为上述命令查询到的进程 ID)
kill -15 <pid>

# 强制终止(进程无响应时使用,谨慎使用)
kill -9 <pid>

2.5 Jar 打包部署流程图(Mermaid 格式)



编写 Spring Boot 业务代码
配置 pom.xml/build.gradle(引入 spring-boot-maven/gradle-plugin)
执行打包命令:mvn clean package / gradle clean bootJar
生成可执行 JAR 包(target/build/libs 目录下)
上传 JAR 包到 Linux 服务器指定目录(如 /opt/app)
执行启动命令(前台/后台/指定配置)
启动是否成功?
项目正常运行,提供 Web 服务
查看日志(nohup.log / 控制台输出),排查配置/依赖问题,返回 A 调整

三、War 打包详解

尽管 Spring Boot 推荐 JAR 打包部署,但在传统企业级场景中,WAR 打包仍然具有不可替代的价值。War 打包的核心是将 Spring Boot 项目转换为符合 Servlet 规范的 Web 应用,移除内嵌容器的独占运行,使其能够部署到外部 Servlet 容器中运行。

3.1 为何需要 War(如部署到传统 Servlet 容器)

选择 War 打包的核心场景主要包括以下几点:

  1. 企业现有容器集群复用:多数传统企业已搭建成熟的 Tomcat/JBoss 集群,具备完善的运维、监控、扩容体系,将 Spring Boot 项目打包为 WAR 部署,无需重新搭建新的运行环境,降低运维成本和学习成本。
  2. 多应用统一管理:外部 Servlet 容器支持在单个实例上部署多个 WAR 应用,实现资源(内存、端口)的共享与隔离,便于统一管理和版本迭代。
  3. 利用外部容器的高级功能:外部 Tomcat/JBoss 提供了虚拟主机配置、SSL 证书统一配置、集群会话同步、访问日志切割等高级功能,这些功能在内嵌容器中配置复杂,甚至无法实现。
  4. 合规与审计要求:部分行业(如金融、政务)对应用部署环境有严格的合规要求,要求使用标准化的外部容器,禁止内嵌容器的独占运行模式。
  5. 遗留系统集成:Spring Boot 项目需要与传统的 Web 项目(如 Struts、SSH 项目)部署在同一容器中,实现会话共享、接口互通等集成需求。

3.2 修改主类继承 SpringBootServletInitializer

Spring Boot 项目打包为 WAR 后,需要遵循 Servlet 3.0+ 规范,由外部容器负责启动应用上下文。此时,项目的主启动类必须继承 SpringBootServletInitializer 并覆盖 configure() 方法,该类的核心作用是替代传统的 web.xml 配置,告诉外部容器如何初始化 Spring Boot 应用。

示例3:修改后的主启动类(Spring Boot 3.x)
java 复制代码
package com.example;

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;

/**
 * Spring Boot WAR 打包示例主启动类
 * 必须继承 SpringBootServletInitializer 并覆盖 configure 方法
 */
@SpringBootApplication
public class SpringBootWarDemoApplication extends SpringBootServletInitializer {

    // 核心:覆盖 configure 方法,指定 Spring Boot 应用入口
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // 传入当前主启动类的 Class 对象,初始化 Spring 应用上下文
        return application.sources(SpringBootWarDemoApplication.class);
    }

    // 保留 main 方法,不影响 JAR 打包启动(兼容两种打包方式)
    public static void main(String[] args) {
        SpringApplication.run(SpringBootWarDemoApplication.class, args);
    }
}

原理说明 :当 WAR 包部署到外部 Servlet 容器时,容器会首先扫描 META-INF/services/javax.servlet.ServletContainerInitializer 文件,该文件指向 SpringServletContainerInitializer,而 SpringServletContainerInitializer 会找到所有继承 SpringBootServletInitializer 的类,调用其 configure() 方法,从而初始化 Spring Boot 应用上下文,完成项目启动。

3.3 Maven/Gradle 配置调整

War 打包的配置核心包括两点:1. 修改打包类型为 war;2. 排除内嵌容器的独占依赖(防止与外部容器冲突)。

示例4:Maven 项目 WAR 打包配置(pom.xml)
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-boot-war-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-war-demo</name>
    <description>Spring Boot War 打包示例项目</description>
    <packaging>war</packaging> <!-- 1. 修改打包类型为 war(默认 jar) -->

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 2. 排除内嵌 Tomcat 依赖(防止与外部容器冲突) -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 3. 提供 Servlet API 依赖(scope 为 provided,外部容器已提供) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-boot-war-demo</finalName> <!-- 最终 WAR 包名称(部署到 Tomcat 后,访问路径为此名称) -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
示例5:Gradle 项目 WAR 打包配置(build.gradle)
groovy 复制代码
// build.gradle(Groovy 语法)
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'war' // 1. 应用 war 插件(支持 WAR 打包)
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
name = 'spring-boot-war-demo'
description = 'Spring Boot War 打包示例项目'

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // 2. 排除内嵌 Tomcat 依赖,并将其设置为 provided 范围
    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'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

// 3. WAR 打包配置
war {
    archiveName = 'spring-boot-war-demo.war' // 最终 WAR 包名称
    manifest {
        attributes 'Main-Class': 'com.example.SpringBootWarDemoApplication'
    }
}

// 保留 bootJar 任务(兼容 JAR 打包)
bootJar {
    enabled = false // 禁用默认的 JAR 打包(若仅需 WAR 打包,可开启此配置)
}

3.4 常见兼容性问题

在 Spring Boot 3.x 项目打包为 WAR 并部署到外部容器时,容易出现以下兼容性问题,需重点规避:

  1. JDK 版本不兼容

    • Spring Boot 3.x 要求 JDK 17+,而外部 Tomcat 容器需支持 JDK 17+(推荐 Tomcat 10.1.x 版本)。
    • 注意:Tomcat 9.x 仅支持 JDK 8-11,部署 Spring Boot 3.x WAR 包会出现类加载异常,需升级 Tomcat 版本。
  2. 内嵌容器依赖未排除干净

    • 若未排除 spring-boot-starter-tomcat 依赖,或其他依赖间接引入了内嵌容器,会导致与外部容器的类冲突(如 javax.servlet.ServletException 类重复)。
    • 解决方案:使用 mvn dependency:tree 查看依赖树,排查并排除所有内嵌容器相关依赖。
  3. Servlet 规范版本不兼容

    • Spring Boot 3.x 基于 Servlet 6.0 规范,而 Tomcat 9.x 仅支持 Servlet 4.0 规范,Tomcat 10.0.x 支持 Servlet 5.0 规范,Tomcat 10.1.x 才支持 Servlet 6.0 规范。
    • 解决方案:升级 Tomcat 到 10.1.x 版本,或降低 Spring Boot 版本到 2.x(基于 Servlet 4.0/5.0 规范)。
  4. WAR 包部署后访问路径异常

    • 外部容器部署 WAR 包后,默认访问路径为 http://<容器地址>:<容器端口>/<WAR 包名称>/,若项目中硬编码了根路径(如 /api/user),可能导致访问 404。
    • 解决方案:使用 @RequestMapping("${server.servlet.context-path}/api/user") 配置动态路径,或在容器中配置虚拟主机,将 WAR 包映射为根路径。
  5. 外部容器的端口、上下文路径覆盖问题

    • Spring Boot 项目中 application.yml 配置的 server.portserver.servlet.context-path 会被外部容器的配置覆盖,失效。
    • 解决方案:在外部容器中配置端口和上下文路径(如 Tomcat 的 server.xml),或通过容器的环境变量传递配置。

3.5 War 打包部署流程图(Mermaid 格式)





编写 Spring Boot 业务代码
修改主启动类:继承 SpringBootServletInitializer 并覆盖 configure 方法
配置 pom.xml/build.gradle(修改打包类型为 war,排除内嵌容器依赖)
执行打包命令:mvn clean package / gradle clean war
生成 WAR 包(target/build/libs 目录下)
准备外部 Servlet 容器(如 Tomcat 10.1.x,兼容 JDK 17+ 和 Servlet 6.0)
将 WAR 包复制到外部容器的 webapps 目录下
启动外部 Servlet 容器(如 Tomcat 的 startup.sh/startup.bat)
容器启动是否成功?
WAR 包是否正常解压并启动?
项目正常运行,通过容器地址+WAR 名称访问服务
查看容器日志(如 Tomcat 的 logs/catalina.out),排查依赖/规范冲突
排查容器配置问题(如端口占用、JDK 版本),返回 F 调整

四、核心对比分析

Jar 与 War 两种打包方式各有优劣,适用于不同的场景,下面从启动速度、资源占用、部署灵活性等多个维度进行核心对比,帮助开发者做出合理选型。

4.1 启动速度、资源占用、部署灵活性

对比维度 Jar 打包方式(内嵌容器) War 打包方式(外部容器)
启动速度 极快。直接通过 java -jar 启动,无需容器初始化额外流程,Spring Boot 启动完成即提供服务。 较慢。需先启动外部容器(如 Tomcat),容器初始化完成后,再解压、加载 WAR 包,初始化 Spring 上下文。
资源占用 相对较高。每个 JAR 包都内嵌了完整的 Servlet 容器,多个 JAR 包运行会重复占用容器资源(如内存、线程池)。 相对较低。多个 WAR 包共享一个外部容器的资源,容器的内存、线程池等可统一配置和复用,资源利用率更高。
部署灵活性 极高。无需依赖外部容器,可直接在任何安装了 JDK 的环境中运行,支持快速部署、单机多实例(指定不同端口)。 较低。依赖外部容器的配置和环境,部署前需先搭建并配置好容器,多实例部署需配置容器集群,流程复杂。
端口配置 灵活。可通过命令行、配置文件指定端口,每个 JAR 包可独立占用端口,无冲突。 固定。所有 WAR 包共享外部容器的端口,通过上下文路径区分,端口修改需调整容器配置,影响所有部署的应用。
版本迭代 便捷。直接停止旧进程,上传新 JAR 包启动即可,无容器重启成本,支持无缝升级(蓝绿部署、滚动升级)。 繁琐。需停止外部容器(或单个应用),替换 WAR 包,重启容器(或重载应用),可能影响其他部署在同一容器的应用。

4.2 安全性与运维管理差异

对比维度 Jar 打包方式(内嵌容器) War 打包方式(外部容器)
安全性 较高。应用与容器隔离,每个 JAR 包独立运行,单个应用的安全漏洞不会影响其他应用;内嵌容器可通过 Spring Boot 配置快速关闭无用功能(如默认错误页面)。 中等。多个 WAR 包共享一个外部容器,容器本身的安全漏洞可能影响所有应用;需统一配置容器的安全策略(如权限控制、SSL),配置复杂,易出现疏漏。
日志管理 灵活。可通过 Spring Boot 配置日志框架(Logback、Log4j2),指定日志输出路径、切割策略;支持每个应用独立配置日志,便于排查问题。 繁琐。默认日志输出到容器的日志目录,多个 WAR 包的日志混合在一起,需额外配置每个应用的日志隔离,或使用容器的日志分割功能,配置复杂。
监控与运维 便捷。支持 Spring Boot Actuator 实现应用的健康检查、指标监控、远程运维,每个应用的监控数据独立,便于精准排查问题;可通过 systemd 等工具实现开机自启、进程守护。 复杂。需依赖容器的监控工具(如 Tomcat Manager),或额外搭建分布式监控系统(如 Prometheus + Grafana),监控数据以容器为单位,难以精准定位单个 WAR 包的问题。
资源限制 灵活。可通过 JVM 参数(-Xms-Xmx)为每个 JAR 包独立配置内存、CPU 资源,避免单个应用占用过多资源影响其他应用。 繁琐。资源限制针对整个外部容器,所有 WAR 包共享容器的资源配置,难以对单个应用进行精细化的资源控制。

4.3 微服务架构下的推荐选择

微服务架构的核心特点是服务拆分、独立部署、弹性扩容、快速迭代,结合 Jar 与 War 两种打包方式的优劣,在微服务架构中:

  1. 优先推荐 Jar 打包方式

    • 符合微服务"独立部署"的核心要求,每个微服务打包为独立的 JAR 包,可在任何环境中快速部署,无需依赖外部容器。
    • 支持快速迭代和弹性扩容,通过容器化(Docker)+ 编排工具(K8s),可实现微服务的秒级部署、滚动升级和自动扩容,这是 War 打包方式无法比拟的。
    • 便于微服务的监控和运维,每个 JAR 包独立提供监控指标,可精准定位单个微服务的问题,降低运维成本。
    • 内嵌容器可灵活选择(如 Tomcat、Undertow),Undertow 相比 Tomcat 具有更高的性能和更低的资源占用,更适合微服务高并发场景。
  2. War 打包方式的适用场景(微服务架构中的例外)

    • 微服务中存在需要与传统遗留系统集成的服务,且遗留系统部署在外部容器集群中,为了统一运维,需将该微服务打包为 WAR 包部署。
    • 企业对微服务的部署环境有严格的合规要求,禁止使用内嵌容器,必须使用标准化的外部容器集群。
    • 单个微服务需要部署多个实例,且企业已有成熟的外部容器集群,无需重新搭建容器化环境,可选择 WAR 打包方式复用现有资源。

五、实战部署流程

5.1 Jar 方式部署到 Linux 服务器(含 systemd 服务配置)

本实战以 CentOS 7.x 服务器、Spring Boot 3.x JAR 包为例,完整演示从打包到开机自启的部署流程。

步骤1:本地打包并上传 JAR 包
  1. 本地执行 Maven 打包命令,生成可执行 JAR 包:
bash 复制代码
mvn clean package -Dmaven.test.skip=true
  1. 找到 target 目录下的 spring-boot-jar-demo.jar,通过 scp 命令上传到 Linux 服务器:
bash 复制代码
# 上传到服务器的 /opt/app 目录(若目录不存在,先执行 mkdir -p /opt/app)
scp target/spring-boot-jar-demo.jar root@<服务器IP>:/opt/app/
步骤2:创建日志目录(避免启动时日志文件无法创建)
bash 复制代码
ssh root@<服务器IP>
mkdir -p /opt/logs/demo
步骤3:编写 systemd 服务配置文件(实现开机自启、进程守护)

systemd 是 CentOS 7.x 及以上版本的系统服务管理器,能够实现服务的开机自启、异常重启、状态监控等功能,相比 nohup 更稳定可靠。

示例6:systemd 服务配置文件(/usr/lib/systemd/system/demo.service

ini 复制代码
[Unit]
# 服务描述
Description=Spring Boot Jar Demo Service
# 依赖网络服务启动
After=network.target

[Service]
# 运行用户(推荐使用非 root 用户,此处以 root 为例,生产环境可创建专用用户)
User=root
# 工作目录(JAR 包所在目录)
WorkingDirectory=/opt/app
# 启动命令(指定 JVM 参数、配置文件)
ExecStart=/usr/local/jdk17/bin/java -Xms512m -Xmx1024m -jar spring-boot-jar-demo.jar --spring.profiles.active=prod
# 停止命令(优雅停止)
ExecStop=/bin/kill -15 $MAINPID
# 异常退出时自动重启
Restart=on-failure
# 重启间隔时间(秒)
RestartSec=5
# 日志输出重定向(可选,也可在 ExecStart 中指定)
StandardOutput=append:/opt/logs/demo/demo.out
StandardError=append:/opt/logs/demo/demo.err

[Install]
# 开机自启的运行级别
WantedBy=multi-user.target
步骤4:加载并启动 systemd 服务
bash 复制代码
# 重新加载 systemd 配置(修改配置文件后必须执行)
systemctl daemon-reload

# 启动 demo 服务
systemctl start demo.service

# 设置开机自启
systemctl enable demo.service

# 查看服务状态
systemctl status demo.service

# 停止服务(如需)
# systemctl stop demo.service

# 重启服务(如需)
# systemctl restart demo.service
步骤5:验证部署结果
  1. 查看服务状态,若显示 active (running) 则表示启动成功:
bash 复制代码
systemctl status demo.service
  1. 访问服务接口,验证是否正常提供服务:
bash 复制代码
curl http://<服务器IP>:8080/api/hello
  1. 查看日志,排查是否有异常:
bash 复制代码
tail -f /opt/logs/demo/demo.out

5.2 War 方式部署到 Tomcat 示例

本实战以 Tomcat 10.1.16(支持 Servlet 6.0、JDK 17+)、Spring Boot 3.x WAR 包为例,完整演示部署流程。

步骤1:本地打包并上传 WAR 包
  1. 本地执行 Maven 打包命令,生成 WAR 包:
bash 复制代码
mvn clean package -Dmaven.test.skip=true
  1. 找到 target 目录下的 spring-boot-war-demo.war,通过 scp 命令上传到 Linux 服务器的 Tomcat webapps 目录:
bash 复制代码
# 假设 Tomcat 安装目录为 /opt/tomcat-10.1.16
scp target/spring-boot-war-demo.war root@<服务器IP>:/opt/tomcat-10.1.16/webapps/
步骤2:准备 Tomcat 环境(兼容 Spring Boot 3.x)
  1. 安装 JDK 17+ 并配置 JAVA_HOME 环境变量:
bash 复制代码
# 编辑环境变量配置文件
vi /etc/profile

# 添加以下内容(根据 JDK 实际安装路径调整)
export JAVA_HOME=/usr/local/jdk17
export PATH=$JAVA_HOME/bin:$PATH

# 生效环境变量
source /etc/profile

# 验证 JDK 版本
java -version
  1. 下载并解压 Tomcat 10.1.16:
bash 复制代码
# 下载 Tomcat 10.1.16
wget https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.16/bin/apache-tomcat-10.1.16.tar.gz

# 解压到 /opt 目录
tar -zxvf apache-tomcat-10.1.16.tar.gz -C /opt/

# 重命名(便于操作)
mv /opt/apache-tomcat-10.1.16 /opt/tomcat-10.1.16
  1. 赋予 Tomcat 脚本执行权限:
bash 复制代码
chmod +x /opt/tomcat-10.1.16/bin/*.sh
步骤3:配置 Tomcat(可选,修改端口、上下文路径)
  1. 修改 Tomcat 端口(默认 8080,若被占用可修改):
bash 复制代码
# 编辑 server.xml 配置文件
vi /opt/tomcat-10.1.16/conf/server.xml

# 找到以下配置,修改 port 为 8081(按需调整)
<Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           redirectPort="8443"/>
  1. 配置 WAR 包根路径映射(无需输入 WAR 包名称访问):
bash 复制代码
# 编辑 context.xml 配置文件
vi /opt/tomcat-10.1.16/conf/context.xml

# 在 <Context> 标签内添加以下内容(docBase 为 WAR 包所在路径,path 为根路径)
<Context docBase="/opt/tomcat-10.1.16/webapps/spring-boot-war-demo.war" path="" reloadable="true"/>
步骤4:启动 Tomcat 并验证部署结果
  1. 启动 Tomcat:
bash 复制代码
# 进入 Tomcat bin 目录
cd /opt/tomcat-10.1.16/bin/

# 启动 Tomcat(前台运行,便于查看启动日志)
./catalina.sh run

# 后台启动(生产环境推荐)
# ./startup.sh
  1. 验证部署结果:
    • 若未修改上下文路径,访问地址为 http://<服务器IP>:8080/spring-boot-war-demo/api/hello
    • 若已配置根路径映射,访问地址为 http://<服务器IP>:8081/api/hello
  2. 查看 Tomcat 日志,排查异常:
bash 复制代码
tail -f /opt/tomcat-10.1.16/logs/catalina.out
  1. 停止 Tomcat(如需):
bash 复制代码
./shutdown.sh

六、故障排查与最佳实践

6.1 常见故障排查

6.1.1 Jar 包部署故障排查
  1. JAR 包无法启动,提示"no main manifest attribute"

    • 原因:未正确配置 spring-boot-maven-plugin 插件,或未执行 repackage 目标,生成的是普通 JAR 包而非可执行 JAR 包。
    • 解决方案:检查 pom.xmlspring-boot-maven-plugin 插件配置,确保包含 repackage 目标,重新执行 mvn clean package 打包。
  2. 启动后端口被占用,提示"Address already in use"

    • 原因:配置的端口已被其他进程占用。
    • 解决方案:使用 netstat -tulpn | grep <端口号> 查找占用进程,终止该进程,或通过 --server.port=<新端口> 指定新端口启动。
  3. 启动后访问 404,日志无异常

    • 原因:项目打包时未包含业务代码(如 src/main/java 目录未编译),或配置文件中的上下文路径配置错误。
    • 解决方案:解压 JAR 包查看 BOOT-INF/classes 目录是否包含编译后的 class 文件,检查 application.yml 中的 server.servlet.context-path 配置,重新打包部署。
  4. systemd 服务启动失败,提示"Failed to start demo.service: Unit not found"

    • 原因:服务配置文件路径错误,或未执行 systemctl daemon-reload 加载配置。
    • 解决方案:检查服务配置文件是否在 /usr/lib/systemd/system/ 目录下,文件名是否为 demo.service,执行 systemctl daemon-reload 后重新启动服务。
6.1.2 War 包部署故障排查
  1. Tomcat 启动后,WAR 包未解压,无访问日志

    • 原因:WAR 包损坏,或 Tomcat 对 WAR 包无读取权限,或 WAR 包不符合 Servlet 规范。
    • 解决方案:重新上传 WAR 包,执行 chmod 755 /opt/tomcat-10.1.16/webapps/spring-boot-war-demo.war 赋予权限,解压 WAR 包检查目录结构是否完整。
  2. Tomcat 启动时报"ClassNotFoundException: org.springframework.boot.web.servlet.support.SpringBootServletInitializer"

    • 原因:主启动类未继承 SpringBootServletInitializer,或 WAR 包中未包含 Spring Boot 核心依赖。
    • 解决方案:修改主启动类,继承 SpringBootServletInitializer 并覆盖 configure 方法,重新打包部署。
  3. 访问时报"javax.servlet.ServletException: Context initialization failed"

    • 原因:内嵌容器依赖未排除干净,与外部 Tomcat 类冲突,或 Servlet 规范版本不兼容。
    • 解决方案:使用 mvn dependency:tree 排查依赖树,排除所有内嵌容器依赖,升级 Tomcat 到 10.1.x 版本(兼容 Spring Boot 3.x)。

6.2 最佳实践

  1. 打包前规范检查

    • 执行 mvn clean 清理旧的编译产物,避免残留文件影响打包结果。
    • 跳过测试用例(-Dmaven.test.skip=true),确保测试用例失败不会阻塞打包流程(生产环境需保证测试用例通过)。
    • 打包后解压验证目录结构,确保核心文件(class、依赖、配置)完整。
  2. 配置文件外部化

    • 无论 Jar 还是 War 打包,都应将生产环境的配置文件(如数据库连接、密钥、端口)外部化(放置在服务器的 /opt/config 目录),避免硬编码在项目中,便于运维修改和版本管理。
    • 示例:java -jar spring-boot-jar-demo.jar --spring.config.location=/opt/config/application.yml
  3. 日志规范化配置

    • 使用 Logback/Log4j2 配置日志切割(按大小、按时间),避免日志文件过大占用磁盘空间。
    • 日志输出包含时间、线程 ID、类名、日志级别,便于问题排查。
    • Jar 包部署时,将日志输出到独立的日志目录(如 /opt/logs);War 包部署时,配置日志隔离,避免与其他应用日志混合。
  4. 资源限制与监控

    • Jar 包部署时,通过 JVM 参数配置合理的堆内存、元空间大小,避免内存溢出(如 -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m)。
    • 启用 Spring Boot Actuator,暴露健康检查、指标监控接口(如 /actuator/health/actuator/metrics),结合 Prometheus + Grafana 实现可视化监控。
    • War 包部署时,合理配置外部容器的线程池、连接超时时间,避免单个应用占用过多容器资源。
  5. 版本管理与回滚

    • 为 Jar/WAR 包添加版本号(如 spring-boot-jar-demo-1.0.0.jar),便于区分不同版本,实现快速回滚。
    • 部署新版本前,备份旧版本包和配置文件,若新版本出现问题,可快速替换回滚。
  6. 容器化部署(推荐)

    • 无论 Jar 还是 War 打包,都推荐使用 Docker 容器化部署,将应用与运行环境打包为镜像,实现"一次构建,到处运行"。
    • 结合 Kubernetes 实现应用的自动扩容、滚动升级、故障自愈,进一步提升部署的稳定性和灵活性。

七、结论与选型建议

7.1 结论

Spring Boot 的 Jar 与 War 两种打包方式,本质上是内嵌容器与外部容器的部署模式差异,各自具有明确的优势和适用场景:

  • Jar 打包方式凭借其"可执行、免容器、易部署"的特性,成为 Spring Boot 项目的默认选择,尤其适合微服务架构、快速迭代、云原生部署的场景,能够极大地简化部署流程,降低运维成本。
  • War 打包方式则延续了传统 Java Web 项目的部署模式,适合需要复用现有外部容器集群、多应用统一管理、兼容遗留系统的传统企业级场景,但其部署流程相对繁琐,灵活性较低。

Spring Boot 3.x 作为最新版本,对 Jar 打包的支持更加完善,内嵌容器的性能和稳定性进一步提升,同时也保留了对 War 打包的兼容,满足不同场景的部署需求。

7.2 选型建议

  1. 优先选择 Jar 打包的场景

    • 微服务架构中的单个微服务部署。
    • 云原生环境(如阿里云、腾讯云、K8s)部署。
    • 快速迭代、频繁发布的项目(如互联网产品、创业公司项目)。
    • 单机多实例部署,需要独立端口、独立资源的场景。
    • 不需要依赖外部容器高级功能,追求部署便捷性的场景。
  2. 选择 War 打包的场景

    • 企业已有成熟的 Tomcat/JBoss 容器集群,需要复用现有环境和运维体系。
    • 项目需要与传统遗留 Web 应用部署在同一容器中,实现集成和统一管理。
    • 行业合规要求,禁止使用内嵌容器,必须使用标准化外部容器。
    • 需要利用外部容器的高级功能(如集群会话同步、虚拟主机配置、SSL 统一配置)。
  3. 兼容两种打包方式的建议

    • 保留主启动类的 main() 方法,同时继承 SpringBootServletInitializer 并覆盖 configure 方法,实现 Jar 与 War 打包的兼容。
    • pom.xml/build.gradle 中通过配置开关,快速切换打包类型,无需大幅修改代码和依赖配置。

7.3 FAQ(常见问题解答)

  1. 能否将 Jar 转为 War?

    • 可以。无需重新编写业务代码,只需完成三步配置:① 修改打包类型为 war;② 主启动类继承 SpringBootServletInitializer 并覆盖 configure 方法;③ 排除内嵌容器依赖,添加 provided 范围的 Servlet API 依赖,重新打包即可。
  2. 如何在不修改代码的情况下切换打包方式?

    • 核心是通过构建工具的配置实现,无需修改业务代码。Maven 项目可通过 profiles 配置打包类型和依赖,Gradle 项目可通过任务开关配置,例如在 pom.xml 中配置两个 profiles(jar-profilewar-profile),分别对应 Jar 和 War 打包的配置,打包时通过 mvn clean package -P war-profile 切换。
  3. Jar 包能否部署到外部 Servlet 容器中运行?

    • 不可以。Spring Boot 生成的可执行 Jar 包是特殊格式的"胖 JAR",其目录结构不符合 Servlet 规范,外部容器无法识别和加载,只能通过 java -jar 命令启动。若需部署到外部容器,必须打包为 War 格式。
  4. War 包能否通过 java -jar 命令直接启动?

    • 可以(需保留内嵌容器依赖)。若 War 包未排除内嵌容器依赖,且主启动类包含 main() 方法,可通过 java -jar spring-boot-war-demo.war 命令直接启动,此时 War 包的作用与 Jar 包一致,内嵌容器会启动并提供服务。
  5. Spring Boot 3.x 打包的 War 包能否部署到 Tomcat 9.x 中?

    • 不推荐。Spring Boot 3.x 基于 Servlet 6.0 规范,而 Tomcat 9.x 仅支持 Servlet 4.0 规范,存在 Servlet 规范版本不兼容问题,会导致类加载异常、应用无法启动。解决方案:升级 Tomcat 到 10.1.x 版本,或降低 Spring Boot 版本到 2.x(基于 Servlet 4.0/5.0 规范)。
  6. Jar 包部署时,如何实现配置文件的热更新?

    • Spring Boot 支持配置文件的热更新,无需重启应用。① 引入 spring-boot-devtools 依赖(开发环境);② 生产环境可通过 spring-cloud-confignacos 等配置中心实现配置热更新;③ 也可通过 java -jar 命令指定外部配置文件,修改外部配置文件后,通过 kill -1 <pid> 发送信号,触发应用重新加载配置(需配置 spring.cloud.config.refresh.enabled=true)。

✍️ 坚持用 清晰易懂的图解 + 可落地的代码,让每个知识点都 简单直观!

💡 座右铭 :"道路是曲折的,前途是光明的!"

相关推荐
柠檬071112 小时前
opencv 未知函数记录-detailEnhance
人工智能·opencv·计算机视觉
空山新雨后、12 小时前
ComfyUI、Stable Diffusion 与 ControlNet解读
人工智能
Hcoco_me12 小时前
大模型面试题42:从小白视角递进讲解大模型训练的重计算
人工智能·rnn·深度学习·lstm·transformer
enjoy编程12 小时前
Spring boot 4 探究netty的关键知识点
spring boot·设计模式·reactor·netty·多线程
喜欢吃豆12 小时前
代理式 CI/CD 的崛起:Claude Code Action 深度技术分析报告
人工智能·ci/cd·架构·大模型
2301_7644413312 小时前
基于HVNS算法和分类装载策略的仓储系统仿真平台
人工智能·算法·分类
aitoolhub12 小时前
在线设计技术实践:稿定设计核心架构与能力拆解
图像处理·人工智能·计算机视觉·自然语言处理·架构·视觉传达
shayudiandian12 小时前
AI生成内容(AIGC)在游戏与影视行业的落地案例
人工智能·游戏·aigc
木头左12 小时前
深度学习驱动的指数期权定价与波动率建模技术实现
人工智能·深度学习
AI科技星12 小时前
统一场论变化的引力场产生电磁场推导与物理诠释
服务器·人工智能·科技·线性代数·算法·重构·生活