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

借助Nano Banana Pro四步画出高质量科研插图,实操教程(附提示词公式+AI提示词模板)

借助Nano Banana Pro四步画出高质量科研插图,实操教程(附提示词公式+AI提示词模板)

一提到科研绘图,很多同仁会想到可以利用AI来进行辅助,但实际操作时,难就难在:提示词怎么写。不同学科绘图类型不同,表达的重点也完全不同。 可能大多数同仁都会遇到这种情况,明明清楚自己要画什么,但提供给AI自己的思路后,AI生成的图总是差点意思,达不到预想的效果。 其实,只要掌握方法,用对工具,生成专业、准确的科研插图并不难,今天七哥教大家借助Nano Banana Pro四步,轻松画出符合要求的高质量科研插图,并给出四个常见学科场景的提示词公式+模板供大家参考。 第一步:需求分析与准备 想要借助AI绘制科研插图,就得先在自己学科的基础上,明确绘图的类型,是画机制示意图、流程图还是概念框架图....然后需要先对插图有个基本的文字描述,如果想要出图效果更好,也可准备几张顶刊类似的图表风格作为风格参考,为接下来借助AI绘图做准备。 第二步:选择AI工具 选择超强大的绘图模型Nano Banana Pro,选它的原因,一个是出图快,能在30秒内画出高质量插图;二是可设置比例,高清无水印中文不乱码。 第三步:输入设计的提示词 不同的学科科研插图的差异还是比较大的,主要原因是

By Ne0inhk
IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地

IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地

IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地 AINode 作为 IoTDB 原生时序 AI 节点,内置 Timer 系列等业界领先时序大模型,支持通过标准 SQL 语句完成模型注册、管理与推理全流程,无需 Python/Java 编程,更无需迁移 IoTDB 存储的数据。本文详细拆解 AINode 的核心优势、模型注册 / 调用 / 权限管理等关键操作,结合电力负载预测、变电站电压预测、民航旅客异常检测三大工业级案例,手把手演示如何通过简单 SQL 实现时序数据的趋势预测、缺失值填补与异常识别,助力开发者快速落地毫秒级实时时序 AI 应用。 AINode 是支持时序相关模型注册、管理、调用的 IoTDB

By Ne0inhk
编程小白也能玩转AI!零基础入门指南

编程小白也能玩转AI!零基础入门指南

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 【前言】 你是不是也经常听到“AI时代来了”,却总觉得那是程序员、算法工程师的专属领域?自己连代码都没写过,是不是就只能旁观?别急——2025年,AI早已不是高不可攀的黑科技,而是一套普通人也能上手的实用工具。本文专为零编程基础的小白量身打造,不讲公式、不谈理论,只教你怎么用最简单的方式,把大模型变成你的“智能外挂”:写周报、回邮件、分析数据、做方案……从注册账号到跑通第一段代码,手把手带你迈出第一步。你会发现,会打字,就能玩转AI。 一、别被“AI”吓到!你不需要成为科学家 很多人一听“人工智能”,立刻想到: * 要懂数学?要会微积分? * 要会写复杂代码? * 要有GPU服务器? 错!2025年了,AI已经像Word、

By Ne0inhk

1Panel+Ollama+WebUI:打造本地AI模型的完整指南(附Gemini插件教程)

1Panel、Ollama与Open WebUI:构建你的私有化AI模型应用平台实战 在AI技术日益普及的今天,许多开发者和技术爱好者不再满足于仅仅调用云端API。他们渴望在本地环境中部署、管理和实验自己的AI模型,无论是出于数据隐私的考量、网络环境的限制,还是纯粹对技术探索的热爱。构建一个稳定、易用且可扩展的本地AI平台,成为了一个极具吸引力的目标。本文将为你呈现一套完整的解决方案,它并非简单的工具堆砌,而是一个经过精心设计的、以1Panel为控制中枢,Ollama为模型引擎,Open WebUI为交互前端的集成化平台。我们将深入探讨如何将它们无缝衔接,并重点解锁通过插件系统集成如Gemini等第三方模型的高级玩法,让你在本地也能拥有媲美云端服务的AI应用体验。 1. 平台基石:1Panel与OpenResty的部署与配置 构建任何复杂应用,一个稳定且管理便捷的基础环境是首要前提。1Panel作为一个现代化的Linux服务器运维管理面板,以其直观的Web界面和容器化应用管理能力,极大地简化了服务器运维工作。而OpenResty,作为Nginx的增强版本,集成了LuaJIT,为

By Ne0inhk