
✨哈喽!我是 我不是呆头呀!
📝 专注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 服务配置))
-
- [步骤1:本地打包并上传 JAR 包](#步骤1:本地打包并上传 JAR 包)
- 步骤2:创建日志目录(避免启动时日志文件无法创建)
- [步骤3:编写 systemd 服务配置文件(实现开机自启、进程守护)](#步骤3:编写 systemd 服务配置文件(实现开机自启、进程守护))
- [步骤4:加载并启动 systemd 服务](#步骤4:加载并启动 systemd 服务)
- 步骤5:验证部署结果
- [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 官方提供的专用插件,其核心功能包括:
- 收集项目编译后的 class 文件、资源文件,以及所有第三方依赖包(如 Spring Core、MyBatis 等);
- 将内嵌 Servlet 容器(默认 Tomcat)的核心包一同打包进入 JAR;
- 生成特殊的目录结构和清单文件(
MANIFEST.MF),指定主启动类和类加载器配置; - 提供
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)及启动逻辑
核心结构说明:
MANIFEST.MF清单文件 :核心配置项包括Main-Class(Spring Boot 启动器类org.springframework.boot.loader.JarLauncher)和Start-Class(项目自身的主启动类,如com.example.demo.DemoApplication)。BOOT-INF/classes:对应普通 Maven 项目的target/classes目录,存放项目自身的业务代码和配置文件。BOOT-INF/lib:存放项目所有的第三方依赖,解决了传统 JAR 无法包含依赖的问题。org/springframework/boot/loader:Spring Boot 自定义的类加载器核心,负责加载BOOT-INF目录下的类和依赖包,这是可执行 JAR 能够正常启动的关键。
启动原理:
- 当执行
java -jar xxx.jar命令时,JVM 会读取MANIFEST.MF文件中的Main-Class,即JarLauncher,并启动该类。 JarLauncher初始化 Spring Boot 自定义的LaunchedURLClassLoader类加载器,该类加载器能够识别BOOT-INF/classes和BOOT-INF/lib目录的结构,实现对项目类和依赖类的加载。JarLauncher读取MANIFEST.MF文件中的Start-Class,即项目的主启动类(如DemoApplication)。- 调用主启动类的
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 常规启动命令
- 基础启动命令(前台运行,关闭终端则进程终止):
bash
java -jar spring-boot-jar-demo.jar
- 指定端口启动(覆盖
application.yml中的端口配置):
bash
java -jar spring-boot-jar-demo.jar --server.port=8081
- 指定配置文件启动(加载自定义配置文件):
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
- 后台运行(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 调试命令(开发/排障场景)
- 远程调试启动(允许 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 连接后再继续运行。
- 查看 JVM 参数启动(打印 JVM 运行参数,排查内存溢出等问题):
bash
java -Xms512m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar spring-boot-jar-demo.jar
- 配置说明:
-Xms512m初始堆内存,-Xmx1024m最大堆内存。
2.4.3 停止命令
- 查找进程并终止(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 打包的核心场景主要包括以下几点:
- 企业现有容器集群复用:多数传统企业已搭建成熟的 Tomcat/JBoss 集群,具备完善的运维、监控、扩容体系,将 Spring Boot 项目打包为 WAR 部署,无需重新搭建新的运行环境,降低运维成本和学习成本。
- 多应用统一管理:外部 Servlet 容器支持在单个实例上部署多个 WAR 应用,实现资源(内存、端口)的共享与隔离,便于统一管理和版本迭代。
- 利用外部容器的高级功能:外部 Tomcat/JBoss 提供了虚拟主机配置、SSL 证书统一配置、集群会话同步、访问日志切割等高级功能,这些功能在内嵌容器中配置复杂,甚至无法实现。
- 合规与审计要求:部分行业(如金融、政务)对应用部署环境有严格的合规要求,要求使用标准化的外部容器,禁止内嵌容器的独占运行模式。
- 遗留系统集成: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 并部署到外部容器时,容易出现以下兼容性问题,需重点规避:
-
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 版本。
-
内嵌容器依赖未排除干净:
- 若未排除
spring-boot-starter-tomcat依赖,或其他依赖间接引入了内嵌容器,会导致与外部容器的类冲突(如javax.servlet.ServletException类重复)。 - 解决方案:使用
mvn dependency:tree查看依赖树,排查并排除所有内嵌容器相关依赖。
- 若未排除
-
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 规范)。
-
WAR 包部署后访问路径异常:
- 外部容器部署 WAR 包后,默认访问路径为
http://<容器地址>:<容器端口>/<WAR 包名称>/,若项目中硬编码了根路径(如/api/user),可能导致访问 404。 - 解决方案:使用
@RequestMapping("${server.servlet.context-path}/api/user")配置动态路径,或在容器中配置虚拟主机,将 WAR 包映射为根路径。
- 外部容器部署 WAR 包后,默认访问路径为
-
外部容器的端口、上下文路径覆盖问题:
- Spring Boot 项目中
application.yml配置的server.port、server.servlet.context-path会被外部容器的配置覆盖,失效。 - 解决方案:在外部容器中配置端口和上下文路径(如 Tomcat 的
server.xml),或通过容器的环境变量传递配置。
- Spring Boot 项目中
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 两种打包方式的优劣,在微服务架构中:
-
优先推荐 Jar 打包方式:
- 符合微服务"独立部署"的核心要求,每个微服务打包为独立的 JAR 包,可在任何环境中快速部署,无需依赖外部容器。
- 支持快速迭代和弹性扩容,通过容器化(Docker)+ 编排工具(K8s),可实现微服务的秒级部署、滚动升级和自动扩容,这是 War 打包方式无法比拟的。
- 便于微服务的监控和运维,每个 JAR 包独立提供监控指标,可精准定位单个微服务的问题,降低运维成本。
- 内嵌容器可灵活选择(如 Tomcat、Undertow),Undertow 相比 Tomcat 具有更高的性能和更低的资源占用,更适合微服务高并发场景。
-
War 打包方式的适用场景(微服务架构中的例外):
- 微服务中存在需要与传统遗留系统集成的服务,且遗留系统部署在外部容器集群中,为了统一运维,需将该微服务打包为 WAR 包部署。
- 企业对微服务的部署环境有严格的合规要求,禁止使用内嵌容器,必须使用标准化的外部容器集群。
- 单个微服务需要部署多个实例,且企业已有成熟的外部容器集群,无需重新搭建容器化环境,可选择 WAR 打包方式复用现有资源。
五、实战部署流程
5.1 Jar 方式部署到 Linux 服务器(含 systemd 服务配置)
本实战以 CentOS 7.x 服务器、Spring Boot 3.x JAR 包为例,完整演示从打包到开机自启的部署流程。
步骤1:本地打包并上传 JAR 包
- 本地执行 Maven 打包命令,生成可执行 JAR 包:
bash
mvn clean package -Dmaven.test.skip=true
- 找到
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:验证部署结果
- 查看服务状态,若显示
active (running)则表示启动成功:
bash
systemctl status demo.service
- 访问服务接口,验证是否正常提供服务:
bash
curl http://<服务器IP>:8080/api/hello
- 查看日志,排查是否有异常:
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 包
- 本地执行 Maven 打包命令,生成 WAR 包:
bash
mvn clean package -Dmaven.test.skip=true
- 找到
target目录下的spring-boot-war-demo.war,通过scp命令上传到 Linux 服务器的 Tomcatwebapps目录:
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)
- 安装 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
- 下载并解压 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
- 赋予 Tomcat 脚本执行权限:
bash
chmod +x /opt/tomcat-10.1.16/bin/*.sh
步骤3:配置 Tomcat(可选,修改端口、上下文路径)
- 修改 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"/>
- 配置 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 并验证部署结果
- 启动 Tomcat:
bash
# 进入 Tomcat bin 目录
cd /opt/tomcat-10.1.16/bin/
# 启动 Tomcat(前台运行,便于查看启动日志)
./catalina.sh run
# 后台启动(生产环境推荐)
# ./startup.sh
- 验证部署结果:
- 若未修改上下文路径,访问地址为
http://<服务器IP>:8080/spring-boot-war-demo/api/hello - 若已配置根路径映射,访问地址为
http://<服务器IP>:8081/api/hello
- 若未修改上下文路径,访问地址为
- 查看 Tomcat 日志,排查异常:
bash
tail -f /opt/tomcat-10.1.16/logs/catalina.out
- 停止 Tomcat(如需):
bash
./shutdown.sh
六、故障排查与最佳实践
6.1 常见故障排查
6.1.1 Jar 包部署故障排查
-
JAR 包无法启动,提示"no main manifest attribute":
- 原因:未正确配置
spring-boot-maven-plugin插件,或未执行repackage目标,生成的是普通 JAR 包而非可执行 JAR 包。 - 解决方案:检查
pom.xml中spring-boot-maven-plugin插件配置,确保包含repackage目标,重新执行mvn clean package打包。
- 原因:未正确配置
-
启动后端口被占用,提示"Address already in use":
- 原因:配置的端口已被其他进程占用。
- 解决方案:使用
netstat -tulpn | grep <端口号>查找占用进程,终止该进程,或通过--server.port=<新端口>指定新端口启动。
-
启动后访问 404,日志无异常:
- 原因:项目打包时未包含业务代码(如
src/main/java目录未编译),或配置文件中的上下文路径配置错误。 - 解决方案:解压 JAR 包查看
BOOT-INF/classes目录是否包含编译后的 class 文件,检查application.yml中的server.servlet.context-path配置,重新打包部署。
- 原因:项目打包时未包含业务代码(如
-
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 包部署故障排查
-
Tomcat 启动后,WAR 包未解压,无访问日志:
- 原因:WAR 包损坏,或 Tomcat 对 WAR 包无读取权限,或 WAR 包不符合 Servlet 规范。
- 解决方案:重新上传 WAR 包,执行
chmod 755 /opt/tomcat-10.1.16/webapps/spring-boot-war-demo.war赋予权限,解压 WAR 包检查目录结构是否完整。
-
Tomcat 启动时报"ClassNotFoundException: org.springframework.boot.web.servlet.support.SpringBootServletInitializer":
- 原因:主启动类未继承
SpringBootServletInitializer,或 WAR 包中未包含 Spring Boot 核心依赖。 - 解决方案:修改主启动类,继承
SpringBootServletInitializer并覆盖configure方法,重新打包部署。
- 原因:主启动类未继承
-
访问时报"javax.servlet.ServletException: Context initialization failed":
- 原因:内嵌容器依赖未排除干净,与外部 Tomcat 类冲突,或 Servlet 规范版本不兼容。
- 解决方案:使用
mvn dependency:tree排查依赖树,排除所有内嵌容器依赖,升级 Tomcat 到 10.1.x 版本(兼容 Spring Boot 3.x)。
6.2 最佳实践
-
打包前规范检查:
- 执行
mvn clean清理旧的编译产物,避免残留文件影响打包结果。 - 跳过测试用例(
-Dmaven.test.skip=true),确保测试用例失败不会阻塞打包流程(生产环境需保证测试用例通过)。 - 打包后解压验证目录结构,确保核心文件(class、依赖、配置)完整。
- 执行
-
配置文件外部化:
- 无论 Jar 还是 War 打包,都应将生产环境的配置文件(如数据库连接、密钥、端口)外部化(放置在服务器的
/opt/config目录),避免硬编码在项目中,便于运维修改和版本管理。 - 示例:
java -jar spring-boot-jar-demo.jar --spring.config.location=/opt/config/application.yml。
- 无论 Jar 还是 War 打包,都应将生产环境的配置文件(如数据库连接、密钥、端口)外部化(放置在服务器的
-
日志规范化配置:
- 使用 Logback/Log4j2 配置日志切割(按大小、按时间),避免日志文件过大占用磁盘空间。
- 日志输出包含时间、线程 ID、类名、日志级别,便于问题排查。
- Jar 包部署时,将日志输出到独立的日志目录(如
/opt/logs);War 包部署时,配置日志隔离,避免与其他应用日志混合。
-
资源限制与监控:
- Jar 包部署时,通过 JVM 参数配置合理的堆内存、元空间大小,避免内存溢出(如
-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m)。 - 启用 Spring Boot Actuator,暴露健康检查、指标监控接口(如
/actuator/health、/actuator/metrics),结合 Prometheus + Grafana 实现可视化监控。 - War 包部署时,合理配置外部容器的线程池、连接超时时间,避免单个应用占用过多容器资源。
- Jar 包部署时,通过 JVM 参数配置合理的堆内存、元空间大小,避免内存溢出(如
-
版本管理与回滚:
- 为 Jar/WAR 包添加版本号(如
spring-boot-jar-demo-1.0.0.jar),便于区分不同版本,实现快速回滚。 - 部署新版本前,备份旧版本包和配置文件,若新版本出现问题,可快速替换回滚。
- 为 Jar/WAR 包添加版本号(如
-
容器化部署(推荐):
- 无论 Jar 还是 War 打包,都推荐使用 Docker 容器化部署,将应用与运行环境打包为镜像,实现"一次构建,到处运行"。
- 结合 Kubernetes 实现应用的自动扩容、滚动升级、故障自愈,进一步提升部署的稳定性和灵活性。
七、结论与选型建议
7.1 结论
Spring Boot 的 Jar 与 War 两种打包方式,本质上是内嵌容器与外部容器的部署模式差异,各自具有明确的优势和适用场景:
- Jar 打包方式凭借其"可执行、免容器、易部署"的特性,成为 Spring Boot 项目的默认选择,尤其适合微服务架构、快速迭代、云原生部署的场景,能够极大地简化部署流程,降低运维成本。
- War 打包方式则延续了传统 Java Web 项目的部署模式,适合需要复用现有外部容器集群、多应用统一管理、兼容遗留系统的传统企业级场景,但其部署流程相对繁琐,灵活性较低。
Spring Boot 3.x 作为最新版本,对 Jar 打包的支持更加完善,内嵌容器的性能和稳定性进一步提升,同时也保留了对 War 打包的兼容,满足不同场景的部署需求。
7.2 选型建议
-
优先选择 Jar 打包的场景:
- 微服务架构中的单个微服务部署。
- 云原生环境(如阿里云、腾讯云、K8s)部署。
- 快速迭代、频繁发布的项目(如互联网产品、创业公司项目)。
- 单机多实例部署,需要独立端口、独立资源的场景。
- 不需要依赖外部容器高级功能,追求部署便捷性的场景。
-
选择 War 打包的场景:
- 企业已有成熟的 Tomcat/JBoss 容器集群,需要复用现有环境和运维体系。
- 项目需要与传统遗留 Web 应用部署在同一容器中,实现集成和统一管理。
- 行业合规要求,禁止使用内嵌容器,必须使用标准化外部容器。
- 需要利用外部容器的高级功能(如集群会话同步、虚拟主机配置、SSL 统一配置)。
-
兼容两种打包方式的建议:
- 保留主启动类的
main()方法,同时继承SpringBootServletInitializer并覆盖configure方法,实现 Jar 与 War 打包的兼容。 - 在
pom.xml/build.gradle中通过配置开关,快速切换打包类型,无需大幅修改代码和依赖配置。
- 保留主启动类的
7.3 FAQ(常见问题解答)
-
能否将 Jar 转为 War?
- 可以。无需重新编写业务代码,只需完成三步配置:① 修改打包类型为
war;② 主启动类继承SpringBootServletInitializer并覆盖configure方法;③ 排除内嵌容器依赖,添加provided范围的 Servlet API 依赖,重新打包即可。
- 可以。无需重新编写业务代码,只需完成三步配置:① 修改打包类型为
-
如何在不修改代码的情况下切换打包方式?
- 核心是通过构建工具的配置实现,无需修改业务代码。Maven 项目可通过 profiles 配置打包类型和依赖,Gradle 项目可通过任务开关配置,例如在
pom.xml中配置两个 profiles(jar-profile、war-profile),分别对应 Jar 和 War 打包的配置,打包时通过mvn clean package -P war-profile切换。
- 核心是通过构建工具的配置实现,无需修改业务代码。Maven 项目可通过 profiles 配置打包类型和依赖,Gradle 项目可通过任务开关配置,例如在
-
Jar 包能否部署到外部 Servlet 容器中运行?
- 不可以。Spring Boot 生成的可执行 Jar 包是特殊格式的"胖 JAR",其目录结构不符合 Servlet 规范,外部容器无法识别和加载,只能通过
java -jar命令启动。若需部署到外部容器,必须打包为 War 格式。
- 不可以。Spring Boot 生成的可执行 Jar 包是特殊格式的"胖 JAR",其目录结构不符合 Servlet 规范,外部容器无法识别和加载,只能通过
-
War 包能否通过
java -jar命令直接启动?- 可以(需保留内嵌容器依赖)。若 War 包未排除内嵌容器依赖,且主启动类包含
main()方法,可通过java -jar spring-boot-war-demo.war命令直接启动,此时 War 包的作用与 Jar 包一致,内嵌容器会启动并提供服务。
- 可以(需保留内嵌容器依赖)。若 War 包未排除内嵌容器依赖,且主启动类包含
-
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 规范)。
-
Jar 包部署时,如何实现配置文件的热更新?
- Spring Boot 支持配置文件的热更新,无需重启应用。① 引入
spring-boot-devtools依赖(开发环境);② 生产环境可通过spring-cloud-config或nacos等配置中心实现配置热更新;③ 也可通过java -jar命令指定外部配置文件,修改外部配置文件后,通过kill -1 <pid>发送信号,触发应用重新加载配置(需配置spring.cloud.config.refresh.enabled=true)。
- Spring Boot 支持配置文件的热更新,无需重启应用。① 引入
✍️ 坚持用 清晰易懂的图解 + 可落地的代码,让每个知识点都 简单直观!
💡 座右铭 :"道路是曲折的,前途是光明的!"
