在 Java 项目交付时,将编译产物及全部第三方依赖打包成单一可执行文件,是简化部署的常见做法。Maven Shade 插件不同于 assembly,它能在字节码层面合并依赖并重新定位类路径,从而避免传统依赖拷贝带来的 CLASSPATH 混乱。理解其配置结构,是生成稳定 Fat JAR 的前提。

Shade 插件的基础配置结构
Maven Shade 插件需要在 pom.xml 的 build/plugins 节点中声明,并通过 execution 绑定到 package 阶段。最核心的任务是告诉插件哪些依赖要打进去、入口类是谁,以及如何处理依赖中的重复资源。
一个最小可运行的配置通常包含 manifest 的 Main-Class 指定,以及可选的 artifactSet 过滤。如果遗漏 Main-Class,生成的 JAR 虽然包含全部类,但系统无法识别启动点,运行 java -jar 时会报找不到主清单属性。下面给出基础模板:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.demo.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
上述配置中,ManifestResourceTransformer 负责把 mainClass 写入 META-INF/MANIFEST.MF。执行 mvn package 后,target 下会同时出现原始瘦包和带 shaded 后缀的胖包,后者即可直接执行。
需要注意,Shade 默认不会覆盖已存在的相同名称资源,比如多个依赖都包含 META-INF/spring.factories,这时必须用 ServicesResourceTransformer 或 AppendingTransformer 来合并,否则部分框架的自动配置会失效。
处理依赖冲突与资源合并
当项目引入多个库,而这些库又依赖同一组件的不同版本,或者包含同路径的配置文件时,直接打包会导致文件互相覆盖。Shade 提供了 transformer 机制来解决这类问题。
以 Java SPI 机制为例,各依赖可能在 META-INF/services 下声明同一个接口的不同实现类。使用 ServicesResourceTransformer 可以把它们拼接成一份完整服务文件,而不是随机保留其中一个。配置方式如下:
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.demo.App</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
对于普通的文本配置,例如 license 文件或自定义的 properties,可以使用 AppendingTransformer 并指定资源路径,让内容纵向追加。这样不会丢失任何一方的配置信息,也避免人工干预。
另一个常见坑是签名文件。若依赖带有 META-INF/*.SF、*.DSA 等签名,合并后 JVM 校验会失败。可通过 filters 排除这些文件:
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
使用 relocate 解决包名冲突
某些情况下,两个依赖各自内嵌了不同版本的同一工具库,且都暴露了公开 API。直接合并会出现类重复定义,甚至引发 NoSuchMethodError。Shade 的 relocate 功能可以对指定包路径做字节码重写,相当于给类搬个家。
例如把依赖中的 org.apache.commons 重定位到 com.demo.shaded.commons,就能和另一份原生 commons 共存。配置如下:
<configuration>
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>com.demo.shaded.commons</shadedPattern>
</relocation>
</relocations>
</configuration>
relocate 会在打包时扫描所有相关 class 文件,把内部的 import 和类名引用同步改写,因此对运行时代码透明。缺点是会增加包体积,并且调试时堆栈中的类名是重写后的,需要对照映射。
建议只对确实冲突的包做 relocate,而不是全量重定位,否则后续排查问题成本较高。配合 artifactSet 精确圈定要打进来的依赖,能进一步减小 Fat JAR 的大小。
完整示例与验证方式
综合前面的要点,一个较完整的 Shade 配置应当包含主类、服务合并、签名排除和必要的重定位。下面给出可直接套用的片段:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<artifactSet>
<includes>
<include>com.google.guava:guava</include>
<include>org.slf4j:slf4j-api</include>
</includes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.demo.App</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
打包完成后,进入 target 目录,执行 java -jar 项目名称-版本-shaded.jar。若控制台正常打印预期日志,说明配置生效。也可用 jar tf 命令列出内容,确认依赖类都在根包下而非 BOOT-INF 这类子目录中。
当项目后续引入新依赖,记得检查是否带来了同名资源或签名文件。保持配置中的过滤器与 transformer 与实际依赖同步,才能保证每次构建出的 Fat JAR 都可执行且行为一致。
Maven_Shade Fat_JAR executable_jar修改时间:2026-08-07 14:54:57