用Maven处理依赖地狱的系统方法

我正在努力解决如何处理jar依赖地狱的问题。 我有一个使用一些aws sdk的Maven-IntelliJ Scala项目。 最近添加kinesis sdk引入了不兼容的Jackson版本。

我的问题是 :我如何系统地解决Jar地狱的问题?

我理解类加载器以及maven如何在重复的Jars之间选择,但我仍然对解决问题的实际步骤感到茫然。

我目前的尝试是基于反复试验,我在jackson的例子中概述了这里:

  • 首先,我在Jackson数据绑定ObjectMapper类上看到了实际的exception,在本例中是NoSuchMethodError。 然后,我查看Jackson文档,了解添加或删除方法的时间。 这通常非常繁琐,因为我手动检查每个版本的api文档(问题1:有更好的方法吗?)
  • 然后,我使用mvn dependency:tree来确定我实际使用的Jackson版本(问题2:是否有一种自动方式询问maven正在使用哪个版本的jar,而不是梳理树输出?)
  • 最后,我在添加Kinesis SDK之前比较mvn dependency:tree输出,之后,检测mvn dependency:tree输出的差异,并希望看看Jackson版本是否发生了变化。 (问题3:当发生依赖性解析时,maven如何使用着色jar中的库?与其他任何一个相同?)

最后,在比较树输出之后,我尝试在POM中明确添加最新的Jackson工作版本,以触发maven依赖关系解析链中的优先级。 如果最新版本不起作用,我会添加下一个最新的lib,依此类推。

整个过程非常繁琐。 除了我提出的具体问题,我也很好奇其他人对这个问题的系统方法。 有没有人有他们使用的资源?

然后,我查看Jackson文档,了解添加或删除方法的时间。 这通常非常繁琐,因为我手动检查每个版本的api文档(问题1:有更好的方法吗?)

为了检查API(破坏)兼容性,有几种工具可以自动分析jar子并为您提供正确的信息。 从这个 Stack Overflowpost中可以看到一些方便的工具。
JAPICC似乎相当不错。

然后,我使用mvn dependency:tree来确定我实际使用的Jackson版本(问题2:是否有一种自动方式询问maven正在使用哪个版本的jar,而不是梳理树输出?)

maven-dependency-tree绝对是可行的方法,但您可以从范围开始过滤掉,只能获得您实际需要的内容,使用其includes选项如下:

 mvn dependency:tree -Dincludes= 

注意:您还可以使用表单groupId:artifactId:type:version或使用通配符(如*:artifactIdincludes选项提供更多信息。

这似乎是一个小小的暗示,但在具有许多依赖性的大型项目中,缩小其输出量是非常有帮助的。 通常,只要groupId应该足够作为filter, *:artifactId可能是最快的,但如果您正在寻找特定的依赖项。

如果您对依赖项列表 (不是树)感兴趣,也按字母顺序排序(在许多情况下非常方便),那么以下内容也可能有所帮助:

 mvn dependency:list -Dsort=true -DincludeGroupIds=groupId 

问题3:当依赖解析发生时,maven如何在着色jar中使用库? 和其他任何一样?

通过阴影jar你可能意味着:

  • 肥胖的jar子 ,它也将其他jar子带入类路径。 在这种情况下,它们被视为一个依赖项, Maven Dependency Mediation的一个单元,其内容将成为项目类路径的一部分。 通常,您不应该将fat-jar作为依赖项的一部分,因为您无法控制它带来的打包库。
  • 带有阴影(重命名)包的jar子 。 在这种情况下 – 再次 – 就Maven依赖中介而言,没有任何控制权:它是一个单位,一个jar,基于其GAVC(GroupId,ArtifactId,Version,Classifier),使其独一无二。 它的内容然后被添加到项目类路径中(根据依赖范围 ,但是由于它的包被重命名,你可能难以处理冲突。再次,你不应该将包重命名为项目依赖项的一部分(但通常你不可能知道)。

有没有人有他们使用的资源?

通常,您应该很好地理解Maven如何处理依赖关系并使用它提供的资源(其工具和机制)。 以下一些要点:

  • dependencyManagement绝对是本主题的切入点 :在这里你可以处理Maven依赖中介,影响它对传递依赖的决定,它们的版本,它们的范围。 重要的一点是:您添加到dependencyManagement内容不会自动添加为依赖项。 dependencyManagement只有在项目的某个依赖项(如pom.xml文件中声明或通过传递依赖项)与其中一个条目匹配时才会被考虑在内,否则将被忽略。 它是pom.xml的一个重要部分,因为它有助于管理依赖关系及其传递图,这就是为什么经常在父poms中使用:你只想处理一个并且以集中的方式处理你想要的版本,例如log4j在所有Maven项目中使用,您在公共/共享父pom及其dependencyManagement声明它,并确保它将被这样使用。 集中化意味着更好的治理和更好的维护。
  • dependency部分对于声明依赖项非常重要 :通常,您应该在此处仅声明所需的直接依赖项。 一个好的重击规则是:在这里声明为compile (默认)范围,只在代码中实际用作import语句(但是你经常需要超越它,例如,运行时需要的JDBC驱动程序,并且从未在代码中引用过,然后它将在runtime范围内)。 还要记住:声明的顺序很重要:第一个声明的依赖项在与传递依赖项发生冲突的情况下获胜,因此通过重新声明一个依赖项,您可以有效地影响依赖项中介。
  • 不要滥用依赖项中的exclusions来处理传递依赖项:如果可以,请使用dependencyManagementdependencyManagement顺序。 滥用exclusions使维护变得更加困难,只有在您真正需要时才使用它。 此外,在添加exclusions项时,请添加XML注释,解释原因:您的团队成员或/和您未来的自我将会欣赏。
  • 仔细使用依赖scope 。 使用默认( compile )范围来编译和测试真正需要的东西(例如loga4j ),仅使用test (并且仅test )用于测试中使用的内容(例如junit ),请注意已provided范围。你的目标容器(例如servlet-api ),只在runtime使用runtime作用域,但你永远不应该用它编译(例如JDBC驱动程序)。 不要使用system范围,因为它只会带来麻烦(例如,它不与最终的工件打包在一起)。
  • 不要使用版本范围 ,除非出于特定原因并且请注意默认情况下指定的版本是最低要求, []表达式是最强的,但您很少需要它。
  • 使用Maven property作为系列的version元素的占位符,以确保您有一个集中的位置来版本化一组依赖项,这些依赖项都具有相同的版本值。 一个典型的例子是spring.versionhibernate.version属性,用于几个依赖项。 同样,集中化意味着更好的治理和维护,这也意味着更少的头痛和更少的地狱
  • 提供时, 导入BOM作为上述点的替代,并更好地处理依赖项系列(例如jboss ),委托另一个pom.xml文件管理某组依赖项。
  • 不要(ab)使用SNAPSHOT依赖项 (或尽可能少)。 如果您确实需要,请确保您永远不会使用SNAPSHOT依赖项发布:否则构建可重现性将处于高危险状态。
  • 在进行故障排除时,请始终使用help:effective-pom 来检查 pom.xml文件的完整层次结构 help:effective-pom就最终依赖关系图所涉及的有效dependencyManagementdependenciespropertieshelp:effective-pom可能非常有用。
  • 使用其他一些Maven插件来帮助您进行治理maven-dependency-plugin在故障排除过程中非常有用,但maven-enforcer-plugin可以提供帮助。 以下是一些值得一提的例子:

以下示例将确保没有人(您,您的团队成员,您自己的未来)将能够在compile范围中添加一个众所周知的测试库:构建将失败。 它确保junit永远不会达到PROD(与你的war打包,例如)

  maven-enforcer-plugin 1.4.1<.version>   enforce-test-scope validate  enforce      junit:junit:*:*:compile org.mockito:mockito-*:*:*:compile org.easymock:easymock*:*:*:compile org.powermock:powermock-*:*:*:compile org.seleniumhq.selenium:selenium-*:*:*:compile org.springframework:spring-test:*:*:compile org.hamcrest:hamcrest-all:*:*:compile  Test dependencies should be in test scope!   true     

看看这个插件提供的其他标准规则 :在出现错误情况时,许多可能对破坏构建很有用:

  • 你可以禁止依赖 (甚至是传递 ),在许多情况下非常方便
  • 例如,在使用SNAPSHOT情况下,您可能会失败 ,在发布配置文件中很方便。

同样,公共父pom可以包括多个这些机制( dependencyManagement ,enforcer插件,依赖项系列的属性),并确保遵守某些规则。 您可能无法涵盖所有​​可能的情况,但它肯定会降低您感知和体验的地狱程度。

使用Maven Helper插件可以通过排除旧版本的依赖项轻松解决所有冲突。

根据我的经验,我没有发现任何完全自动化的东西,但我发现以下方法对我自己非常有用和有用:

首先,我尝试了一个清晰的项目结构图,项目之间的关系,我通常使用Eclipse图形依赖视图,这告诉我,例如,是否省略了依赖与另一个相冲突。 此外,它告诉您已解决的项目依赖项。 我真诚地不使用IntelliJ IDEA,但我相信它有类似的function。

通常我会尝试将非常常见的依赖关系置于结构中,并且我利用function来处理传递依赖关系的版本,最重要的是, 避免项目结构中的重复

在这个Maven – Manage Dependencies博客文章中,您可以找到关于依赖关系管理的好教程。

在我的项目中添加新的依赖项时,就像在你的情况下一样,我会在项目结构中添加它的位置并相应地进行更改,但在大多数情况下,依赖项管理机制能够处理这个问题。

在这个Maven Best Practices博客文章中,您可以找到:

Maven的dependencyManagement部分允许父pom.xml定义可能在子项目中重用的依赖项。 这避免了重复; 如果没有dependencyManagement部分,每个子项目都必须定义自己的依赖项并复制依赖项的版本,范围和类型。

显然,如果您需要项目的特定版本的依赖项,您始终可以在层次结构的深层指定本地需要的版本。

我同意你的观点,这可能相当繁琐,但依赖管理可以给你一个很好的帮助。

即使替换所有具有相同名称的jar,您仍然可以拥有一些具有相同完全限定名称的类。 我在我的一个项目中使用了maven shade插件。 它打印来自不同jar的具有相同限定名称的类。 也许这可以帮到你