Maven Shading 的原理
Maven Shading 是一种在构建项目时,用来解决 Java 项目中“依赖冲突”问题的技术。当你的项目或者你项目所依赖的库,同时引用了另一个库的不同版本时,就可能出现依赖冲突,这会导致程序在运行时出现 ClassNotFoundException 或 NoSuchMethodError 等错误。
Maven Shading 的核心原理是通过 maven-shade-plugin 插件,将项目依赖的第三方库的包名进行重命名(也称为“重定位”或 "relocate"),然后将重命名后的类文件和项目本身的类文件一起打包到一个最终的 "uber-jar" (或 "fat jar") 中。
具体过程
-
识别冲突
假设你的项目my-app依赖了library-a和library-b:library-a依赖common-lib的 1.0 版本。library-b依赖common-lib的 2.0 版本。- 1.0 和 2.0 版本不兼容。
- 此时,Maven 的类路径中会包含两个版本的
common-lib,这会导致不可预测的行为。
-
配置 Shading
你可以在my-app项目的pom.xml文件中配置maven-shade-plugin。你可以指定对library-a所依赖的common-lib-1.0进行 shading。 -
重定位包名 (Relocation)
在 Maven 构建过程中,maven-shade-plugin会:- 解压
common-lib-1.0.jar。 - 将其中的所有类的包名进行修改。例如,将原来的包名
com.example.common修改为my.app.shaded.com.example.common。 - 同时,插件会修改
library-a的代码,让它调用重命名后的my.app.shaded.com.example.common包下的类。
- 解压
-
打包
最后,maven-shade-plugin会将my-app自己的代码、library-b、common-lib-2.0以及被重命名后的common-lib-1.0一起打包到一个单独的、可执行的 JAR 文件中。
最终效果
通过这个过程,你的项目 my-app 内部就有了两个版本的 common-lib,但它们位于不同的包名下,因此不会产生冲突:
library-a使用的是你为其“私有化”的common-lib-1.0。library-b则使用正常的common-lib-2.0。
这就好像你把一个依赖“藏”在了你的项目里,使其与外部环境隔离。
总结
Maven Shading 的原理就是通过重命名和重新打包,将一个或多个依赖项“内嵌”并“隔离”在你的项目中,从而从根本上解决了类路径冲突的问题。
许多开源项目(例如 Apache Kyuubi)都使用这种技术来保证其在复杂的大数据环境中能够稳定运行,避免与用户环境中已有的库版本产生冲突。
Maven 依赖优先级
Maven 依赖调解(Dependency Mediation)决定了在依赖关系图中出现多个同一依赖的不同版本时,应该使用哪个版本。Maven 采用"最短路径优先"和"第一声明优先"的原则来解决版本冲突。
-
最短路径优先 (Shortest Path Wins)
- 这是最主要的原则。Maven 会优先选择在依赖树中路径最短的版本。
- 例子:
- 你的项目 A 依赖 B,B 依赖 C 2.0 (
A -> B -> C:2.0)。 - 同时,你的项目 A 直接依赖 C 1.0 (
A -> C:1.0)。 - 因为
A -> C:1.0的路径(长度为1)比A -> B -> C:2.0的路径(长度为2)更短,所以 Maven 会选择 C 1.0 版本。
- 你的项目 A 依赖 B,B 依赖 C 2.0 (
-
第一声明优先 (First Declaration Wins)
- 如果两个依赖的路径长度相同,那么 Maven 会选择在
pom.xml中最先声明的那个依赖所对应的版本。 - 例子:
- 你的项目 A 依赖 B,B 依赖 D 1.0 (
A -> B -> D:1.0)。 - 你的项目 A 也依赖 C,C 依赖 D 2.0 (
A -> C -> D:2.0)。 - 这两个路径
A -> B -> D和A -> C -> D的长度都是 2。 - 在这种情况下,如果在你的
pom.xml中,对B的依赖声明在对C的依赖声明之前,那么 Maven 会选择 D 1.0 版本。反之,则会选择 D 2.0 版本。
- 你的项目 A 依赖 B,B 依赖 D 1.0 (
- 如果两个依赖的路径长度相同,那么 Maven 会选择在
可以通过在项目的 pom.xml 文件中添加 <dependencyManagement> 标签来显式地声明你想要使用的依赖版本。在 <dependencyManagement> 中指定的版本会覆盖所有传递性依赖(transitive dependencies)中的版本,无论其路径长短或声明顺序。这是推荐的最佳实践,可以确保项目构建的稳定性和可预测性。
每个 pom.xml 对应什么产物
一个 pom.xml 文件定义了一个 Maven 项目(或模块)的构建方式,但它最终生成的产物(artifact)不一定是一个 JAR 文件。
-
pom.xml是项目的核心描述文件:它包含了项目的基本信息、依赖关系、插件、构建配置等。 - 产物类型由
<packaging>决定:在pom.xml中,你可以通过<packaging>标签来指定项目的打包类型。-
jar (默认值): 生成一个 JAR (Java Archive) 文件。这是最常见的类型。 -
war: 生成一个 WAR (Web Application Archive) 文件,用于部署 Web 应用。 -
ear: 生成一个 EAR (Enterprise Application Archive) 文件,用于部署企业级应用。 -
pom: 这种类型的项目本身不产生构建产物,它通常用作一个父 POM,来管理子模块的依赖和配置,或者作为一个聚合器(aggregator)来一次性构建多个模块。 - 其他类型: 还有许多其他打包类型,通常由特定的插件提供,例如
maven-bundle-plugin用于生成 OSGi bundles。
-
此外,通过配置构建插件(如 maven-assembly-plugin 或 maven-shade-plugin),一个 pom.xml 也可以配置生成多个不同的产物,例如一个普通的 JAR 和一个包含所有依赖的 "uber-jar"。
总结一下:
- 一个
pom.xml文件定义了一个 Maven 项目。 - 这个项目的主要产物类型由
<packaging>标签定义,默认为jar,但也可以是war,pom等。 - 因此,一个
pom.xml通常对应一个主要产物,但不一定总是 JAR 文件。
828

被折叠的 条评论
为什么被折叠?



