揭秘MavenShading和Maven依赖优先级:解决依赖冲突的终极方案

Maven Shading 的原理

​Maven Shading​​ 是一种在构建项目时,用来解决 Java 项目中“依赖冲突”问题的技术。当你的项目或者你项目所依赖的库,同时引用了另一个库的不同版本时,就可能出现依赖冲突,这会导致程序在运行时出现 ClassNotFoundExceptionNoSuchMethodError 等错误。

Maven Shading 的核心原理是通过 maven-shade-plugin 插件,将项目依赖的第三方库的包名进行重命名(也称为“重定位”或 "relocate"),然后将重命名后的类文件和项目本身的类文件一起打包到一个最终的 "uber-jar" (或 "fat jar") 中。

具体过程

  1. ​识别冲突​
    假设你的项目 my-app 依赖了 library-alibrary-b

    • library-a 依赖 common-lib 的 1.0 版本。
    • library-b 依赖 common-lib 的 2.0 版本。
    • 1.0 和 2.0 版本不兼容。
    • 此时,Maven 的类路径中会包含两个版本的 common-lib,这会导致不可预测的行为。
  2. ​配置 Shading​
    你可以在 my-app 项目的 pom.xml 文件中配置 maven-shade-plugin。你可以指定对 library-a 所依赖的 common-lib-1.0 进行 shading。

  3. ​重定位包名 (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 包下的类。
  4. ​打包​
    最后,maven-shade-plugin 会将 my-app 自己的代码、library-bcommon-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 采用"最短路径优先"和"第一声明优先"的原则来解决版本冲突。

  1. ​最短路径优先 (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​​ 版本。
  2. ​第一声明优先 (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 -> DA -> C -> D 的长度都是 2。
      • 在这种情况下,如果在你的 pom.xml 中,对 B 的依赖声明在对 C 的依赖声明之前,那么 Maven 会选择 ​​D 1.0​​ 版本。反之,则会选择 D 2.0 版本。

可以通过在项目的 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-pluginmaven-shade-plugin),一个 pom.xml 也可以配置生成多个不同的产物,例如一个普通的 JAR 和一个包含所有依赖的 "uber-jar"。

​总结一下:​

  • 一个 pom.xml 文件定义了一个 Maven 项目。
  • 这个项目的主要产物类型由 <packaging> 标签定义,默认为 jar,但也可以是 war, pom 等。
  • 因此,一个 pom.xml 通常对应一个主要产物,但不一定总是 JAR 文件。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值