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

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

我的问题是:我如何系统地处理Jar地狱的问题?

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

我目前的尝试是基于反复试验的,我用杰克逊的例子概述了这一点:

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

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

整个过程非常繁琐。除了我提出的具体问题外,我还对其他人对这个问题的系统性方法感到好奇。有没有人拥有他们使用的任何资源?


答案 1

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

要检查API(中断)兼容性,有几个工具可以自动分析jar并为您提供正确的信息。从这个Stack Overflow帖子中,有一些方便的工具有很好的提示。
JAPICC似乎相当不错。

然后,我用来弄清楚我实际使用的是哪个版本的Jackson(问题2:有没有一种自动的方式来询问专家正在使用哪个版本的jar,而不是梳理树输出?mvn dependency:tree

这绝对是要走的路,但是您可以从一开始就过滤掉范围,只获取您实际要查找的内容,使用其包含选项,如下所示:maven-dependency-tree

mvn dependency:tree -Dincludes=<groupId>

注意:您还可以在表单中为该选项提供更多信息,或使用通配符,例如.includesgroupId:artifactId:type:version*:artifactId

这似乎是一个小小的提示,但在具有许多依赖项的大型项目中,缩小其输出范围会有很大的帮助。通常,简单地说,作为过滤器应该足够了,如果您正在寻找特定的依赖项,这可能是最快的。groupId*:artifactId

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

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

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

通过阴影罐,你可能意味着:

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

有没有人拥有他们使用的任何资源?

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

  • 依赖关系管理绝对是本主题的切入点:在这里,您可以处理 Maven 依赖关系中介,影响其对传递依赖关系、版本和范围的决策。重要的一点是:您添加到的内容不会自动添加为依赖项。 只有在项目的某个依赖项(如在文件中声明或通过传递依赖项)与其某个条目匹配时才被考虑在内,否则它将被简单地忽略。这是其中很重要的一部分,因为它有助于管理依赖关系及其传递图,这就是为什么经常在父pom中使用的原因:你只想处理一个版本,并以集中的方式处理哪个版本,例如,你想在所有Maven项目中使用,你在一个公共/共享的父pom中声明它,并确保它将被这样使用。集中化意味着更好的治理和更好的维护。dependencyManagementdependencyManagementpom.xmlpom.xmllog4jdependencyManagement
  • 依赖关系部分对于声明依赖关系很重要:通常,您应在此处仅声明所需的直接依赖关系。一个好的 thump 规则是:在这里声明为(默认)范围,只声明您在代码中实际用作语句的内容(但是您通常需要超越它,例如,运行时需要的 JDBC 驱动程序,并且永远不会在代码中引用,然后它将在范围内)。还要记住:声明的顺序很重要:在与传递依赖关系发生冲突的情况下,第一个声明的依赖项获胜,因此,通过重新声明依赖项,您可以有效地影响依赖项中介。compileimportruntime
  • 不要滥用依赖关系中的排除来处理传递依赖关系:如果可以的话,它的使用和顺序。滥用使维护更加困难,只有在您真正需要时才使用它。此外,在添加时,请始终添加一个XML注释来解释原因:您的队友或/和您未来的自己会欣赏。dependencyManagementdependenciesexclusionsexclusions
  • 深思熟虑地使用依赖项范围。将默认 () 作用域用于编译和测试(例如),仅(且仅)用于测试中使用的内容(例如),注意目标容器已提供的内容的范围(例如),仅将作用域用于运行时所需的内容,但永远不要使用它进行编译(例如 JDBC 驱动程序)。不要使用 scope,因为它只会带来麻烦(例如,它没有与最终工件一起打包)。compileloga4jtestjunitprovidedservlet-apiruntimesystem
  • 不要使用版本范围,除非出于特定原因,并且请注意,默认情况下指定的版本是最低要求,否则表达式是最强的,但您很少需要它。[<version>]
  • 使用 Maven 属性作为库系列的版本元素的占位符,以确保您有一个集中的位置用于对一组依赖项进行版本控制,这些依赖项都具有相同的版本值。一个典型的示例是用于多个依赖项的 or 属性。同样,集中化意味着更好的治理和维护,这也意味着更少的头痛和更少的地狱spring.versionhibernate.version
  • 在提供时,导入 BOM 作为上述点的替代方法,并更好地处理依赖项系列(例如 jboss),将一组特定依赖项的管理委托给另一个文件。pom.xml
  • 不要(ab)使用快照依赖项(或尽可能少地使用)。如果你真的需要,确保你永远不会使用依赖关系发布:否则,构建可重复性将处于很高的危险之中。SNAPSHOT
  • 在进行故障排除时,请始终检查文件的完整层次结构,使用 help:effective-pom 在检查 effective 时可能非常有用,并且就最终的依赖关系图而言。pom.xmldependencyManagementdependenciesproperties
  • 使用其他一些Maven插件来帮助你进行治理。这在故障排除过程中确实很有帮助,但也提供了帮助。以下是一些值得一提的例子:maven-dependency-pluginmaven-enforcer-plugin

以下示例将确保没有人(您、您的队友、您未来的自己)能够在范围内添加众所周知的测试库:构建将失败。它确保永远不会到达PROD(与您的,例如打包在一起)compilejunitwar

<plugin>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>1.4.1<.version>
    <executions>
        <execution>
            <id>enforce-test-scope</id>
            <phase>validate</phase>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <bannedDependencies>
                        <excludes>
                            <exclude>junit:junit:*:*:compile</exclude>
                            <exclude>org.mockito:mockito-*:*:*:compile</exclude>
                            <exclude>org.easymock:easymock*:*:*:compile</exclude>
                            <exclude>org.powermock:powermock-*:*:*:compile</exclude>
                            <exclude>org.seleniumhq.selenium:selenium-*:*:*:compile</exclude>
                            <exclude>org.springframework:spring-test:*:*:compile</exclude>
                            <exclude>org.hamcrest:hamcrest-all:*:*:compile</exclude>
                        </excludes>
                        <message>Test dependencies should be in test scope!</message>
                    </bannedDependencies>
                </rules>
                <fail>true</fail>
            </configuration>
        </execution>
    </executions>
</plugin>

看看这个插件提供的其他标准规则:在错误的情况下,许多规则可能有助于破坏构建:

同样,一个共同的父 pom 可以包含多个这些机制(强制器插件、依赖项系列的属性),并确保遵守某些规则。你可能不会涵盖所有可能的场景,但它肯定会降低你所感知和体验的地狱程度。dependencyManagement


答案 2

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


推荐