Spring Boot 部署优化:打包体积缩小 80% 的秘诀

Spring Boot 部署优化:打包体积缩小 80% 的秘诀
在这里插入图片描述

✨道路是曲折的,前途是光明的!

📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记!

🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流!

在这里插入图片描述
在微服务架构盛行的今天,Spring Boot 应用的打包体积直接影响着部署效率和资源成本。本文将分享如何通过一系列优化手段,将一个典型 Spring Boot 应用的打包体积从 150MB 缩减至 30MB,缩减幅度达 80%。

目录


问题背景

典型场景

假设我们有一个标准的 Spring Boot Web 应用,包含以下依赖:

# 项目依赖概览dependencies:- spring-boot-starter-web - spring-boot-starter-data-jpa - spring-boot-starter-security - spring-boot-starter-validation - mysql-connector-java - lombok - commons-lang3 - guava - poi (Excel处理) - itext (PDF处理) 

执行 mvn clean package 后,得到一个 150MB 的 fat JAR:

target/ ├── application-1.0.0.jar (150MB) └── original-application-1.0.0.jar (30KB) 

为什么这么大?

让我们深入分析这个 150MB 的 JAR 包结构:

65%20%10%3%2%Spring Boot Fat JAR 体积构成分析第三方依赖库 [65]Spring Framework [20]业务代码 [2]嵌入式Tomcat [10]其他资源 [3]

分析结论

  • 第三方依赖 占据了 65% 的体积(97.5MB)
  • Spring Framework 自身占用 20%(30MB)
  • 业务代码 仅占 2%(3MB)
  • 嵌入式 Tomcat 占用 10%(15MB)

体积分析

1. 依赖分析工具

首先,我们需要找出哪些依赖占用空间最大。

Maven Dependency Analyzer
# 查看依赖树 mvn dependency:tree # 分析依赖大小 mvn com.github.ferstl:depgraph-maven-plugin:aggregate 
JAR 分析工具
# 使用 jar 命令查看内容 jar tf application.jar |head -20 # 解压后分析目录大小unzip -q application.jar -d app-extracted du -sh app-extracted/BOOT-INF/lib/* |sort -hr |head -10 
典型的大体积依赖
依赖体积说明
spring-boot-starter-web~15MB包含完整的 Web 模块
poi-ooxml~20MBExcel 处理全套组件
itextpdf~18MBPDF 生成库
tensorflow-core~50MBAI 模型(如有)
guava~8MBGoogle 工具库

2. 瘦身机会识别

Fat JAR 150MB

分析优化机会

移除未使用依赖
预期减少: 20-30MB

模块化拆分
预期减少: 40-50MB

启用分层构建
预期减少: 60-70MB

使用 Native Image
预期减少: 80-100MB

目标: 120MB

目标: 100MB

目标: 50MB

目标: 30MB


优化策略

策略一:依赖精简

1.1 排除传递依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 排除默认的 Tomcat,改用 Undertow --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><!-- Undertow 更轻量 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency></dependencies>
1.2 使用范围限定依赖
<dependencies><!-- 测试依赖仅在测试时使用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Lombok 仅在编译时需要 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency></dependencies>
1.3 替换重型依赖
// 场景:Excel 导出功能// 方案 A:使用 Apache POI(重型,~20MB)importorg.apache.poi.ss.usermodel.*;importorg.apache.poi.xssf.usermodel.*;// 方案 B:使用 EasyExcel(轻量,~2MB)importcom.alibaba.excel.EasyExcel;importcom.alibaba.excel.write.metadata.WriteSheet;// 代码对比publicclassExcelExportExample{// EasyExcel 实现(内存占用更小)publicvoidexportLightweight(HttpServletResponse response){ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8");EasyExcel.write(response.getOutputStream(),UserData.class).sheet("用户数据").doFinish(()->{// 流式写入,内存友好});}}

策略二:分层 JAR 构建

2.1 Spring Boot Layered JARs

Spring Boot 2.3+ 支持分层 JAR,配合 Docker 可以极大优化镜像大小。

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><layers><enabled>true</enabled></layers></configuration></plugin></plugins></build>
2.2 分层结构说明

Layered JAR

Layer 1: 依赖

Layer 2: Spring Boot Loader

Layer 3: SNAPSHOT 依赖

Layer 4: 应用代码

Layer 5: 应用资源

变化频率: 极低

变化频率: 中等

变化频率: 高

优化原理:Docker 构建时,只有变化的层会被重新构建和推送,变化频率低的层可以长期复用缓存。

2.3 多阶段 Dockerfile
# 构建阶段 FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 运行阶段 - 使用精简的基础镜像 FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 只复制必要的文件 COPY --from=builder /app/target/*.jar app.jar # 创建非 root 用户 RUN addgroup -S spring && adduser -S spring -G spring USER spring:spring EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] 

策略三:自定义类加载

3.1 外部依赖 JAR
/** * 外部依赖加载器 * 将依赖放在外部 lib/ 目录,减少主 JAR 体积 */publicclassExternalDependencyLauncher{publicstaticvoidmain(String[] args)throwsException{File libDir =newFile("lib"); URL[] jarUrls =Arrays.stream(Objects.requireNonNull(libDir.listFiles())).filter(file -> file.getName().endsWith(".jar")).map(File::toURI).map(uri ->{try{return uri.toURL();}catch(MalformedURLException e){thrownewRuntimeException(e);}}).toArray(URL[]::new);URLClassLoader classLoader =newURLClassLoader(jarUrls);Thread.currentThread().setContextClassLoader(classLoader);// 启动 Spring Boot 应用Class<?> appClass = classLoader.loadClass("com.example.Application");Method mainMethod = appClass.getMethod("main",String[].class); mainMethod.invoke(null,(Object) args);}}
3.2 Maven 配置
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifest><mainClass>com.example.ExternalDependencyLauncher</mainClass><classpathPrefix>lib/</classpathPrefix><addClasspath>true</addClasspath></manifest></archive></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><executions><execution><id>copy-dependencies</id><phase>package</phase><goals><goal>copy-dependencies</goal></goals><configuration><outputDirectory>${project.build.directory}/lib</outputDirectory></configuration></execution></executions></plugin></plugins></build>

策略四:GraalVM Native Image

4.1 原理说明

Java 源码

Maven 编译

Class 文件

GraalVM Native Image

原生可执行文件

传统 JVM

JVM 启动
~2-5秒

类加载
~1-3秒

JIT 编译
运行时

应用运行

Native Image

直接执行
~0.1秒

4.2 配置示例
<!-- pom.xml --><dependencies><dependency><groupId>org.springframework.experimental</groupId><artifactId>spring-native</artifactId><version>0.12.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.experimental</groupId><artifactId>spring-aot-maven-plugin</artifactId><version>0.12.0</version><executions><execution><id>test-generate</id><goals><goal>test-generate</goal></goals></execution><execution><id>generate</id><goals><goal>generate</goal></goals></execution></executions></plugin></plugins></build><profiles><profile><id>native</id><build><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><version>0.9.13</version><executions><execution><id>build-native</id><goals><goal>build</goal></goals><phase>package</phase></execution></executions></plugin></plugins></build></profile></profiles>
4.3 反射配置
// src/main/resources/META-INF/native-image/reflect-config.json[{"name":"com.example.model.User","allDeclaredConstructors":true,"allPublicConstructors":true,"allDeclaredMethods":true,"allPublicMethods":true,"allDeclaredFields":true,"allPublicFields":true},{"name":"com.example.controller.UserController","allDeclaredConstructors":true,"allDeclaredMethods":true}]

实战演示

完整优化配置

Maven 配置文件
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>optimized-spring-boot</artifactId><version>1.0.0</version><name>Optimized Spring Boot Application</name><properties><java.version>17</java.version><!-- 排除不必要的依赖 --><spring-boot-admin.version>3.1.0</spring-boot-admin.version></properties><dependencies><!-- Web 模块 - 使用 Undertow 替代 Tomcat --><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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency><!-- 只引入需要的模块 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- 数据库 - 按需引入 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 驱动 - 使用 runtime scope --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- 工具类 - 选择轻量级替代品 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!-- Lombok - provided scope --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- 测试依赖 - test scope --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><!-- Spring Boot 分层插件 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><layers><enabled>true</enabled></layers><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin><!-- 依赖分析插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><executions><execution><id>analyze</id><goals><goal>analyze</goal></goals></execution></executions></plugin></plugins></build></project>
优化后的 Dockerfile
# syntax=docker/dockerfile:1.4 # 多阶段构建 + BuildKit 缓存优化 # =================================== # 阶段 1: 依赖解析 (利用 Docker 缓存) # =================================== FROM maven:3.8-eclipse-temurin-17 AS deps WORKDIR /app # 先复制 pom.xml,下载依赖(这一层会被缓存) COPY pom.xml ./ RUN mvn dependency:go-offline -B # =================================== # 阶段 2: 编译 (代码变化时重新执行) # =================================== FROM maven:3.8-eclipse-temurin-17 AS builder WORKDIR /app # 从 deps 阶段复制本地仓库 COPY --from=deps /root/.m2 /root/.m2 # 复制源代码 COPY src ./src # 构建应用 RUN mvn clean package -DskipTests -B && \ mv target/*.jar app.jar # =================================== # 阶段 3: 运行时镜像 (最小化体积) # =================================== FROM eclipse-temurin:17-jre-alpine # 安装必要的工具和时区数据 RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata WORKDIR /app # 创建非特权用户 RUN addgroup -S appgroup && \ adduser -S appuser -G appgroup # 复制应用 JAR COPY --from=builder --chown=appuser:appgroup /app/app.jar app.jar # 切换用户 USER appuser # JVM 参数优化 ENV JAVA_OPTS="-XX:+UseContainerSupport \ -XX:MaxRAMPercentage=75.0 \ -XX:InitialRAMPercentage=50.0 \ -XX:+UseG1GC \ -Djava.security.egd=file:/dev/./urandom" EXPOSE 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] 
应用配置优化
# application.ymlspring:# 禁用不需要的自动配置autoconfigure:exclude:- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration # Web 优化web:resources:chain:cache:truestatic-locations: classpath:/static/ server:# Undertow 配置undertow:threads:io:4worker:20buffer-size:1024direct-buffers:true# 管理端点management:endpoints:web:exposure:include: health,info,metrics endpoint:health:show-details: always # 日志配置logging:level:root: INFO com.example: DEBUG pattern:console:"%d{yyyy-MM-dd HH:mm:ss} - %msg%n"

效果对比

优化前后对比表

指标优化前优化后改善幅度
JAR 包大小150 MB30 MB⬇️ 80%
Docker 镜像450 MB120 MB⬇️ 73%
启动时间8.5 秒2.3 秒⬇️ 73%
内存占用(运行时)512 MB256 MB⬇️ 50%
构建时间90 秒120 秒⬆️ 33%
注意:构建时间略有增加是正常的,因为增加了分层构建和优化步骤,但这是一次性成本。

体积优化分解图

35%25%20%10%10%各优化策略贡献分析依赖精简分层构建Undertow替换Native编译其他优化

不同方案适用场景

传统Web应用

微服务

云原生

选择优化方案

应用类型

方案1: 依赖精简
+ 分层构建
体积减少: 50%

方案2: 多阶段构建
+ 外部依赖
体积减少: 60%

方案3: Native Image
体积减少: 80%
启动时间减少: 90%

适用场景:
传统部署、虚拟机

适用场景:
Kubernetes、容器化

适用场景:
Serverless、边缘计算


最佳实践

1. 持续监控

# 定期检查依赖大小 mvn dependency:tree |grep -E "compile|runtime"# 使用 Maven 插件分析 mvn com.github.ferstl:depgraph-maven-plugin:aggregate 

2. 依赖管理规范

<!-- 在父 POM 中统一管理版本 --><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>3.2.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

3. CI/CD 集成

# .github/workflows/docker-build.ymlname: Build and Push Docker Image on:push:branches:[main]jobs:build:runs-on: ubuntu-latest steps:-uses: actions/checkout@v3 -name: Set up JDK 17 uses: actions/setup-java@v3 with:java-version:'17'distribution:'temurin'-name: Build with Maven run: mvn clean package -DskipTests -name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 -name: Login to Docker Hub uses: docker/login-action@v2 with:username: ${{ secrets.DOCKER_USERNAME }}password: ${{ secrets.DOCKER_PASSWORD }}-name: Build and push uses: docker/build-push-action@v4 with:context: . push:truetags: user/app:latest cache-from: type=gha cache-to: type=gha,mode=max build-args:| BUILDKIT_INLINE_CACHE=1

4. 避免的陷阱

陷阱说明解决方案
过度优化为了优化而优化,牺牲可维护性明确优化目标,权衡利弊
忽略测试删除"未使用"的依赖后测试失败完善测试覆盖,使用依赖分析工具
Native Image 兼容性反射、动态代理可能不工作提前测试,编写配置文件
安全隐患使用精简镜像可能缺少安全补丁定期更新基础镜像

总结

优化路线图

开始
Fat JAR 150MB

第一步
依赖分析

第二步
依赖精简
120MB

第三步
切换Undertow
110MB

第四步
分层构建
50MB

是否需要
极致优化?

第五步
Native Image
30MB

完成
50MB

完成
30MB

关键要点

  1. 先分析再优化:使用工具找出真正的体积大头
  2. 分步优化:不要一次性应用所有策略,逐步验证
  3. 权衡取舍:体积、性能、兼容性三者之间找平衡
  4. 团队协作:建立依赖管理规范,防止体积膨胀
  5. 持续改进:定期审查依赖,移除不再使用的库

参考资料


✍️ 坚持用清晰易懂的图解+可落地的代码,让每个知识点都简单直观!💡 座右铭:“道路是曲折的,前途是光明的!”

Read more

Flutter for OpenHarmony: Flutter 三方库 flutter_cors 应对鸿蒙 Web 与混合开发中的跨域挑战(网络兼容方案)

Flutter for OpenHarmony: Flutter 三方库 flutter_cors 应对鸿蒙 Web 与混合开发中的跨域挑战(网络兼容方案)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 的跨平台开发时,我们不仅开发原生 HAP,有时也会涉及 Flutter Web 或是在鸿蒙端侧运行 Webview 混合应用。这时,一个经典的“拦路虎”就会出现:CORS (跨源资源共享) 限制。当你的 Web 端尝试访问一个未配置跨域头部的后端 API 时,请求会被浏览器拦截,报错信息极其晦涩。 虽然 CORS 主要是后端的工作,但 flutter_cors 提供了一种客户端视角的辅助工具。它通过工具化手段帮助开发者分析、绕过或生成跨域适配规则,是保证鸿蒙跨平台 Web 项目顺利运行的调试利器。 一、跨域访问逻辑模型 CORS 是一种浏览器的安全保护机制,它在请求发出前先进行“预检(Preflight)

By Ne0inhk
[前后端系统开发教程]第四节-前端多平台部署的终极解决方案

[前后端系统开发教程]第四节-前端多平台部署的终极解决方案

在上一节中我们已经制作了一个简单的用户管理后端系统,我们这节就来尝试制作一个对应的前端系统。那么,我们是要使用安卓开发者工具制作一个安卓app,或者部署为微信小程序,亦或部署为传统的html网页? 答案是我全都要!通过DCloud生态,我们可以实现一份代码,多端部署。 第一部分:什么是DCloud生态? 众将士多端露难色,新面孔竟生好胆识 注:本节开始,教程的节奏会适当加快,希望各位可以跟上。 简单来说,DCloud生态的核心功能是,通过将项目按照不同的目标部署平台,二次编译为对应平台的代码,以实现“一份代码,多端部署”,以提高开发效率。详细介绍请参考uniapp官方文档:简介 - HBuilderX 文档。DCloud还提供云函数、云对象等工具,我们将在教程的后面去学习。 在这节教程中我们先学习如何在HBuilderX中调用上节中后端系统的API(即后端服务接口),编写一份前端代码,再将其打包为微信小程序、html网页和安卓app。 第二部分:怎么调用后端API接口? 接口表叫那前端瞧,服务器知晓谁来还 我们先回顾一下上节教程中的接口类,将其整理为一份API接口说明

By Ne0inhk
AI编程实战 : 使用 TRAE CN 将 MasterGo 设计稿转化为前端代码

AI编程实战 : 使用 TRAE CN 将 MasterGo 设计稿转化为前端代码

文章目录 * 什么是 MCP * 前置条件 * 1. 账号权限 * 2. 环境要求 * 3. 设计稿准备 * MasterGo AI Bridge 支持的能力 * 操作步骤 * 第一步: 安装/升级 TRAE CN IDE * 第二步: 获取 MasterGo 的 Personal Access Token * 第三步: 添加 MCP Server * 第四步: 创建自定义智能体(可选) * 第五步: 调用 MCP 生成前端代码 * 5.1 复制 MasterGo 设计稿链接 * 5.2 在 TRAE CN IDE

By Ne0inhk
AI 生成的 UI 太丑?3 步让你的前端秒变高级感

AI 生成的 UI 太丑?3 步让你的前端秒变高级感

🚀 AI 生成的 UI 太丑?3 步让你的前端秒变高级感 你是不是也遇到过这种情况:满心期待地用 AI 生成一个前端页面,结果得到的是一个土到掉渣的蓝紫色界面,丑到自己都看不下去?🤦‍♂️ 别担心,你不是一个人!这是目前 90% 开发者使用 AI 写前端时都会遇到的痛点。 好消息是,经过一番研究和实践,我们发现了一些有效的方法!通过几个简单的技巧,不需要手写任何 CSS,就能让 AI 帮你生成媲美专业设计师的 UI 界面。 今天就手把手教你 3 步搞定,让 AI 彻底告别 “AI 味”! 🧪 实验准备 工具准备 想要跟着实验,你需要准备: 1. Claude Code (2.0.55) 底层模型是 Minimax-M2

By Ne0inhk