Spring Boot 项目打包部署详解
Spring Boot 项目最终要运行起来,必须经过打包和部署两个环节。打包方式决定了部署方式,选错了会导致应用无法启动。
一、两种打包方式:Jar 与 War
Spring Boot 支持两种打包方式:Jar 和 War。
| 打包方式 | 运行方式 | Web 容器来源 | 适用场景 |
|---|---|---|---|
| Jar | java -jar app.jar |
内嵌 Tomcat/Jetty/Undertow | 微服务、云原生、前后端分离 |
| War | 部署到外置 Tomcat 的 webapps 目录 | 外置 Tomcat 或其他容器 | 传统企业应用、必须使用外置容器的场景 |
Spring Boot 官方推荐 Jar 包方式。内嵌容器带来的好处是:本地开发和线上运行环境完全一致,不存在"本地跑得起来,部署到服务器就挂了"的问题。
如何切换打包方式?
在 pom.xml 中设置 <packaging> 标签:
xml
<!-- Jar 包(默认) -->
<packaging>jar</packaging>
<!-- War 包 -->
<packaging>war</packaging>
二、Jar 包部署(默认方式)
这是 Spring Boot 最推荐的部署方式。应用和容器打包在一起,直接通过 java -jar 运行。
第一步:配置 spring-boot-maven-plugin
spring-boot-starter-parent 已经帮你配置好了这个插件,通常不需要额外配置。但如果你想生成带有完整依赖信息的清单文件,可以显式添加:
xml
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.MyApplication</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
spring-boot-maven-plugin 的核心功能有两个:一是 repackage 目标,把普通 Jar 重打成可执行的 Fat Jar;二是 run 目标,直接在开发阶段快速启动应用。通过 mvn package 或 mvn spring-boot:repackage 触发重打包。
第二步:执行打包命令
bash
mvn clean package
执行后,target/ 目录下会生成两个 Jar 文件:
myapp.jar:可执行的 Fat Jar,包含所有依赖myapp.jar.original:Maven 生成的原始 Jar,不包含依赖
第三步:运行
bash
java -jar target/myapp.jar
常用运行参数:
bash
# 指定端口
java -jar myapp.jar --server.port=8081
# 指定配置文件
java -jar myapp.jar --spring.config.location=/path/to/application.yml
# 指定环境
java -jar myapp.jar --spring.profiles.active=prod
# 设置 JVM 参数
java -Xmx512m -Xms256m -jar myapp.jar
后台运行:
Linux / Mac:
bash
nohup java -jar myapp.jar > logs/out.log 2>&1 &
Windows(PowerShell):
powershell
Start-Process -NoNewWindow java -ArgumentList "-jar myapp.jar"
或者注册为 Windows 服务。
三、War 包部署(传统方式)
某些企业环境要求必须使用外置 Tomcat,这时候需要打 War 包。
第一步:修改打包方式
xml
<packaging>war</packaging>
第二步:排除内嵌 Tomcat
xml
<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>
<!-- 如果使用 Servlet API,需要显式添加 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
第三步:修改启动类
java
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
SpringBootServletInitializer 是 Spring Boot 提供的适配类,让 War 包能在外置容器中启动。它重写了 Spring 的 WebApplicationInitializer 接口,容器启动时会调用 onStartup 方法,再由 Spring Boot 初始化 ApplicationContext。如果不继承这个类,外置容器不会加载 Spring Boot 的配置。
第四步:打包
bash
mvn clean package
生成 target/myapp.war。
第五步:部署
将 War 包复制到 Tomcat 的 webapps 目录,Tomcat 会自动解压并部署。
bash
cp target/myapp.war /opt/tomcat/webapps/
cd /opt/tomcat/bin
./startup.sh
访问路径:http://localhost:8080/myapp/xxx
四、多环境配置
不同环境需要不同的配置:开发用本地数据库,测试用测试库,生产用生产库。
Spring Boot 支持通过 Profile 区分环境:
方式一:多个配置文件
application.yml # 公共配置
application-dev.yml # 开发环境
application-test.yml # 测试环境
application-prod.yml # 生产环境
方式二:单个 yml 多文档块
yaml
spring:
profiles:
active: dev
---
spring:
config:
activate:
on-profile: dev
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
server:
port: 80
启动时指定环境:
bash
java -jar myapp.jar --spring.profiles.active=prod
五、配置文件外部化
生产环境通常不希望把数据库密码等敏感信息写在打包好的 Jar 包里,而是通过外部配置文件覆盖。
Spring Boot 的配置优先级从高到低:
- 命令行参数
- 操作系统环境变量
- Jar 包外的
application.yml(config/目录下优先级更高) - Jar 包内的
application.yml
指定外部配置文件:
bash
java -jar myapp.jar --spring.config.location=/opt/config/application-prod.yml
通过环境变量设置:
bash
export SPRING_PROFILES_ACTIVE=prod
java -jar myapp.jar
六、打包原理:Fat Jar 的结构
Spring Boot 的可执行 Jar 之所以能通过 java -jar 直接运行,是因为它改变了 Jar 包的结构。普通的 Jar 包只包含当前项目的 class 文件和资源文件,不包含依赖包,Java 的默认类加载器无法从嵌套的 Jar 中加载类。
Spring Boot 的 spring-boot-maven-plugin 在 repackage 阶段,把 Maven 解析出来的所有依赖 Jar 包解压后重新打包,同时生成一个 META-INF/MANIFEST.MF 文件,其中的 Main-Class 指向 JarLauncher,Start-Class 指向应用的 main 方法所在的类。
JarLauncher 是 Spring Boot 自定义的类加载入口,它读取 BOOT-INF/lib/ 目录下的所有依赖 Jar,通过自定义的 LaunchedURLClassLoader 加载这些类,然后通过反射调用 Start-Class 中的 main 方法。这个设计让应用能够以单个文件的形式分发,同时保持了类加载的完整性。
War 包的结构与 Jar 包类似,区别在于它遵循 Java EE 的 War 规范,可以被外置容器识别,启动入口也改为了 SpringBootServletInitializer。
七、常见问题与解决方案
问题 1:打包后运行报 no main manifest attribute
原因:没有使用 spring-boot-maven-plugin 重新打包,或者该插件未配置。
解决:在 pom.xml 中添加 spring-boot-maven-plugin 并执行 mvn spring-boot:repackage。需要保证 mainClass 配置正确。
问题 2:Jar 包太大
原因:所有依赖都打包进去了。
解决:
- 使用
spring-boot-maven-plugin的excludes排除不需要的依赖 - 使用
spring-boot-thin-launcher或layers分层打包
问题 3:线上启动时端口被占用
解决:在启动命令中指定 --server.port=8081,或者通过环境变量 SERVER_PORT=8081 设置。
问题 4:配置文件不生效
检查配置文件的加载顺序和覆盖关系。命令行参数 > 外部配置文件 > 内部配置文件。
问题 5:War 包部署后返回 404
原因:启动类没有继承 SpringBootServletInitializer,或者应用上下文路径不正确。
解决:继承 SpringBootServletInitializer,并重写 configure 方法。部署后访问路径需要加上上下文路径,默认是 War 包文件名。
八、总结
| 部署方式 | 命令 | 适用场景 |
|---|---|---|
| Jar 包开发环境 | java -jar myapp.jar |
日常开发测试 |
| Jar 包后台运行 | nohup java -jar myapp.jar & |
生产环境(微服务) |
| Jar 包指定环境 | java -jar myapp.jar --spring.profiles.active=prod |
环境隔离 |
| War 包部署 | 拷贝到 Tomcat webapps 目录 |
传统企业应用、必须使用外置容器的场景 |