Java项目开发中,随着依赖数量增加,不同版本的同一依赖被引入的情况十分常见,这时候就容易出现类加载异常、方法不存在等问题,理解Java类加载器的工作机制和Shaded Jar的处理逻辑,是解决这类问题的关键。

Java类加载器核心原理
Java类加载器负责将class文件加载到JVM中,其默认遵循双亲委派模型,该模型的核心逻辑是:当一个类加载器收到类加载请求时,首先不会自己尝试加载,而是把请求委派给父加载器完成,只有当父加载器无法完成加载时,子加载器才会尝试自己加载。
常见的类加载器层级
- Bootstrap ClassLoader:最顶层的类加载器,负责加载JVM核心类库,比如<JAVA_HOME>/lib目录下的类,由C++实现,在Java中无法直接获取引用。
- Extension ClassLoader:扩展类加载器,负责加载<JAVA_HOME>/lib/ext目录下的类,或者系统变量java.ext.dirs指定的路径下的类。
- Application ClassLoader:应用程序类加载器,负责加载用户类路径(classpath)下的所有类,是程序中默认的类加载器。
- 自定义类加载器:开发者可以继承
ClassLoader类实现自定义的类加载逻辑,比如热部署、模块化加载等场景会用到。
双亲委派模型的问题
双亲委派模型虽然保证了核心类库的安全性,但是也带来了依赖冲突的隐患。比如项目同时引入了A依赖的1.0版本和B依赖的2.0版本,两个版本都包含同一个全限定名的类,按照双亲委派模型,只会加载其中一个版本的类,另一个版本的类就不会被加载,这时候如果代码调用了高版本才有的方法,就会出现NoSuchMethodError异常。
依赖冲突的产生原因
依赖冲突的本质是同一个全限定名的类在classpath中存在多个版本,而类加载器只能加载其中一个。常见的冲突场景有以下几种:
- 直接依赖引入不同版本的同一依赖,比如项目同时依赖了
com.google.guava:guava:20.0和com.google.guava:guava:30.0-jre。 - 传递依赖引入冲突版本,比如依赖A依赖guava20.0,依赖B依赖guava30.0,两个版本都会被传递到项目中。
- 不同groupId但相同全限定名类的情况,比如某些库会修改其他库的类重新打包,导致类名相同但实现不同。
Shaded Jar的解决方案
Shaded Jar是通过重定位依赖类的包名,将依赖的类打包到当前Jar中,同时修改类的全限定名,避免和其他版本的同一依赖产生冲突。常用的工具是Maven的maven-shade-plugin插件。
maven-shade-plugin的基本使用
在项目的pom.xml中添加插件配置,就可以实现依赖的重定位和打包:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- 配置需要重定位的依赖 -->
<relocations>
<relocation>
<pattern>com.google.guava</pattern>
<shadedPattern>com.example.shaded.guava</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
上面的配置会将com.google.guava包下的所有类重定位到com.example.shaded.guava包下,这样即使项目中其他依赖引入了不同版本的guava,也不会产生冲突,因为全限定名已经不同了。
Shaded Jar的优缺点
| 优点 | 缺点 |
|---|---|
| 彻底解决依赖版本冲突问题,不需要处理复杂的依赖排除逻辑 | 会增加Jar包的体积,因为会把依赖的类全部打包进去 |
| 可以自定义需要打包的依赖,避免传递依赖带来的冗余 | 如果依赖的类有反射相关的逻辑,重定位包名后可能会导致反射失败 |
| 打包后的Jar可以直接运行,不需要额外依赖 | 多个Shaded Jar如果重定位到相同的包名,还是会产生冲突 |
实际场景中的使用建议
在实际开发中,可以根据场景选择是否使用Shaded Jar:
- 如果是开发基础工具包、SDK,建议优先使用Shaded Jar,避免和用户项目的依赖产生冲突。
- 如果是普通业务项目,优先通过Maven的依赖排除(
exclusions)解决冲突,减少Jar包体积。 - 使用Shaded Jar时,重定位的包名尽量带上项目独有的前缀,比如
com.公司名.项目名.shaded.原包名,避免和其他Shaded Jar的包名冲突。
排查依赖冲突时,可以使用mvn dependency:tree命令查看依赖树,找到冲突的依赖版本,再结合类加载器的逻辑判断冲突原因,最后选择合适的解决方案。
Java类加载器Shaded_Jar依赖冲突版本管理修改时间:2026-07-09 14:33:31