停止使用 Spring Boot 的Fat JAR,它正在拖垮你的 Pipeline-Spring专区论坛-技术-SpringForAll社区

停止使用 Spring Boot 的Fat JAR,它正在拖垮你的 Pipeline

如果你在2025年仍将Spring Boot应用程序打包成Fat JAR,那你已经落后了。

虽然Fat JAR看起来是一种方便的、一体化的解决方案,但它们正悄然对你的持续集成/持续交付(CI/CD)流水线造成严重破坏。它们使你的Docker镜像变得臃肿,拖慢了构建速度,还消耗了不必要的云计算资源——而且在部署时间大幅增加、成本飙升之前,你甚至可能都不会注意到这些低效率问题。

这篇文章不只是发牢骚——它是一次警钟。让我们来剖析一下为什么Fat JAR是个问题,它们是如何在不知不觉中耗尽你的资源的,以及最重要的是,更明智的Spring Boot开发者们正在采用的替代方案是什么

图片[1]-停止使用 Spring Boot 的Fat JAR,它正在拖垮你的 Pipeline-Spring专区论坛-技术-SpringForAll社区

什么是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了吗?对你来说哪种策略效果最好?留下评论,或者与你的团队分享这篇文章!

请登录后发表评论

    没有回复内容