Java项目打包实战:从Maven基础到Spring Boot与Docker分层优化

发布时间:2026/8/15 13:24:09
Java项目打包实战:从Maven基础到Spring Boot与Docker分层优化 1. 项目概述为什么我们需要“打包大全”在Java开发的世界里Maven几乎是项目构建和依赖管理的代名词。而将我们的心血——一个完整的Java应用——打包成一个可执行的JAR文件是交付给用户或部署到生产环境前的最后一步也是最关键的一步。这听起来简单不就是打个包吗但真正踩过坑的开发者都知道这里面的门道可太多了。你可能遇到过这些问题打出来的JAR包在本地java -jar运行得好好的一到服务器就报ClassNotFoundException或者依赖了上百个第三方库打出来的“胖JAR”体积巨大每次上传部署都慢得让人心焦又或者你的应用需要读取src/main/resources下的配置文件打包后却怎么也找不到路径了。这些问题根源都在于打包方式的选择和配置上。网上关于Maven打包的文章很多但往往只讲一两种方法或者只给配置代码不说原理遇到稍微复杂点的场景就抓瞎。这就是我写这篇“大全”的初衷。我将结合自己十多年在大小项目中摸爬滚打的经验为你系统梳理从最基础的maven-jar-plugin到最流行的maven-shade-plugin再到追求极致体验的spring-boot-maven-plugin以及面向未来的分层打包等所有主流方法。我会详细拆解每种方法的原理、适用场景、详细配置以及那些官方文档里不会写的“坑”。目标只有一个让你看完之后面对任何打包需求都能游刃有余地选出最合适的那把“瑞士军刀”。2. 核心打包策略深度解析打包一个可执行JAR核心要解决三个问题依赖管理、主类设定和资源处理。不同的插件以不同的哲学来处理这些问题从而衍生出不同的打包策略。2.1 基础策略标准JAR与依赖分离这是最“Maven”的方式使用自带的maven-jar-plugin。它的哲学是“职责分离”生成的projectname-version.jar只包含你项目自己编译的类文件和资源所有第三方依赖都通过maven-dependency-plugin复制到独立的lib/目录下。为什么选择它这种方式的优势在于清晰和高效。在持续集成/持续部署CI/CD流水线中如果你的依赖不常变化那么只需要重复构建和传输很小的应用JAR包依赖库可以被缓存或共享极大地加快了构建和部署速度。它也便于依赖的单独管理和更新。它的致命短板是什么可执行性差。你无法直接通过java -jar app.jar来运行它因为ClassLoader在标准JAR包中找不到依赖的类。你必须手动指定类路径classpath例如java -cp “app.jar:lib/*” com.yourcompany.MainClass这在生产环境的启动脚本中显得笨拙且容易出错。因此这种策略更适合作为库Library发布或者需要与其他工具如容器深度集成的场景。2.2 经典策略构建“胖JAR”或“超级JAR”为了解决依赖分离带来的启动不便“胖JAR”Fat JAR或“超级JAR”Uber JAR的概念应运而生。其核心思想是将所有依赖的类文件、资源都解压后重新打包进同一个JAR文件中。这样一个JAR包就包含了运行所需的一切。实现这一思想的两位主力干将是maven-assembly-plugin 这是一个非常通用和强大的打包插件不仅能打JAR还能打ZIP、TAR等。通过预定义的或自定义的assembly descriptor装配描述符它可以精确控制将哪些文件项目代码、依赖、资源、脚本等以何种结构打包到最终产物中。功能强大但配置相对繁琐。maven-shade-plugin 可以看作是assembly插件在打“胖JAR”领域的专业化、优化版本。它专为打包可执行JAR设计不仅合并依赖还提供了两个杀手级功能重命名依赖包中的类解决不同依赖库中同名类文件的冲突以及处理资源文件中的特定内容比如合并所有依赖中的META-INF/services/文件。对于需要解决依赖冲突的复杂项目shade插件几乎是首选。注意“胖JAR”并非银弹。它会导致JAR包体积庞大每次更新哪怕只改一行代码也需要上传整个巨大的JAR。在微服务架构和容器化部署中这会影响部署效率。此外如果多个服务使用大量相同的依赖如Spring框架每个服务的“胖JAR”都会包含这些依赖的副本造成存储和内存的浪费。2.3 现代策略Spring Boot的“可执行JAR”spring-boot-maven-plugin重新定义了可执行JAR。它打的包不是一个标准的JAR而是一个“JAR中的JAR”或者说是一种特殊的归档格式。它的工作原理很巧妙它创建一个“胖JAR”但内部结构是分层的。外层是应用的加载器org.springframework.boot.loader.JarLauncher和你的应用类。内层在BOOT-INF/lib/目录下以JAR文件的形式原封不动地包含了所有依赖。内层在BOOT-INF/classes/目录下是你的应用资源。这样做的好处是什么直接可执行 因为有了自定义的JarLauncher它可以加载BOOT-INF/lib/下的嵌套JAR所以直接java -jar就能运行。支持依赖分层优化Docker镜像 这是它的王牌功能。通过配置可以将依赖分为多个层如Spring Boot依赖层、快照依赖层、应用层。在构建Docker镜像时不常变化的依赖层可以被缓存只有经常变动的应用层需要重建和传输这使得Docker镜像的构建和推送速度极快。与Spring Boot生态无缝集成 自动处理配置文件加载、Actuator端点、Profile激活等Spring Boot特性。如果你的项目是基于Spring Boot的那么spring-boot-maven-plugin就是你的不二之选。即使不是Spring Boot项目你也可以利用它的repackage目标来获得一个结构良好的可执行JAR。2.4 前沿策略Docker时代的分层打包与JLink在云原生和容器化成为主流的今天打包策略也在进化。利用Docker多阶段构建和分层 如前所述spring-boot-maven-plugin的分层特性可以与Dockerfile的多阶段构建完美结合。首先在“构建阶段”用Maven打包并将分层信息layers.idx和每层文件导出然后在“运行阶段”的Docker镜像中按层拷贝文件。依赖层只要没有变化就会命中Docker构建缓存大大提升效率。使用jlink创建定制化运行时 对于Java 9及以上版本的项目如果你的依赖模块化清晰可以考虑使用jlink工具。它允许你基于JDK模块只打包你的应用及其真正需要的JDK模块生成一个极小的、定制化的Java运行时镜像。这个镜像可以直接包含你的应用生成一个完全自包含的、无需安装系统级JDK的可执行文件。这能极大减少容器镜像的体积从几百MB的完整JDK缩减到几十MB是追求极致效率和安全的场景下的终极方案但前提是你的项目和依赖必须支持JPMSJava Platform Module System。3. 五大打包方法实战详解理论说再多不如一行配置。下面我将逐一演示每种方法的详细配置和操作并附上我踩过的坑和总结的技巧。3.1 方法一使用 maven-jar-plugin maven-dependency-plugin依赖外置这是最基础、最标准的Maven方式。pom.xml配置如下build plugins !-- 1. 配置主类 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.3.0/version configuration archive manifest addClasspathtrue/addClasspath !-- 在MANIFEST.MF中生成Class-Path -- classpathPrefixlib//classpathPrefix !-- 指定依赖库的相对路径 -- mainClasscom.example.myapp.Main/mainClass !-- 指定主类 -- /manifest /archive /configuration /plugin !-- 2. 将依赖复制到指定目录 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-dependency-plugin/artifactId version3.6.0/version executions execution idcopy-dependencies/id phasepackage/phase !-- 绑定到package阶段 -- goals goalcopy-dependencies/goal /goals configuration outputDirectory${project.build.directory}/lib/outputDirectory !-- 复制到target/lib -- overWriteReleasesfalse/overWriteReleases overWriteSnapshotsfalse/overWriteSnapshots overWriteIfNewertrue/overWriteIfNewer /configuration /execution /executions /plugin /plugins /build执行与运行运行mvn clean package。查看target/目录会生成myapp-1.0.jar和lib/文件夹里面是所有依赖的JAR。运行应用有两种方式指定类路径运行java -cp “target/myapp-1.0.jar:target/lib/*” com.example.myapp.Main利用MANIFEST中的Class-Path更优雅确保JAR包和lib/目录的相对位置不变然后java -jar target/myapp-1.0.jar。这是因为我们在maven-jar-plugin里配置了addClasspathtrue/addClasspath生成的MANIFEST.MF文件里会有一行Class-Path: lib/dependency1.jar lib/dependency2.jar ...JVM会自动去这些路径下寻找类。实操心得classpathPrefix的值lib/是相对于生成的JAR包所在目录的。如果你打算把JAR包和lib文件夹一起移动到别处必须保持它们在同一目录下且lib文件夹名称不变。这种方式非常适合与服务器上的共享类库结合。比如你可以把常用的、版本稳定的依赖如Log4j、Guava放在服务器的某个公共路径如/usr/share/java/lib/然后在classpathPrefix或启动脚本中指向这个绝对路径这样多个应用可以共享同一份依赖节省空间。3.2 方法二使用 maven-assembly-plugin定制化打包当你需要将应用、依赖、配置文件、启动脚本甚至文档打包成一个用于分发的ZIP或TAR包时assembly插件是神器。首先定义一个装配描述符文件src/assembly/distribution.xmlassembly xmlnshttp://maven.apache.org/ASSEMBLY/2.2.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/ASSEMBLY/2.2.0 http://maven.apache.org/xsd/assembly-2.2.0.xsd iddistribution/id formats formatzip/format !-- 也可以同时生成tar.gz -- formattar.gz/format /formats includeBaseDirectorytrue/includeBaseDirectory !-- 包含一个以artifactId-version为名的根目录 -- dependencySets dependencySet outputDirectory/lib/outputDirectory !-- 依赖放到根目录下的lib里 -- scoperuntime/scope !-- 只包含runtime范围的依赖 -- /dependencySet /dependencySets fileSets fileSet directory${project.basedir}/src/main/resources/directory outputDirectory/conf/outputDirectory !-- 配置文件放到conf目录 -- includes include*.yml/include include*.properties/include /includes /fileSet fileSet directory${project.basedir}/src/main/scripts/directory outputDirectory/bin/outputDirectory !-- 启动脚本放到bin目录 -- fileMode0755/fileMode !-- 赋予脚本可执行权限 -- /fileSet fileSet directory${project.build.directory}/directory outputDirectory//outputDirectory includes include*.jar/include !-- 主JAR包放到根目录 -- /includes /fileSet /fileSets /assembly然后在pom.xml中配置插件plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.6.0/version configuration descriptors descriptorsrc/assembly/distribution.xml/descriptor /descriptors archive manifest mainClasscom.example.myapp.Main/mainClass /manifest /archive !-- 附加后缀避免与默认的jar包重名 -- appendAssemblyIdfalse/appendAssemblyId /configuration executions execution idmake-assembly/id phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin执行与结果运行mvn clean package后在target/目录下会生成类似myapp-1.0-distribution.zip的文件。解压后你会看到一个结构清晰的分发包myapp-1.0/ ├── bin/ │ └── start.sh ├── conf/ │ ├── application.yml │ └── logback.xml ├── lib/ │ ├── dependency1.jar │ └── dependency2.jar └── myapp-1.0.jar用户拿到这个包只需要运行bin/start.sh里面包含了正确的java -cp命令即可启动非常友好。注意事项assembly插件功能强大但配置复杂容易出错。务必仔细检查fileSet的路径和包含/排除规则。对于简单的“胖JAR”需求用它有点杀鸡用牛刀。但对于需要生成包含多种文件、有特定目录结构的发布包它是无可替代的。3.3 方法三使用 maven-shade-plugin解决冲突的胖JARshade插件是构建“胖JAR”的行业标准尤其擅长处理依赖冲突。它的核心配置如下plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.5.1/version executions execution phasepackage/phase goals goalshade/goal /goals configuration createDependencyReducedPomfalse/createDependencyReducedPom !-- 通常不需要生成简化pom -- transformers !-- 合并多个依赖中的META-INF/services/下的文件对使用ServiceLoader机制的关键 -- transformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/ !-- 指定主类 -- transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.myapp.Main/mainClass /transformer !-- 合并Apache许可证等文件避免重复 -- transformer implementationorg.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer/ /transformers !-- 可选重命名冲突的包 -- relocations relocation patterncom.google.guava/pattern shadedPatterncom.example.shaded.guava/shadedPattern /relocation relocation patternorg.apache.commons.lang3/pattern shadedPatterncom.example.shaded.commons.lang3/shadedPattern /relocation /relocations !-- 过滤掉签名文件避免安全警告 -- filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin执行与结果运行mvn clean package后target/目录下会生成两个JAR一个是原始的myapp-1.0.jar另一个是带有-shaded分类器或根据配置替换了原始文件的“胖JAR”。这个胖JAR包含了所有依赖可以直接用java -jar myapp-1.0-shaded.jar运行。重定位Relocation的妙用这是shade插件解决类冲突的核武器。假设你的项目依赖了库A内嵌了Guava 18.0和库B内嵌了Guava 30.0直接打包会导致类冲突。通过上述relocations配置你可以将其中一个库或两者中的Guava类全部重命名到新的包路径下如com.example.shaded.guava。这样两个版本的Guava在运行时就被隔离了互不影响。代价是如果你在代码中直接使用了Guava的类也需要相应修改导入语句或者只重定位那些你确信不会直接使用的、纯内部依赖的库。踩坑实录资源文件合并ServicesResourceTransformer至关重要。很多库如JDBC驱动、日志实现、序列化框架通过META-INF/services/下的文件声明其SPIService Provider Interface实现。如果不合并这些文件打包后可能只有最后一个被处理的依赖的SPI配置生效导致功能异常比如找不到数据库驱动。务必在transformers中加上它。3.4 方法四使用 spring-boot-maven-pluginSpring Boot之道对于Spring Boot项目这是最简单、最强大的方式。基本配置极其简洁plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version3.1.5/version !-- 使用与你Spring Boot版本对应的插件版本 -- executions execution goals goalrepackage/goal !-- 重新打包将依赖打入 -- /goals configuration !-- 主类通常会自动从spring-boot-starter中推断也可显式指定 -- mainClasscom.example.myapp.MyApplication/mainClass !-- 启用分层支持为Docker优化做准备 -- layers enabledtrue/enabled /layers /configuration /execution /executions /plugin执行与结果运行mvn clean package后在target/目录下会生成两个JAR一个是原始的myapp-1.0.jar内容很少另一个是重新打包后的myapp-1.0.jar.original原始的瘦JAR而默认的myapp-1.0.jar则变成了Spring Boot的可执行“胖JAR”。直接运行java -jar target/myapp-1.0.jar即可。高级特性分层打包优化Docker构建启用layerstrue/layers后打包时会额外生成一个layers.idx文件定义了JAR包内部的分层结构如dependencies, spring-boot-loader, snapshot-dependencies, application。你可以使用jarmode工具来提取这些层# 查看分层信息 java -Djarmodelayertools -jar myapp-1.0.jar list # 提取各层到指定目录 java -Djarmodelayertools -jar myapp-1.0.jar extract --destination /path/to/extract结合以下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 openjdk:17-jdk-slim WORKDIR /app # 从构建阶段复制分层JAR COPY --frombuilder /app/target/myapp-*.jar app.jar # 使用jarmode提取层实际中更常用的是在构建阶段提取好再复制 # 为了清晰这里展示在运行阶段提取的简单方式生产环境建议优化 RUN java -Djarmodelayertools -jar app.jar extract # 按层拷贝依赖层变化少可以利用Docker缓存 COPY --frombuilder /app/target/dependencies/ ./ COPY --frombuilder /app/target/spring-boot-loader/ ./ COPY --frombuilder /app/target/snapshot-dependencies/ ./ COPY --frombuilder /app/target/application/ ./ ENTRYPOINT [“java”, “org.springframework.boot.loader.JarLauncher”]这样当你的应用代码变更而依赖不变时Docker在构建到COPY --frombuilder /app/target/dependencies/ ./这一层时就会直接使用缓存无需重新下载和安装依赖极大加速构建过程。3.5 方法五结合Docker多阶段构建与JLink追求极致对于非Spring Boot项目或追求极致镜像体积和启动速度的场景可以结合Docker多阶段构建和JLink。思路使用maven-dependency-plugin和maven-jar-plugin打出标准的“依赖外置”包。在第一阶段构建阶段使用jlink基于你的模块描述module-info.java创建一个只包含必要模块的定制JRE。在第二阶段运行阶段只拷贝这个定制JRE、你的应用JAR和依赖库。简化示例Dockerfile# 第一阶段用Maven构建应用并用JLink创建定制JRE FROM maven:3.8-openjdk-17 AS build WORKDIR /app COPY . . RUN mvn clean package -DskipTests # 假设项目是模块化的模块名为 com.example.myapp # 找出所有依赖的模块这里需要更复杂的脚本分析此处简化 RUN jlink --add-modules java.base,java.sql,java.desktop,com.example.myapp \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress2 \ --output /custom-jre # 第二阶段最小化运行镜像 FROM debian:bullseye-slim WORKDIR /app # 拷贝定制JRE COPY --frombuild /custom-jre /opt/jre ENV JAVA_HOME/opt/jre ENV PATH“${JAVA_HOME}/bin:${PATH}” # 拷贝应用jar和依赖lib COPY --frombuild /app/target/myapp-1.0.jar . COPY --frombuild /app/target/lib ./lib ENTRYPOINT [“java”, “-cp”, “myapp-1.0.jar:lib/*”, “com.example.myapp.Main”]通过这种方式最终的运行镜像可以非常小可能只有50MB左右因为它不包含完整的JDK只包含了应用运行必需的模块。安全补丁更新也只需要重建和替换这个定制JRE即可。4. 常见问题排查与实战技巧即使配置正确打包和运行过程中也可能遇到各种“妖魔鬼怪”。下面是我总结的一些高频问题和解决思路。4.1 类找不到ClassNotFoundException/NoClassDefFoundError这是最常见的问题根本原因都是类加载器在运行时找不到指定的类。问题表现 运行java -jar时报错ClassNotFoundException: com/example/SomeClass或NoClassDefFoundError。排查思路检查依赖是否真的被打包对于“胖JAR”用解压工具如jar tf your.jar或unzip -l your.jar查看JAR包内部在BOOT-INF/lib/Spring Boot或根目录下是否存在包含缺失类的依赖JAR。对于“依赖外置”方式检查lib/目录下是否有对应JAR。检查依赖作用域Scope在pom.xml中scopeprovided/scope的依赖如Servlet API在运行时由容器提供和scopetest/scope的依赖不会被包含到打包的依赖中。确保运行时需要的依赖是scopecompile/scope默认或scoperuntime/scope。检查MANIFEST.MF文件对于依赖外置的JAR检查MANIFEST.MF中的Class-Path属性是否完整、路径是否正确。路径分隔符在Unix系统是冒号:在Windows是分号;。依赖冲突导致类被错误覆盖如果使用了shade插件但没有正确配置重定位或者多个依赖包含了不同版本的同名类可能会加载到错误的版本。使用mvn dependency:tree查看依赖树排查冲突。对于shade插件考虑启用重定位。4.2 资源文件找不到问题表现 代码中使用getClass().getResource(“/config.properties”)或Thread.currentThread().getContextClassLoader().getResourceAsStream(“template.html”)在IDE中运行正常但打包后返回null。根本原因 资源文件的路径在打包后发生了变化。标准Maven项目src/main/resources下的文件在打包后会位于JAR包的根目录。但在“胖JAR”中你的资源文件可能被“淹没”在无数个依赖JAR里或者被插件处理时移动了位置。解决方案统一使用ClassLoader获取资源优先使用Thread.currentThread().getContextClassLoader().getResourceAsStream()它的搜索范围更广。检查插件配置对于assembly或shade插件检查fileSet或资源转换器的配置确保资源文件被包含并放在了预期的路径。Spring Boot的特殊处理Spring Boot有自己的一套资源加载逻辑ResourceLoader通常能很好地处理嵌套JAR中的资源。如果仍有问题可以尝试使用ResourceUtils或new ClassPathResource(“file.txt”)。4.3 JAR包太大或构建太慢问题“胖JAR”动辄几十MB甚至上百MB每次mvn package都要花很长时间下载和打包依赖。优化策略依赖分析去芜存菁定期运行mvn dependency:analyze检查Unused declared dependencies移除真正用不到的依赖。检查Used undeclared dependencies确认是否是必需的。使用轻量级替代库例如用OkHttp替代老旧的HttpClient用SLF4JLogback替代Log4j 1.x。启用Docker分层Spring Boot如前所述这虽然不减少JAR本身大小但能极大优化Docker镜像的构建和传输效率。考虑非“胖JAR”方案如果环境可控如容器化回归“依赖外置”模式结合Docker的卷挂载或分层可能更高效。使用Maven离线模式与本地仓库缓存在CI/CD服务器上维护好本地仓库缓存并使用mvn -ooffline模式进行打包可以跳过网络下载。4.4 版本冲突与NoSuchMethodError问题表现 程序运行时抛出NoSuchMethodError或NoSuchFieldError但编译时一切正常。根本原因 依赖的传递性导致了同一个库有多个版本被引入而Maven默认选择了其中一个版本遵循“最近定义”和“最短路径”原则但这个版本可能缺少你的代码所调用的方法。排查与解决锁定依赖版本在顶级pom.xml的dependencyManagement中显式声明常用库的版本统一所有子模块的版本。使用mvn dependency:tree -Dverbose查看详细的依赖树找出冲突的库和它们的不同版本路径。排除特定传递依赖在引入依赖时使用exclusions标签排除掉不需要的传递性依赖。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency终极武器Shade插件重定位如果冲突发生在你无法直接控制的第三方库之间使用shade插件的重定位功能将其中一个库的包路径整体迁移走。4.5 签名与安全相关警告问题 打包时或运行“胖JAR”时控制台出现“Invalid signature file digest for Manifest main attributes”或关于META-INF/*.SF的警告。原因 有些依赖JAR是经过数字签名的如某些旧版本的BouncyCastle。当“胖JAR”插件将这些签名文件一并打包进去时会破坏签名导致JVM验证失败。解决 在shade或spring-boot-maven-plugin的配置中过滤掉这些签名文件。!-- 在shade插件中 -- filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters!-- 在spring-boot-maven-plugin中 -- configuration excludes exclude groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15on/artifactId /exclude /excludes !-- 或者更通用的过滤 -- excludeGroupIdsorg.bouncycastle/excludeGroupIds /configuration5. 如何选择最适合你的打包方式面对这么多方法到底该怎么选我总结了一个决策流程图和场景建议你可以对号入座决策流程你的项目是Spring Boot吗是- 毫不犹豫选择spring-boot-maven-plugin。如果考虑容器化部署务必开启layerstrue/layers。否- 进入第2步。你需要解决复杂的依赖冲突吗或者你需要一个包含所有依赖的单一可执行JAR是且需要解决冲突- 选择maven-shade-plugin并配置好重定位。是但冲突不复杂或可以规避-maven-shade-plugin或maven-assembly-plugin后者配置更灵活都可以。否我可以接受依赖外置- 进入第3步。你的交付物需要包含启动脚本、配置文件、文档等形成一个完整的发布包吗是- 选择maven-assembly-plugin定制你的分发包结构。否我只需要一个可运行的JAR- 回到第2步的“是”分支选择shade或assembly打胖JAR。否我只需要一个库LibraryJAR- 使用默认的maven-jar-plugin即可。你对启动速度和镜像体积有极致要求且项目是模块化的吗是- 深入研究JLink Docker多阶段构建这是未来的方向但前期有一定复杂度。场景化建议表场景推荐方案关键理由传统单体Spring Boot应用spring-boot-maven-plugin(开启分层)开箱即用生态支持好分层优化Docker构建。微服务非Spring Bootmaven-shade-plugin或依赖外置Docker优化服务独立部署胖JAR简单依赖外置更适合CI/CD流水线优化。提供二进制的客户端工具maven-shade-plugin用户只需一个JAR文件双击或命令行直接运行体验最好。企业内网复杂依赖环境maven-shade-plugin(配合重定位)内网库版本混乱重定位能彻底隔离依赖避免冲突。需要生成包含脚本、配置的安装包maven-assembly-plugin灵活定义打包内容和结构生成ZIP/TAR等格式方便分发给运维或用户。作为公共库发布到Maven中央仓库默认的maven-jar-plugin标准格式依赖由使用者管理避免传递依赖污染。对安全、体积有严苛要求的容器化应用JLink 多阶段Docker构建生成最小化运行时镜像减少攻击面提升启动速度。打包不是一件一劳永逸的事情。随着项目演进、依赖更新、部署环境变化你可能需要重新评估和调整打包策略。我的经验是在项目初期选择一种简单可靠的方式如Spring Boot插件或Shade插件快速搭建起部署流水线。当遇到性能瓶颈、依赖冲突或新的部署需求时再根据上述指南进行优化和调整。理解每种工具背后的原理才能让你在遇到问题时能快速定位并找到最适合的解决方案。

相关新闻