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

(免费领源码)基于SpringBoot+Vue的日用品购物平台93947-计算机毕设JAVA、PHP、python、爬虫、APP、小程序、C# 、C++、数据可视化、大数据、全套文案

(免费领源码)基于SpringBoot+Vue的日用品购物平台93947-计算机毕设JAVA、PHP、python、爬虫、APP、小程序、C# 、C++、数据可视化、大数据、全套文案

目  录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 日用品购物平台分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系统流程分析 2.4.1数据流程 2.

By Ne0inhk
Flutter 三方库 image_compare_2 的鸿蒙化适配指南 - 实现像素级的图像分块对比、支持感知哈希(pHash)与端侧视觉差异检测实战

Flutter 三方库 image_compare_2 的鸿蒙化适配指南 - 实现像素级的图像分块对比、支持感知哈希(pHash)与端侧视觉差异检测实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 image_compare_2 的鸿蒙化适配指南 - 实现像素级的图像分块对比、支持感知哈希(pHash)与端侧视觉差异检测实战 前言 在进行 Flutter for OpenHarmony 的图像处理、自动化 UI 测试或内容防侵权应用开发时,如何科学地判断两张图片是否“相似”?简单的字节对比显然无法处理微小的色差或尺寸缩放。image_compare_2 是一个功能完备的图像对比算法库。它支持从均值哈希(aHash)到分块均方差(MSE)等多种度量算法。本文将指导大家如何在鸿蒙真机上利用该库构建精准的视觉检测链路。 一、原原理性解析 / 概念介绍 1.1 基础原理 image_compare_2 通过将原始图片灰度化、缩小尺寸并进行频域变换(或像素聚合)

By Ne0inhk
Python爬虫实战实例:Python6个爬虫小案例(附源码)收藏这篇就够了

Python爬虫实战实例:Python6个爬虫小案例(附源码)收藏这篇就够了

引言 随着互联网的快速发展,数据成为了新时代的石油。Python作为一种高效、易学的编程语言,在数据采集领域有着广泛的应用。本文将详细讲解Python爬虫的原理、常用库以及实战案例,帮助读者掌握爬虫技能。 ==(文末获取Python入门学习资料+视频教程+学习路线)== 一、爬虫原理 爬虫,又称网络爬虫,是一种自动获取网页内容的程序。它模拟人类浏览网页的行为,发送HTTP请求,获取网页源代码,再通过解析、提取等技术手段,获取所需数据。 1. HTTP请求与响应过程 爬虫首先向目标网站发送HTTP请求,请求可以包含多种参数,如URL、请求方法(GET或POST)、请求头(Headers)等。服务器接收到请求后,返回相应的HTTP响应,包括状态码、响应头和响应体(网页内容)。 2. 常用爬虫技术 (1)请求库:如requests、aiohttp等,用于发送HTTP请求。 (2)解析库:如BeautifulSoup、lxml、PyQuery等,

By Ne0inhk
Python从0到100(九十四):深度可分离卷积的深入解析及在OPPORTUNITY数据集上的实战

Python从0到100(九十四):深度可分离卷积的深入解析及在OPPORTUNITY数据集上的实战

前言:零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学业升学和工作就业的先行者! 【优惠信息】 • 新专栏订阅前500名享9.9元优惠 • 订阅量破500后价格上涨至19.9元 • 订阅本专栏可免费加入粉丝福利群,享受: - 所有问题解答 -专属福利领取 欢迎大家订阅专栏:零基础学Python:Python从0到100最新最全教程! 本文目录: * 一、深度可分离卷积的基础原理 * 1. 传统卷积的痛点 * 2. 深度可分离卷积的核心思路 * 二、深度可分离卷积的架构 * 1. 输入层 * 2. 深度可分离卷积模块 * 2.1 深度卷积(Depthwise Convolution) * 2.2 点卷积(Pointwise Convolution)

By Ne0inhk