如果你在2025年仍将Spring Boot应用程序打包成Fat JAR,那你已经落后了。
虽然Fat JAR看起来是一种方便的、一体化的解决方案,但它们正悄然对你的持续集成/持续交付(CI/CD)流水线造成严重破坏。它们使你的Docker镜像变得臃肿,拖慢了构建速度,还消耗了不必要的云计算资源——而且在部署时间大幅增加、成本飙升之前,你甚至可能都不会注意到这些低效率问题。
这篇文章不只是发牢骚——它是一次警钟。让我们来剖析一下为什么Fat JAR是个问题,它们是如何在不知不觉中耗尽你的资源的,以及最重要的是,更明智的Spring Boot开发者们正在采用的替代方案是什么。
![null 图片[1]-停止使用 Spring Boot 的Fat JAR,它正在拖垮你的 Pipeline-Spring专区论坛-技术-SpringForAll社区](https://miro.medium.com/v2/resize:fit:1400/1*Urc28sbnORGOW5oyohQ06g.gif)
什么是Fat JAR?📦
Fat JAR是一种Java归档(JAR)文件,它不仅包含你应用程序的类文件,还包含所有的依赖项、库,有时甚至还有资源文件。它是一个一体化的包,旨在让部署变得简单——只需运行java -jar app.jar
,你就可以启动应用了。
Spring Boot默认使用Spring Boot Maven插件来支持这种打包方式。它将所有内容打包到一个自包含的可执行JAR文件中。
听起来不错,对吧? 嗯……并不总是这样。
CI/CD中Fat JAR的真正成本🐌
1. 构建时间缓慢
Fat JAR非常庞大。一个简单的Spring Boot应用程序的大小很容易达到60MB到150MB。每次你的CI/CD流水线触发构建时,它都必须:
- • 下载依赖项
- • 将它们与你的代码打包在一起
- • 归档并推送完整的工件
这会减慢从编译到工件上传的每一个构建步骤。
2. Docker镜像臃肿
如果你正在将你的应用程序容器化(很可能你就是这样做的),那么那些庞大的Fat JAR就会成为你的Docker镜像的基础。最终会导致:
- • Docker镜像大小超过300MB
- • 镜像构建时间长
- • 推送/拉取镜像时网络流量增加
- • 部署期间水平扩展速度变慢
在Kubernetes或AWS Lambda中,每一个字节都很重要。Fat JAR会影响冷启动性能,并增加基础设施成本。
3. Docker中较差的层缓存机制
当Docker的层不发生变化时,层缓存机制的效果最好。但是对于Fat JAR,即使是一行代码的更改也可能使整个JAR文件失效。这意味着Docker会将其视为一个全新的文件,使缓存层失效,并强制进行完整的重建和推送。
你的CI/CD系统(例如,GitHub Actions、GitLab CI、Jenkins)最终会每次都重新上传完整的工件。
4. 不必要的重新部署
因为Fat JAR包含了所有内容,所以即使是单个.class
文件的更改也会导致生成一个全新的JAR。这就迫使整个应用程序都要重新部署,即使95%的内容都没有改变。
在微服务架构中,这种低效率会在各个服务之间成倍增加——浪费时间、计算资源和资金。
Spring Boot开发者能做些什么呢?💡
别担心——Fat JAR并不是世界末日。有一些更好的方法可以在不牺牲开发者体验的情况下构建、打包和部署Spring Boot应用程序。
让我们来看看最有效的解决方案。
1. 使用Fat JAR + 依赖项缓存
Thin JAR只包含你的应用程序代码——不包含所有的依赖项。你可以将它与清单文件或依赖项列表一起部署。
像Spring Boot Thin Launcher这样的工具或自定义脚本可以将依赖项与应用程序代码分离。CI/CD可以缓存依赖项,并且只有应用程序的JAR会被重新构建。
优点:
- • 工件小得多
- • Docker构建速度快(依赖项保持缓存状态)
- • 部署速度更快
在Kubernetes等生产环境中,你甚至可以将依赖项层存储在卷或挂载位置。
2. 使用多阶段Docker构建
多阶段构建允许你在一个阶段编译Fat JAR,然后在最后一个阶段仅将输出的JAR复制到一个精简的运行时镜像中。
示例Dockerfile:
# 阶段1:构建
FROM maven:3.9.0-eclipse-temurin-17 as builder
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# 阶段2:运行
FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY –from=builder /app/target/app.jar app.jar
ENTRYPOINT [“java”, “-jar”, “app.jar”]
它有什么帮助:
- • 将构建工具排除在生产镜像之外
- • 减小最终镜像的大小
- • 使Docker缓存更有效
3. 使用构建包(Paketo)拆分层
Paketo构建包提供了更智能的方法,可以将Spring Boot应用程序构建成经过优化的容器。它们会检测依赖项,分离层,并生成可用于生产的镜像,而且不需要Dockerfile。
pack build my-app --builder paketobuildpacks/builder:base
主要优点:
- • 自动层缓存
- • 重新构建的部分更小
- • CI/CD流水线速度更快
- • 与Heroku或Cloud Foundry等平台的兼容性更好
Paketo也被Spring Boot的原生构建工具在后台使用。
4. 切换到原生镜像(GraalVM + Spring AOT)
Spring Boot 3及以上版本支持提前(AOT)编译和GraalVM原生镜像,它可以将你的应用程序编译成一个独立的二进制文件。
没有JAR文件。没有JVM启动开销。只有快速、小巧的可执行文件。
./mvnw spring-boot:build-image -Pnative
优点:
⚡ 启动速度极快(非常适合无服务器场景)
🐜 镜像大小极小(约50MB)
🚀 在容器中可即时横向扩展
对于对性能敏感的Spring Boot应用程序来说,这就是未来的发展方向。不过要注意其局限性(例如,反射处理、构建时间较长)。
5. 重新思考CI/CD中的工件策略
不要总是重新构建所有内容,而是配置你的CI/CD流水线,使其:
- • 缓存Maven依赖项
- • 缓存Docker层
- • 分离测试和部署流水线
- • 重用预构建的基础镜像
例如,在GitHub Actions中:
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
这些小的调整可以极大地提高流水线速度,并降低计算成本。
最终思考🚀
在容器化之前的时代,Fat JAR让Java部署变得简单。但在如今这个由CI/CD驱动的云原生世界里,它们往往带来的麻烦比其价值更多。
以下是要点总结:
✅ Fat JAR虽然方便,但对于流水线和容器构建来说成本高昂。
✅ Spring Boot应用程序可以使用Thin JAR、分层构建或原生镜像来摒弃Fat JAR。
✅ 像多阶段Dockerfile、构建包和GraalVM这样的工具让你有了实现精简的灵活性。
📉 如果你的CI/CD流水线感觉迟缓或臃肿,也许是时候看看你在工件中打包了些什么了。精简你的构建并加快你的交付速度。
你怎么看💬?你在项目中摒弃Fat JAR了吗?对你来说哪种策略效果最好?留下评论,或者与你的团队分享这篇文章!
没有回复内容