用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
或使用通配符(如*:artifactId
为includes
选项提供更多信息。
这似乎是一个小小的暗示,但在具有许多依赖性的大型项目中,缩小其输出量是非常有帮助的。 通常,只要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
来处理传递依赖项:如果可以,请使用dependencyManagement
和dependencyManagement
顺序。 滥用exclusions
使维护变得更加困难,只有在您真正需要时才使用它。 此外,在添加exclusions
项时,请添加XML注释,解释原因:您的团队成员或/和您未来的自我将会欣赏。 - 仔细使用依赖
scope
。 使用默认(compile
)范围来编译和测试真正需要的东西(例如loga4j
),仅使用test
(并且仅test
)用于测试中使用的内容(例如junit
),请注意已provided
范围。你的目标容器(例如servlet-api
),只在runtime
使用runtime
作用域,但你永远不应该用它编译(例如JDBC驱动程序)。 不要使用system
范围,因为它只会带来麻烦(例如,它不与最终的工件打包在一起)。 - 不要使用版本范围 ,除非出于特定原因并且请注意默认情况下指定的版本是最低要求,
[
表达式是最强的,但您很少需要它。] - 使用Maven
property
作为库系列的version
元素的占位符,以确保您有一个集中的位置来版本化一组依赖项,这些依赖项都具有相同的版本值。 一个典型的例子是spring.version
或hibernate.version
属性,用于几个依赖项。 同样,集中化意味着更好的治理和维护,这也意味着更少的头痛和更少的地狱 。 - 提供时, 导入BOM作为上述点的替代,并更好地处理依赖项系列(例如jboss ),委托另一个
pom.xml
文件管理某组依赖项。 - 不要(ab)使用
SNAPSHOT
依赖项 (或尽可能少)。 如果您确实需要,请确保您永远不会使用SNAPSHOT
依赖项发布:否则构建可重现性将处于高危险状态。 - 在进行故障排除时,请始终使用
help:effective-pom
来检查pom.xml
文件的完整层次结构help:effective-pom
就最终依赖关系图所涉及的有效dependencyManagement
,dependencies
和properties
,help: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的具有相同限定名称的类。 也许这可以帮到你
- 如何在IDEA中使用类路径而不是模块路径运行Java 9应用程序?
- 在IntelliJ IDEA中创建一个可删除的jar
- 所有子包的IntelliJ IDEA @ParametersAreNonnullByDefault
- 除了使用集成测试运行器在IntelliJ IDEA项目中以“IntegrationTest”结尾的所有JUnitunit testing之外,我该如何运行?
- 无法在旧版本的JUnit上运行简单的JUnit TestCase
- IntelliJ IDEA社区版可以以某种方式支持Ultimate的function吗?
- 在IntelliJ IDEA中将多个项目复制并粘贴到剪贴板
- 在IntelliJ中添加最喜欢的静态导入方法?
- 尝试更新我的Intellij时出现OutOfMemory错误