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

7 阅读22分钟

目录

一、引言

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)及启动逻辑

AI写代码
1234567891011
核心结构说明:
  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/classes 和 BOOT-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 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>

AI写代码xml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
示例2:Gradle 项目配置(build.gradle / build.gradle.kts)
// 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 依赖(生产环境无需)
}

AI写代码groovy
12345678910111213141516171819202122232425262728293031323334

2.4 启动与调试命令

2.4.1 常规启动命令
  1. 基础启动命令(前台运行,关闭终端则进程终止):
java -jar spring-boot-jar-demo.jar

AI写代码bash
1
  1. 指定端口启动(覆盖 application.yml 中的端口配置):
java -jar spring-boot-jar-demo.jar --server.port=8081

AI写代码bash
1
  1. 指定配置文件启动(加载自定义配置文件):
# 加载 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

AI写代码bash
12345
  1. 后台运行(Linux/Mac 环境,输出日志到指定文件):
# nohup 忽略挂断信号,& 后台运行
nohup java -jar spring-boot-jar-demo.jar --spring.profiles.active=prod > /opt/logs/demo.log 2>&1 &

AI写代码bash
12
2.4.2 调试命令(开发/排障场景)
  1. 远程调试启动(允许 IDE 连接调试,端口 5005):
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar spring-boot-jar-demo.jar

AI写代码bash
1
  • 配置说明:suspend=n 表示项目启动后不阻塞,直接运行;若设置为 suspend=y,则项目启动后阻塞,等待 IDE 连接后再继续运行。
  1. 查看 JVM 参数启动(打印 JVM 运行参数,排查内存溢出等问题):
java -Xms512m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar spring-boot-jar-demo.jar

AI写代码bash
1
  • 配置说明:-Xms512m 初始堆内存,-Xmx1024m 最大堆内存。
2.4.3 停止命令
  1. 查找进程并终止(Linux 环境):
# 查找 JAR 包对应的进程 ID
ps -ef | grep spring-boot-jar-demo.jar | grep -v grep | awk '{print $2}'

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

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

AI写代码bash
12345678

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)
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);
    }
}

AI写代码java
运行
1234567891011121314151617181920212223242526

原理说明:当 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 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>

AI写代码xml
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
示例5:Gradle 项目 WAR 打包配置(build.gradle)
// 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 打包,可开启此配置)
}

AI写代码groovy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

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 包:
mvn clean package -Dmaven.test.skip=true

AI写代码bash
1
  1. 找到 target 目录下的 spring-boot-jar-demo.jar,通过 scp 命令上传到 Linux 服务器:
# 上传到服务器的 /opt/app 目录(若目录不存在,先执行 mkdir -p /opt/app)
scp target/spring-boot-jar-demo.jar root@<服务器IP>:/opt/app/

AI写代码bash
12
步骤2:创建日志目录(避免启动时日志文件无法创建)
ssh root@<服务器IP>
mkdir -p /opt/logs/demo

AI写代码bash
12
步骤3:编写 systemd 服务配置文件(实现开机自启、进程守护)

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

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

[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

AI写代码ini
1234567891011121314151617181920212223242526
步骤4:加载并启动 systemd 服务
# 重新加载 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

AI写代码bash
1234567891011121314151617
步骤5:验证部署结果
  1. 查看服务状态,若显示 active (running) 则表示启动成功:
systemctl status demo.service

AI写代码bash
1
  1. 访问服务接口,验证是否正常提供服务:
curl http://<服务器IP>:8080/api/hello

AI写代码bash
1
  1. 查看日志,排查是否有异常:
tail -f /opt/logs/demo/demo.out

AI写代码bash
1

5.2 War 方式部署到 Tomcat 示例

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

步骤1:本地打包并上传 WAR 包
  1. 本地执行 Maven 打包命令,生成 WAR 包:
mvn clean package -Dmaven.test.skip=true

AI写代码bash
1
  1. 找到 target 目录下的 spring-boot-war-demo.war,通过 scp 命令上传到 Linux 服务器的 Tomcat webapps 目录:
# 假设 Tomcat 安装目录为 /opt/tomcat-10.1.16
scp target/spring-boot-war-demo.war root@<服务器IP>:/opt/tomcat-10.1.16/webapps/

AI写代码bash
12
步骤2:准备 Tomcat 环境(兼容 Spring Boot 3.x)
  1. 安装 JDK 17+ 并配置 JAVA_HOME 环境变量:
# 编辑环境变量配置文件
vi /etc/profile

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

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

# 验证 JDK 版本
java -version

AI写代码bash
123456789101112
  1. 下载并解压 Tomcat 10.1.16:
# 下载 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

AI写代码bash
12345678
  1. 赋予 Tomcat 脚本执行权限:
chmod +x /opt/tomcat-10.1.16/bin/*.sh

AI写代码bash
1
步骤3:配置 Tomcat(可选,修改端口、上下文路径)
  1. 修改 Tomcat 端口(默认 8080,若被占用可修改):
# 编辑 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"/>

AI写代码bash
1234567
  1. 配置 WAR 包根路径映射(无需输入 WAR 包名称访问):
# 编辑 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"/>

AI写代码bash
12345
步骤4:启动 Tomcat 并验证部署结果
  1. 启动 Tomcat:
# 进入 Tomcat bin 目录
cd /opt/tomcat-10.1.16/bin/

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

# 后台启动(生产环境推荐)
# ./startup.sh

AI写代码bash
12345678
  1. 验证部署结果:

    • 若未修改上下文路径,访问地址为 http://<服务器IP>:8080/spring-boot-war-demo/api/hello
    • 若已配置根路径映射,访问地址为 http://<服务器IP>:8081/api/hello
  2. 查看 Tomcat 日志,排查异常:

tail -f /opt/tomcat-10.1.16/logs/catalina.out

AI写代码bash
1
  1. 停止 Tomcat(如需):
./shutdown.sh

AI写代码bash
1

六、故障排查与最佳实践

6.1 常见故障排查

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

    • 原因:未正确配置 spring-boot-maven-plugin 插件,或未执行 repackage 目标,生成的是普通 JAR 包而非可执行 JAR 包。
    • 解决方案:检查 pom.xml 中 spring-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-config 或 nacos 等配置中心实现配置热更新;③ 也可通过 java -jar 命令指定外部配置文件,修改外部配置文件后,通过 kill -1 <pid> 发送信号,触发应用重新加载配置(需配置 spring.cloud.config.refresh.enabled=true)。