1. 项目概述:从代码到服务的最后一公里
做SpringBoot开发的朋友,肯定都经历过本地调试一切顺利,但一到部署上线就状况百出的阶段。这感觉就像精心组装了一辆赛车,在自家后院跑得飞快,但真要上赛道了,却发现连怎么把它运过去都是个问题。今天要聊的,就是这“最后一公里”——如何把一个SpringBoot项目,通过Maven打包成Jar包,并让它稳定地在Windows和Linux两个主流服务器平台上跑起来。
这个过程看似基础,却是项目从开发环境走向生产环境的必经之路,也是区分“会写代码”和“能交付服务”的关键一步。很多新手,甚至一些有经验的开发者,都可能在这里踩坑:为什么本地能跑,服务器上就报错?为什么用java -jar启动后,关掉终端服务就停了?Linux上怎么安装合适版本的Java?这些问题不解决,再好的代码也只能躺在你的电脑里。接下来,我会结合自己多次部署的经验,把从打包、环境准备到运行、排错的完整链条拆开揉碎了讲清楚,目标是让你看完就能动手,一次部署成功。
2. 核心思路与工具选型:为什么是Jar包?
在开始动手之前,我们先要搞清楚几个核心选择背后的逻辑。这能帮你理解每一步操作的意义,而不是机械地复制命令。
2.1 打包格式之争:Jar vs War vs 容器镜像
SpringBoot项目主要有三种部署形态:传统的WAR包、可执行JAR包和Docker容器镜像。我们选择可执行JAR包,是基于以下几个现实的考量:
- 内置容器的便利性:SpringBoot最大的特点之一就是内置了Tomcat、Jetty或Undertow等Servlet容器。打成的可执行JAR包(即
fat jar或uber jar)包含了应用本身、依赖库以及内嵌的Web容器。这意味着你不需要在目标服务器上预先安装和配置一个独立的Tomcat。部署变得极其简单:有Java环境,就能运行。这对于需要快速部署、环境隔离要求不是极端苛刻的场景,是效率最高的选择。 - 环境一致性:WAR包部署到外部Tomcat时,其行为会受到外部Tomcat版本、配置的极大影响。而可执行JAR包将运行时环境(Web容器)也打包了进去,只要Java版本匹配,它在任何地方的行为都几乎一致,减少了“在我这儿是好的”这类问题。
- 简化运维:启动命令统一为
java -jar yourapp.jar,停止服务也只需找到进程ID进行kill。相比于管理Tomcat的bin/startup.sh、webapps目录、server.xml配置,运维复杂度直线下降。配合后面会讲的nohup或系统服务(如systemd),可以轻松实现后台运行和开机自启。 - 与云原生的过渡:虽然Docker+K8S是更现代的部署方式,但可执行JAR是迈向容器化的一个完美中间态。你可以先熟练使用JAR包部署,理解应用运行的所有依赖,然后再将整个JAR包和其运行命令封装进Dockerfile,平滑过渡到容器化部署。
注意:对于超大型、需要高度模块化部署,或者必须与旧有企业级应用服务器(如WebLogic、WebSphere)集成的项目,WAR包部署仍是必要选项。但绝大多数微服务和中小型项目,可执行JAR是首选。
2.2 构建工具的选择:Maven的核心角色
Maven在这里不只是一个“打包工具”,它更是一个项目生命周期和依赖管理工具。它的核心价值在于:
- 依赖管理:通过
pom.xml声明所有第三方库,Maven会自动从中央仓库或你配置的私服下载,并处理传递性依赖,确保打包时所有必需的jar文件都被收集。 - 标准化构建流程:
mvn clean package这个命令背后,Maven执行了清理、编译、运行测试、打包等一系列标准化的阶段(phase),保证了构建结果的可重复性。 - 插件生态:SpringBoot专门提供了
spring-boot-maven-plugin。这个插件是生成可执行JAR的关键。它负责:- 将应用代码、所有依赖库、资源文件等打包进一个JAR。
- 在JAR的
META-INF/MANIFEST.MF文件中,设置Main-Class为org.springframework.boot.loader.JarLauncher。这个特殊的启动器负责以正确的方式加载内嵌的依赖JAR包,这是SpringBoot JAR能独立运行的核心魔法。 - 提供
repackage目标,确保最终生成的是可执行JAR,而不是普通的库JAR。
所以,我们的部署流水线可以清晰地概括为:编写代码 -> Maven管理依赖并驱动构建 ->spring-boot-maven-plugin生成可执行JAR -> 上传至目标服务器 -> 配置Java环境并运行。
3. 实操全流程:从打包到运行
理论清晰了,我们进入实战环节。我会按照一个标准的操作顺序来讲解,并穿插每个步骤的注意事项。
3.1 阶段一:本地开发环境准备与打包
在按打包按钮之前,请先做好以下检查,这能避免80%的部署失败。
3.1.1 确认项目配置
打开你的pom.xml文件,确保以下几点:
- 打包类型:
<packaging>jar</packaging>。SpringBoot默认就是jar。 - 父项目或依赖:通常通过继承
spring-boot-starter-parent或引入spring-boot-dependencies来管理版本。<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.5</version> <!-- 请使用稳定版本 --> <relativePath/> </parent> - 关键插件:必须有
spring-boot-maven-plugin。<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> - 应用入口:确保你的主类(带有
@SpringBootApplication注解的类)位置正确,通常Maven和插件能自动找到。如果不放心,可以在插件中显式指定:<configuration> <mainClass>com.yourcompany.yourapp.Application</mainClass> </configuration>
3.1.2 执行Maven打包命令
打开终端(CMD或PowerShell)或直接在IDE(如IntelliJ IDEA)的Maven工具窗口中操作。
核心命令:
mvn clean package -DskipTestsclean:清理之前构建的target目录,确保全新构建。package:执行编译、测试、打包的生命周期阶段。-DskipTests:跳过单元测试。在确保本地测试通过后,生产打包时可以跳过以加快速度。但切记,完整的CI/CD流程中不应该跳过测试。
执行结果: 命令成功后,在项目的target目录下,你会找到两个主要的JAR文件:
your-app-name-0.0.1-SNAPSHOT.jar:这是普通的库JAR,不包含依赖,不能直接运行。your-app-name-0.0.1-SNAPSHOT.jar.original:这是spring-boot-maven-plugin重新打包前Maven生成的原始JAR。- 我们要用的是同目录下最大的那个JAR文件,它通常有几十MB甚至上百MB(因为包含了所有依赖)。这个才是可执行的
fat jar。
实操心得:
- 如果打包失败,首先看错误信息。常见问题有:依赖下载失败(网络或仓库配置)、编译错误、插件版本冲突。可以尝试先执行
mvn clean compile看编译能否通过。 - 在IDEA中,你可以右键点击
pom.xml->Maven->Reload project来刷新依赖,有时能解决一些诡异的依赖问题。
3.2 阶段二:目标服务器环境准备
打包好的JAR是“货物”,服务器环境就是“赛道”和“燃料”。燃料就是Java运行时环境(JRE)或开发工具包(JDK)。
3.2.1 Windows服务器安装Java
Windows环境相对简单,主要是选择安装包和配置环境变量。
- 下载JDK:访问Oracle官网或Adoptium等开源发行版网站,下载Windows平台的JDK安装程序(如
.msi格式)。建议选择LTS(长期支持)版本,如JDK 17或JDK 21,以获得更稳定的支持和安全更新。 - 安装:运行安装程序,记住安装路径,例如
C:\Program Files\Java\jdk-17。 - 配置环境变量(这是关键):
- 右键“此电脑” -> “属性” -> “高级系统设置” -> “环境变量”。
- 在“系统变量”部分,新建变量
JAVA_HOME,值为你的JDK安装路径,如C:\Program Files\Java\jdk-17。 - 编辑“系统变量”中的
Path,新建一条记录,值为%JAVA_HOME%\bin。
- 验证:打开新的命令提示符(CMD),输入
java -version和javac -version。如果能正确显示版本信息,说明配置成功。
注意:很多Windows服务器管理面板(如宝塔)也提供了一键安装Java环境的功能,可视化管理,更为方便。但理解手动配置的原理有助于排错。
3.2.2 Linux服务器安装Java
Linux服务器是SpringBoot应用更常见的生产环境。安装方式多样,推荐使用包管理器或直接下载归档文件。
方法一:使用包管理器安装(推荐,便于管理)
对于CentOS/RHEL/AlmaLinux/Rocky Linux系列:
# 1. 搜索可用的JDK包 (以OpenJDK 17为例) yum search openjdk-17 # CentOS 7/RHEL 7 # 或 dnf search openjdk-17 # CentOS 8+/RHEL 8+ # 2. 安装JDK (包含JRE) sudo yum install java-17-openjdk-devel # 安装开发包,包含javac等 # 或 sudo yum install java-17-openjdk # 仅安装运行时环境 # 3. 验证安装 java -version对于Ubuntu/Debian系列:
# 1. 更新包索引 sudo apt update # 2. 安装JDK sudo apt install openjdk-17-jdk # 3. 验证安装 java -version方法二:手动下载安装(适用于需要特定版本或离线环境)
- 从官网下载Linux版的
.tar.gz归档文件(如jdk-17_linux-x64_bin.tar.gz)。 - 通过FTP/SFTP工具(如WinSCP、FileZilla)或
scp命令将文件上传到服务器,例如传到/opt目录。 - 在服务器上执行解压和配置:
# 切换到安装目录 cd /opt # 解压 sudo tar -xzf jdk-17_linux-x64_bin.tar.gz # 创建软链接(可选,便于版本管理) sudo ln -s /opt/jdk-17 /opt/java # 配置环境变量,编辑 /etc/profile 文件 sudo vim /etc/profile - 在
/etc/profile文件末尾添加:export JAVA_HOME=/opt/jdk-17 # 或 /opt/java 如果你创建了软链接 export PATH=$JAVA_HOME/bin:$PATH - 使配置生效:
source /etc/profile # 验证 java -version
实操心得:
- 版本对齐:务必确保服务器上的Java主版本号(如17)与本地开发、编译用的版本一致。虽然高版本Java通常可以运行低版本编译的代码,但为了绝对稳定,建议保持一致。
- 选择JRE还是JDK:如果服务器只运行应用,安装JRE即可,更轻量。如果可能需要调试、编译或使用
jps、jstack等工具,则安装JDK。 yum/apt安装的Java,其安装路径可能比较分散(如/usr/lib/jvm/),JAVA_HOME需要指向具体的版本目录。可以通过which java和ls -l命令追踪到实际路径。
3.3 阶段三:部署与运行JAR包
环境就绪,JAR包在手,现在让它跑起来。
3.3.1 上传JAR包到服务器
- Windows:可以直接复制到服务器某个目录,如
D:\app。如果通过远程桌面,直接拖拽即可。 - Linux:使用
scp命令或SFTP工具。
建议在服务器上创建一个专门的目录来管理应用,例如# 从本地机器上传到服务器 scp target/your-app.jar user@your-server-ip:/home/user/app//opt/app或/home/username/app,并设置好适当的权限。
3.3.2 运行JAR包
基础运行命令: 无论是在Windows的CMD/PowerShell还是Linux的终端,基础命令都是:
java -jar your-app.jar这条命令会在前台运行应用,控制台会打印SpringBoot的启动日志。一旦关闭终端窗口或按下Ctrl+C,应用就会停止。这仅适用于测试。
后台运行与日志管理(Linux重点):
在生产环境,我们需要让应用在后台运行,并且将日志输出到文件。
使用
nohup和&(最常用):nohup java -jar your-app.jar > app.log 2>&1 &nohup:让命令忽略挂断信号(SIGHUP),即使终端关闭,进程也不会退出。> app.log:将标准输出重定向到app.log文件。2>&1:将标准错误也重定向到标准输出,即所有日志都写到app.log。&:让命令在后台运行。- 命令执行后会返回一个进程ID(PID),记下它或用
ps命令查找,用于后续管理。
使用
systemd管理服务(现代Linux发行版推荐): 这是更专业、更强大的方式,可以实现开机自启、自动重启、集中日志管理(journalctl)等。- 创建一个服务单元文件:
sudo vim /etc/systemd/system/your-app.service - 写入以下配置(根据实际情况修改):
[Unit] Description=Your SpringBoot Application After=network.target [Service] Type=simple User=appuser # 建议使用非root用户运行 WorkingDirectory=/opt/app ExecStart=/usr/bin/java -jar /opt/app/your-app.jar Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target - 启用并启动服务:
sudo systemctl daemon-reload sudo systemctl enable your-app.service sudo systemctl start your-app.service # 查看状态和日志 sudo systemctl status your-app.service sudo journalctl -u your-app.service -f
- 创建一个服务单元文件:
Windows后台运行: 在Windows上,除了直接双击(不推荐),可以:
- 编写一个批处理文件(
.bat),内容为start javaw -jar your-app.jar。javaw是无控制台窗口的Java启动器。 - 使用
nssm(Non-Sucking Service Manager)这类工具将JAR包安装为Windows服务,实现后台运行和开机启动。
3.3.3 传递运行参数
SpringBoot支持通过命令行参数覆盖application.properties中的配置,这在部署时非常有用。
java -jar your-app.jar --server.port=8081 --spring.profiles.active=prod--server.port=8081:指定应用启动在8081端口。--spring.profiles.active=prod:激活名为prod的配置文件(如application-prod.properties),用于加载生产环境专用配置(数据库地址、日志级别等)。
4. 部署过程中的典型问题与排查实录
即使步骤清晰,实际部署时也难免遇到问题。下面是我总结的几个高频问题及排查思路。
4.1 问题一:java -jar启动时报ClassNotFoundException或NoClassDefFoundError
现象:应用启动失败,控制台抛出异常,提示找不到某个类,这个类通常是项目自身的类或某个依赖库的类。
排查思路:
- 检查打包是否完整:首先确认你运行的是
target目录下那个最大的fat jar,而不是.original文件。可以用解压工具(如7-Zip)打开JAR包,查看BOOT-INF/classes目录下是否有你的项目编译后的类文件,BOOT-INF/lib目录下是否有所有的依赖JAR。 - 检查依赖冲突:使用
mvn dependency:tree命令查看依赖树,检查是否有多个不同版本的相同依赖。SpringBoot内置的依赖管理通常能解决大部分问题,但如果你引入了第三方库,可能会引发冲突。解决方法是使用<exclusions>标签排除冲突的传递性依赖。 - 检查本地仓库:有时Maven本地仓库(
~/.m2/repository)的依赖包可能损坏。尝试删除整个本地仓库或对应出错的依赖目录,然后重新执行mvn clean package,让Maven重新下载。
4.2 问题二:端口被占用 (Port xxxx was already in use)
现象:应用启动失败,提示80、8080等端口已被占用。
解决方案:
- 更换端口:在启动命令中指定另一个端口,如
--server.port=8081。 - 找出并停止占用进程:
- Linux:
# 查找占用8080端口的进程PID sudo netstat -tlnp | grep :8080 # 或使用更现代的lsof sudo lsof -i:8080 # 终止进程 (假设PID是12345) sudo kill -9 12345 - Windows:
# 查找占用端口的PID netstat -ano | findstr :8080 # 在任务管理器的“详细信息”选项卡中,根据PID找到进程并结束,或使用命令 taskkill /PID 12345 /F
- Linux:
4.3 问题三:应用启动成功,但无法从外部访问
现象:服务器本地curl http://localhost:8080可以访问,但用其他机器的浏览器访问服务器IP:8080却连接失败。
排查思路:
- 检查SpringBoot服务绑定地址:默认情况下,SpringBoot应用监听
0.0.0.0:8080,表示接受所有网络接口的连接。但如果你在配置文件中设置了server.address=127.0.0.1,那么它只监听本地回环地址,外部自然无法访问。确保配置为0.0.0.0或不设置此项。 - 检查服务器防火墙:这是最常见的原因。
- Linux (firewalld):
# 查看开放端口 sudo firewall-cmd --list-ports # 永久开放8080端口 sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent # 重载防火墙规则 sudo firewall-cmd --reload - Linux (iptables):规则较复杂,需根据具体配置添加。
- Windows防火墙:在“高级安全Windows防火墙”中添加入站规则,允许特定端口(如8080)的TCP连接。
- Linux (firewalld):
- 检查云服务商安全组/网络ACL:如果你使用的是阿里云、腾讯云、AWS等云服务器,必须在控制台配置安全组规则,允许外部IP访问你应用监听的端口。
4.4 问题四:使用nohup启动后,如何查看和管理日志?
现象:应用用nohup启动在后台,想看看实时日志或者服务是否还活着。
解决方案:
- 查看日志文件:
# 查看最后100行 tail -100f app.log # 持续滚动查看最新日志(最常用) tail -f app.log - 查找进程:
输出结果中第二列是PID。# 根据名称查找进程 ps -ef | grep java # 或查找特定JAR包 ps -ef | grep your-app.jar - 停止应用:
# 先尝试正常终止 kill PID # 如果无效,强制终止 kill -9 PID
4.5 问题五:JVM内存参数优化
现象:应用运行一段时间后变慢或崩溃,可能是内存设置不合理。
解决方案:在java -jar命令中指定JVM参数。
java -Xms512m -Xmx1024m -jar your-app.jar-Xms512m:设置JVM初始堆内存为512MB。-Xmx1024m:设置JVM最大堆内存为1024MB。- 对于生产环境,需要根据服务器物理内存和应用实际使用情况来调整。通常
-Xms和-Xmx设置为相同值,可以避免运行期堆内存扩容带来的性能抖动。 - 还可以添加其他参数,如
-XX:+UseG1GC指定垃圾回收器等。
4.6 问题速查表
| 问题现象 | 可能原因 | 排查命令/步骤 |
|---|---|---|
java: command not found | Java未安装或环境变量未配置 | which java,echo $JAVA_HOME, 检查Path |
no main manifest attribute | 打包不正确,非可执行JAR | 检查是否使用了正确的fat jar,确认pom.xml中插件配置 |
| 启动时报数据库连接错误 | 数据库地址/密码错误,或网络不通 | 检查application-prod.properties配置,用telnet或nc测试数据库端口 |
| 应用响应慢,CPU/内存高 | 应用存在性能瓶颈或内存泄漏 | top,jps,jstack PID,jmap分析,查看GC日志 |
nohup启动后找不到进程 | 启动命令有误或进程已崩溃 | 检查nohup命令语法,查看nohup.out或指定的日志文件 |
部署SpringBoot JAR包是一个系统工程,涉及构建、传输、环境、运行、运维多个环节。掌握这个流程,意味着你具备了将代码转化为稳定服务的基本能力。从简单的java -jar到使用systemd管理,再到后续结合CI/CD工具(如Jenkins、GitLab CI)实现自动化部署,这条路会越走越宽。最关键的是,每一步都要清楚自己在做什么,遇到问题有条理地排查。希望这份结合了原理和实战经验的指南,能让你在部署之路上少走弯路。