重要提示: Spring Boot 默认推荐使用 可执行 JAR (Executable JAR) 包。它内置了 Tomcat/Jetty/Undertow 等 Web 服务器,实现了“应用即服务”的理念,部署极其简单。WAR 包主要用于需要部署到传统外部 Servlet 容器(如 Tomcat, WebLogic)的场景,现在已不常见。
一、核心概念
1. 可执行 JAR (Executable JAR / Fat JAR / Uber JAR)
- 定义: 一个独立的、包含应用所有代码、依赖库(JARs)、资源文件以及一个嵌入式 Web 服务器(如 Tomcat)的 JAR 文件。
- 特点:
- 自包含 (Self-Contained): 所有运行所需的一切都在一个文件里。
- 内置服务器 (Embedded Server): 不需要外部安装的 Web 服务器。应用自己启动一个 Web 服务器。
- 易于部署:
java -jar your-app.jar
即可启动,非常适合容器化(Docker)和云原生环境。 - 启动快: 避免了外部容器的启动开销。
- 版本控制: 应用和其依赖的库版本都打包在一起,避免了“在我机器上能运行”的问题。
- 文件结构 (使用
jar -tf your-app.jar
查看):META-INF/ META-INF/MANIFEST.MF org/ org/springframework/ ... (Spring Boot 核心类) BOOT-INF/ BOOT-INF/classes/ (你的应用类和资源) BOOT-INF/classes/application.yml BOOT-INF/lib/ (所有依赖的 JAR 文件) BOOT-INF/lib/spring-boot-starter-web-2.7.14.jar BOOT-INF/lib/tomcat-embed-core-9.0.65.jar ...
2. WAR (Web Application Archive)
- 定义: 一种传统的 Java Web 应用打包格式,需要部署到外部的 Servlet 容器(如 Apache Tomcat, Jetty, WebLogic, WebSphere)中运行。
- 特点:
- 依赖外部容器: 应用本身不包含 Web 服务器,需要外部容器提供。
- 共享容器资源: 多个 WAR 应用可以部署在同一个容器实例上,共享其资源(内存、线程池等)。
- 部署复杂: 需要先安装和配置外部容器,然后将 WAR 文件放到容器的
webapps
目录或通过管理界面部署。 - 启动慢: 需要先启动外部容器,再加载应用。
- 遗留系统集成: 主要用于需要与旧有系统集成或必须使用特定外部容器的场景。
- 文件结构 (标准 WAR):
META-INF/ META-INF/MANIFEST.MF WEB-INF/ WEB-INF/classes/ (你的应用类和资源) WEB-INF/classes/application.yml WEB-INF/lib/ (应用的依赖库,但通常排除容器提供的库) WEB-INF/lib/your-app-dependency.jar WEB-INF/web.xml (可选,Spring Boot 通常不需要) index.html (静态资源) ...
二、操作步骤(非常详细)
步骤 1:创建 Spring Boot 项目
- 使用 Spring Initializr:
- 访问 https://start.spring.io/。
- 选择:
- Project:
Maven Project
或Gradle Project
- Language:
Java
- Spring Boot: 选择一个稳定版本 (如
2.7.14
,3.1.0
) - Project Metadata:
- Group:
com.example
- Artifact:
demo
(这将决定最终 JAR/WAR 的名字) - Name:
demo
- Description:
Demo project for Spring Boot
- Package name:
com.example.demo
- Group:
- Packaging: 默认是
Jar
。如果要打 WAR 包,这里可以先选Jar
,后续在pom.xml
中修改。 - Java Version: 选择你的 JDK 版本 (如 8, 11, 17)。
- Project:
- Dependencies: 添加
Spring Web
(用于创建 Web 应用)。 - 点击
Generate
下载项目压缩包,解压到你的工作目录。
步骤 2:生成可执行 JAR 包 (推荐)
方式 A:使用 Maven
- 检查
pom.xml
:- 确保
<packaging>
是jar
(默认)。 - 确保有
spring-boot-maven-plugin
:<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- 通常不需要配置,使用默认即可 --> </plugin> </plugins> </build>
- 确保
- 构建 JAR:
- 打开命令行,进入项目根目录 (
pom.xml
所在目录)。 - 执行命令:
mvn clean package # 或 ./mvnw clean package # (如果使用 mvnw 脚本)
- 打开命令行,进入项目根目录 (
- 查找 JAR 文件:
- 构建成功后,会在
target/
目录下生成 JAR 文件。 - 文件名通常是
{artifactId}-{version}.jar
(如demo-0.0.1-SNAPSHOT.jar
)。 - 还会生成一个
original-{artifactId}-{version}.jar
,这是不包含依赖的原始 JAR。
- 构建成功后,会在
- 运行 JAR:
- 在命令行执行:
java -jar target/demo-0.0.1-SNAPSHOT.jar
- 你应该能看到 Spring Boot 启动日志,内嵌的 Tomcat 也启动了。
- 在命令行执行:
方式 B:使用 Gradle
- 检查
build.gradle
:- 确保
plugins
块中有id 'org.springframework.boot' version '...'
和id 'java'
。 - 确保
jar
任务被启用 (通常是默认的)。 - 确保
bootJar
任务存在 (由 Spring Boot 插件提供):plugins { id 'org.springframework.boot' version '2.7.14' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id 'java' } // 确保有这个任务,它会生成可执行 JAR tasks.named('bootJar') { // 可以在这里配置,但通常不需要 }
- 确保
- 构建 JAR:
- 打开命令行,进入项目根目录 (
build.gradle
所在目录)。 - 执行命令:
./gradlew clean build # 或 gradle clean build
build
任务会执行bootJar
。
- 打开命令行,进入项目根目录 (
- 查找 JAR 文件:
- 构建成功后,会在
build/libs/
目录下生成 JAR 文件。 - 文件名通常是
{artifactId}-{version}.jar
(如demo-0.0.1-SNAPSHOT.jar
)。 - 还会生成一个
demo-0.0.1-SNAPSHOT-original.jar
,是不包含依赖的原始 JAR。
- 构建成功后,会在
- 运行 JAR:
- 在命令行执行:
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
- 在命令行执行:
步骤 3:生成 WAR 包 (用于外部容器)
注意: 除非有特殊要求,否则不推荐。JAR 包是首选。
修改打包方式 (
pom.xml
或build.gradle
):- Maven (
pom.xml
):- 将
<packaging>jar</packaging>
修改为<packaging>war</packaging>
。 - 排除嵌入式 Tomcat: 因为外部容器会提供 Servlet API,需要将
spring-boot-starter-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> <!-- 添加 Servlet API 依赖,范围为 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> <!-- 如果是 Spring Boot 3.x, 使用 jakarta.servlet --> <!-- <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <scope>provided</scope> </dependency> --> </dependencies>
- 将
- Gradle (
build.gradle
):- 应用
war
插件:plugins { id 'org.springframework.boot' version '2.7.14' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id 'java' id 'war' // 添加 war 插件 }
- 排除嵌入式 Tomcat:
dependencies { implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' } providedRuntime 'javax.servlet:javax.servlet-api:4.0.1' // 对于 Boot 3.x, 使用 jakarta.servlet-api }
- 应用
- Maven (
创建 WAR 部署入口类 (仅 Maven 需要,Gradle 通常不需要):
- 在
src/main/java/com/example/demo/
目录下创建一个类,继承SpringBootServletInitializer
并重写configure
方法。 ServletInitializer.java
:package com.example.demo; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class); // 指向你的主应用类 } }
- 说明: 这个类告诉外部 Servlet 容器如何启动 Spring Boot 应用。Gradle 的
war
插件通常会自动处理。
- 在
构建 WAR:
- Maven:
mvn clean package # 在 target/ 目录下生成 demo-0.0.1-SNAPSHOT.war
- Gradle:
./gradlew clean build # 在 build/libs/ 目录下生成 demo-0.0.1-SNAPSHOT.war
- Maven:
部署到外部 Tomcat:
- 下载并安装 Apache Tomcat (如 9.x)。
- 启动 Tomcat (
bin/startup.sh
或bin/startup.bat
)。 - 将生成的
demo-0.0.1-SNAPSHOT.war
文件复制到 Tomcat 的webapps/
目录下。 - Tomcat 会自动解压 WAR 文件并部署应用(或通过 Tomcat Manager Web 界面部署)。
- 访问
http://localhost:8080/demo-0.0.1-SNAPSHOT/
(URL 路径通常与 WAR 文件名一致,除非在application.yml
中配置了server.servlet.context-path
)。
三、常见错误
java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
:- 原因: 构建的 JAR 是普通的 JAR,不是可执行 JAR。
MANIFEST.MF
文件中缺少Main-Class
和Start-Class
。 - 解决: 确保使用了
spring-boot-maven-plugin
(Maven) 或bootJar
任务 (Gradle),并且插件配置正确。
- 原因: 构建的 JAR 是普通的 JAR,不是可执行 JAR。
Port already in use: 8080
:- 原因: 端口被占用。
- 解决: 修改
application.yml
中的server.port
,或杀死占用端口的进程。
Error: Could not find or load main class com.example.demo.DemoApplication
:- 原因: JAR 文件损坏,或
MANIFEST.MF
中的Main-Class
指向了错误的类。 - 解决: 重新构建 JAR,检查
MANIFEST.MF
内容。
- 原因: JAR 文件损坏,或
WAR 包部署后 404:
- 原因 1:
ServletInitializer
类缺失或配置错误 (Maven 项目)。 - 解决: 检查
ServletInitializer.java
是否存在且正确继承和重写。 - 原因 2:
spring-boot-starter-tomcat
未正确排除,导致与外部容器的 Servlet API 冲突。 - 解决: 检查
pom.xml
或build.gradle
的依赖排除。 - 原因 3: WAR 文件未正确复制到
webapps
目录,或 Tomcat 未自动部署。 - 解决: 检查文件路径,查看 Tomcat 日志。
- 原因 1:
java.lang.ClassNotFoundException: javax.servlet.ServletContextListener
:- 原因: 缺少
javax.servlet-api
依赖,且范围不是provided
。 - 解决: 确保在
pom.xml
或build.gradle
中添加了javax.servlet:javax.servlet-api
依赖,并且scope
是provided
(Maven) 或providedRuntime
(Gradle)。
- 原因: 缺少
Failed to load ApplicationContext
:- 原因: 配置文件 (
application.yml
) 错误,或 Bean 注入失败。 - 解决: 检查配置文件语法,检查
@ComponentScan
路径,检查依赖注入的 Bean 是否存在。
- 原因: 配置文件 (
四、注意事项
- JAR 是首选: 除非有明确要求必须使用外部容器,否则始终选择可执行 JAR。
MANIFEST.MF
: 可执行 JAR 的META-INF/MANIFEST.MF
文件至关重要,它包含Main-Class: org.springframework.boot.loader.JarLauncher
和Start-Class: com.yourpackage.YourApplication
。Spring Boot 插件会自动生成。- 依赖范围 (
scope
): 打 WAR 包时,spring-boot-starter-tomcat
必须被排除,javax.servlet-api
(或jakarta.servlet-api
) 的范围必须是provided
。 ServletInitializer
: Maven 项目打 WAR 包时通常需要这个类。Gradle 项目通常不需要。- 上下文路径 (
context-path
): 在application.yml
中可以通过server.servlet.context-path=/myapp
设置应用的访问路径。部署到外部容器时,WAR 文件名也会影响路径。 - 外部配置: 可以通过
--spring.config.location=...
或SPRING_CONFIG_LOCATION
环境变量在运行时覆盖 JAR 内的配置。 - JDK 版本: 确保运行 JAR/WAR 的 JDK 版本与编译时的版本兼容。
五、使用技巧
- 查看 JAR 内容:
jar -tf your-app.jar
(列出所有文件)jar -xf your-app.jar META-INF/MANIFEST.MF
(解压特定文件)
- 传递 JVM 参数:
java -Xms512m -Xmx1024m -jar your-app.jar --server.port=9090
- 传递 Spring Boot 参数:
java -jar your-app.jar --spring.profiles.active=prod --logging.level.root=DEBUG
- 后台运行:
- Linux:
nohup java -jar your-app.jar > app.log 2>&1 &
- 使用
screen
或tmux
。
- Linux:
- 使用启动脚本: 编写 shell 脚本 (
start.sh
,stop.sh
) 来管理应用的启动、停止和状态检查。 - Docker 化: 将 JAR 包放入 Docker 镜像是最常见的部署方式。
FROM openjdk:11-jre-slim COPY target/demo-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app.jar"]
- 分离依赖库 (可选): 对于超大应用,可以配置
spring-boot-maven-plugin
将依赖库复制到lib/
目录,减少主 JAR 大小,但会增加部署复杂性。<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> <!-- 或 MODULES --> <addStartScripts>false</addStartScripts> </configuration> </plugin>
六、最佳实践
- 默认使用可执行 JAR: 这是 Spring Boot 的设计哲学和最佳实践。
- 版本化构建: 在
pom.xml
/build.gradle
中使用明确的版本号,便于追踪和回滚。 - 自动化构建: 使用 CI/CD 工具 (如 Jenkins, GitLab CI, GitHub Actions) 自动执行
mvn clean package
或./gradlew build
。 - Docker 优先: 将构建好的 JAR 包制作成 Docker 镜像,实现环境一致性。
- 健康检查: 确保应用暴露
/actuator/health
端点,便于监控。 - 日志配置: 在
application.yml
中配置日志级别和输出位置 (logging.file.name=app.log
)。 - 配置外部化: 将敏感配置(密码、密钥)通过环境变量或配置中心管理,不要硬编码在
application.yml
中。 - 资源优化: 对于 WAR 包,确保
provided
依赖正确,避免打包不必要的库。
七、性能优化
- JAR 包大小优化:
- 分析依赖: 使用
mvn dependency:tree
或./gradlew dependencies
查看依赖树,移除未使用的依赖。 - 使用精简依赖: 选择功能更专注的 Starter,避免引入庞大的依赖。
- 排除传递依赖: 如果某个依赖的传递依赖你不需要,可以排除它。
- 使用
spring-boot-thin-layout
: 一个实验性插件,可以生成更小的 JAR,依赖在运行时下载。
- 分析依赖: 使用
- 启动速度优化:
- 减少自动配置: 使用
@EnableAutoConfiguration(exclude = {...})
或spring.autoconfigure.exclude
排除不需要的自动配置类。 - 延迟初始化 (Lazy Initialization): 在
application.yml
中设置spring.main.lazy-initialization=true
,让 Bean 在首次使用时才创建。 - 使用 GraalVM Native Image: 将 Spring Boot 应用编译成本地可执行文件,启动速度极快(毫秒级),内存占用小。但构建复杂,兼容性需验证。
- 减少自动配置: 使用
- 运行时性能:
- JVM 调优: 根据应用负载合理设置
-Xms
,-Xmx
, 选择合适的 GC 算法 (-XX:+UseG1GC
)。 - 监控: 使用
spring-boot-actuator
+Micrometer
监控应用性能指标(CPU, 内存, HTTP 延迟, 数据库连接等)。
- JVM 调优: 根据应用负载合理设置
- 部署优化:
- 蓝绿部署/金丝雀发布: 使用 JAR 包 + Docker + Kubernetes 可以轻松实现滚动更新,减少停机时间。
- 缓存: 利用 Spring Cache 抽象缓存频繁访问的数据。
总结
- 核心概念: Spring Boot 可执行 JAR 是自包含、内置服务器的独立应用包;WAR 是依赖外部 Servlet 容器的传统 Web 包。
- 操作步骤: JAR 包只需
mvn clean package
或./gradlew build
,然后java -jar
。WAR 包需修改pom.xml
/build.gradle
(改packaging
为war
,排除tomcat starter
,添加servlet-api provided
),Maven 项目通常需ServletInitializer
,然后构建并部署到外部容器。 - 常见错误: JAR 找不到主类、端口占用、WAR 部署 404(缺少
ServletInitializer
或依赖冲突)。 - 注意事项: JAR 是绝对首选;注意
MANIFEST.MF
、依赖范围 (provided
)、ServletInitializer
。 - 使用技巧:
jar -tf
查看内容,传递 JVM/Spring 参数,后台运行,Docker 化。 - 最佳实践: 默认用 JAR,CI/CD 自动化,Docker 优先,外部化配置。
- 性能优化: 优化 JAR 大小(分析依赖),加快启动(延迟初始化、Native Image),JVM 调优,监控。