本文目录导读:

- 📖 目录导读
- 什么是“可执行Jar”?—— 不只是“双击就能跑”那么简单
- 三大主流构建工具实操(Maven/Gradle/IDE)
- 真实案例:Spring Boot + 外部配置 + 多环境打包
- 可执行Jar的隐藏陷阱(类加载器、资源泄漏、瘦身方案)
- 生产环境部署:脚本化启动 + 优雅停机 + 日志切割
- 常见问题FAQ(面试高频 & 实战踩坑)
- 一劳永逸的发布流程设计
可执行Jar案例深度解析:从零构建到自动化部署的完整实战指南
📖 目录导读
- 什么是“可执行Jar”?—— 不只是“双击就能跑”那么简单
- 三大主流构建工具实操(Maven/Gradle/IDE)
- 真实案例:Spring Boot + 外部配置 + 多环境打包
- 可执行Jar的隐藏陷阱(类加载器、资源泄漏、瘦身方案)
- 生产环境部署:脚本化启动 + 优雅停机 + 日志切割
- 常见问题FAQ(面试高频 & 实战踩坑)
- 一劳永逸的发布流程设计
什么是“可执行Jar”?—— 不只是“双击就能跑”那么简单
很多初学者误以为“可执行Jar”仅仅是在打包时加了个Main-Class属性,真正的可执行Jar(Fat Jar / Uber Jar)需要解决依赖打包、资源文件路径、类加载顺序三大核心问题。
在经典的“瘦Jar”中,如果你的应用引用了10个第三方库,部署时就必须手动维护lib/目录并拼写冗长的classpath,而可执行Jar将所有.class和.jar依赖重新压缩到一个单一文件中,真正实现了“单文件交付”。
关键原理:JVM规范中的JarFile支持嵌套Jar加载,Spring Boot Loader通过自定义JarURLConnection实现了jar-in-jar的透明读取,这意味你可以直接用:
java -jar my-app.jar
而无需管里面的依赖在哪里。
三大主流构建工具实操(Maven/Gradle/IDE)
案例A:Maven maven-shade-plugin(最通用)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
<!-- 排除签名文件,避免SecurityException -->
<filters>
<filter><artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
案例B:Gradle Shadow 插件(更简洁)
plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' }
jar { manifest { attributes 'Main-Class': 'com.example.Main' } }
执行 ./gradlew shadowJar,输出-all.jar即可直接运行。
案例C:IDE内置(IDEA/Eclipse)
IDEA中:File → Project Structure → Artifacts → + → JAR → From modules with dependencies → Main Class → Build,此方法适合快速原型,但不适合CI/CD,因为IDE环境不可重复。
真实案例:Spring Boot + 外部配置 + 多环境打包
需求背景:某支付服务需要根据dev/prod环境切换数据库地址,且生产环境不能重新打包。
步骤1:配置文件分离
# application.yml
server:
port: 8080
spring:
profiles:
active: @profile.active@ # 通过Maven profile动态填充
步骤2:Maven多Profile配置
<profiles>
<profile>
<id>dev</id>
<properties><profile.active>dev</profile.active></properties>
</profile>
<profile>
<id>prod</id>
<properties><profile.active>prod</profile.active></properties>
</profile>
</profiles>
打包命令:mvn clean package -Pprod -DskipTests。
步骤3:生产运行(外部化配置)
java -jar app.jar --spring.config.location=/etc/myapp/application-prod.yml
关键点:可执行Jar内嵌的配置文件只是“默认值”,外部配置优先级永远高于内部配置,这一机制使得运维无需修改jar包,只需维护一个外部yaml即可。
可执行Jar的隐藏陷阱(类加载器、资源泄漏、瘦身方案)
陷阱1:NoClassDefFoundError 或 ClassNotFoundException
原因:多个依赖中存在相同类的不同版本(冲突),使用mvn dependency:tree排查,用exclusions排除旧版。
陷阱2:静态资源读取失败
错误写法:new File("src/main/resources/data.txt"),正确方法:
ClassPathResource res = new ClassPathResource("data.txt");
InputStream is = res.getInputStream();
因为打包后资源在Jar内部,不再是文件系统路径。
陷阱3:瘦身方案
如果无法接受50MB的Fat Jar,使用spring-boot-maven-plugin的requiresUnpack精确控制哪些依赖需要解压,或者使用layertools分离依赖层:
java -Djarmode=layertools -jar app.jar extract
然后通过docker build时逐层缓存,大幅减少镜像推送时间。
生产环境部署:脚本化启动 + 优雅停机 + 日志切割
启动脚本(配合systemd)
#!/bin/bash
JAR_PATH=/opt/myapp/app.jar
PID_FILE=/var/run/myapp.pid
case $1 in
start)
nohup java -Xms512m -Xmx1g -jar $JAR_PATH --spring.config.location=/etc/myapp/ > /var/log/myapp/stdout.log 2>&1 &
echo $! > $PID_FILE ;;
stop)
# 优雅停机:Spring Boot 2.3+自带 /actuator/shutdown
curl -X POST http://localhost:8080/actuator/shutdown -H 'X-Forwarded-For: internal'
;;
restart)
$0 stop; sleep 2; $0 start;;
esac
日志切割(logrotate)
/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
copytruncate
}
常见问题FAQ(面试高频 & 实战踩坑)
Q1:java -jar 和 java -cp 启动有什么区别?
A:-jar 忽略-cp,仅依据Manifest中的Class-Path和Main-Class;-cp手动管理所有类,可执行Jar内嵌了所有依赖,所以无需外部-cp。
Q2:为什么我打包后运行提示“没有主清单属性”?
A:maven-jar-plugin默认不包含Main-Class,必须配合maven-shade-plugin(第2节)或直接修改jar插件的manifest配置。
Q3:可执行Jar内的日志文件能写到当前目录吗?
A:写法相同,但注意磁盘权限,更推荐日志通过logback的${user.home}/logs或外部挂载卷,避免容器文件系统碎片化。
Q4:如何降低可执行Jar的启动内存?
A:使用-XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none(JDK8),或者改用GraalVM原生镜像。
Q5:Spring Boot可执行Jar能否直接解压修改资源后重新打包?
A:不推荐,Jar是ZIP格式但包含“缺失的头部”定位信息,手工修改会破坏内部索引,正确做法是使用“提取→修改→重新打包”的layertools插件。
一劳永逸的发布流程设计
一个高质量的可执行Jar发布流程应具备:
- 可重复构建:基于Maven/Gradle,锁定依赖版本,拒绝使用
SNAPSHOT。 - 外部化配置:环境相关一切参数从环境变量或外部配置文件注入。
- 版本化与回滚:使用
git tag记录Jar SHA256值,部署时通过软链切换版本。 - 安全加固:使用
jarsigner对Jar签名,防止篡改。
最终验收标准:新同事拿到jar包,不阅读任何文档,仅凭--help和默认配置就能在5分钟内启动服务并看到健康检查通过。
你可以自信地告诉团队:“从今天起,我们的部署只有一条命令:scp app.jar target:/opt/myapp/ && ssh target ./restart.sh”。