大型 Java 系统依赖管理
Posted
技术标签:
【中文标题】大型 Java 系统依赖管理【英文标题】:Large Java System Dependency Management 【发布时间】:2010-11-30 04:13:56 【问题描述】:我们有一个大型 (>500,000 LOC) Java 系统,它依赖于 40-50 个 OSS 包。系统是用Ant搭建的,依赖管理是 目前人工处理。我正在调查 Ivy 和/或 Maven 自动化依赖。我们将 Maven 视为构建自动化 去年的系统并拒绝了它,因为它需要完全 重构我们的系统以匹配 Maven 的架构。现在我 希望仅自动化依赖管理任务。
我对 Ivy 进行了一些实验,但遇到了问题。 例如,当我将 ActiveMQ 指定为依赖项,并告诉 Ivy 使用 Maven 存储库中的 POM 进行依赖规范,Ivy 检索一堆包(Jetty、Derby 和 Geronimo 用于 实例)我知道不需要只使用 ActiveMQ。
如果我在 ivysettings.xml 中设置 usepoms="false" 它只会获取 activemq.jar,但这似乎违背了 Ivy 和 将其降级为具有手动构建依赖项的简单 jar-fetcher 规格。
这里有一个更大的问题,过去被称为“DLL Hell”的东西 视窗。在某些情况下,两个直接的第一级依赖项将 指向同一传递依赖的不同版本(对于 实例 log4j.jar)。类路径中只能有一个 log4j.jar,所以 依赖解析涉及手动确定哪个版本是 与我们系统中的所有客户端兼容。
我想这一切都归结为每个包的依赖质量 规范(POM)。在 ActiveMQ 的情况下,没有范围 声明,所以任何对 ActiveMQ 的引用都会下载它的所有 依赖关系,除非我们手动排除我们知道我们不知道的那些 想要。
在 log4j 的情况下,自动依赖解析需要 所有 log4j 的客户端(其他依赖于 log4j 的包) 针对所有先前版本的 log4j 进行验证并提供一个范围(或 POM 中兼容的 log4j 版本列表)。这大概也是 有很多问题要问。
这是目前的情况,还是我遗漏了什么?
【问题讨论】:
+1 用于清楚地描述问题并尝试解决方案。我希望你能得到一个好的答案。 投票率最高的 4 个答案都提供了有价值的观点。凯文的简明准确。罗伯特和里奇提供了更多细节。弗拉基米尔根据现实世界的经验提出了积极的意见。这四个共同帮助我设定了我的期望并为解决问题指明了方向。我想“接受”所有四个答案,但不允许这样做。我接受 Robert Munteanu 是因为他首先给出了详细的答复。 【参考方案1】:你说的很对
我想这一切都归结为每个包的依赖规范(POM)的质量。
我唯一要补充的是查看 POM 或任何其他形式的元数据,作为起点。它非常有用,例如ActiveMQ 为您提供所有依赖项,但您可以自行选择它是否真的适合您的项目。
毕竟,即使考虑到 log4j 版本,您是否有外部依赖项选择版本或选择您知道适合您的版本?
至于如何选择定制依赖项,以下是 Ivy 可以做的事情:
不需要的包
Ivy 检索到一堆我知道不需要仅使用 ActiveMQ 的包(例如 Jetty、Derby 和 Geronimo)。
这通常是因为应用程序的模块化程度低。 应用程序的某些部分例如需要 Jetty,但即使你不使用它,你最终也会得到这种传递依赖。
您可能想查看ivy exclude mechanism:
<dependency name="A" rev="1.0">
<exclude module="B"/>
</dependency>
依赖版本
类路径中只能有一个 log4j.jar,因此依赖关系解析涉及手动确定哪个版本与我们系统中的所有客户端兼容。
也许我看错了,但 Ivy 的冲突解决方案中没有 manual 元素。有default conflict managers的列表:
all:此冲突管理器通过选择所有修订来解决冲突。也称为 NoConflictManager,它确实驱逐任何模块。 latest-time:此冲突管理器仅选择“最新”修订,最新被定义为最新时间。请注意,最新的时间计算成本很高,因此如果可以,请选择最新版本。 latest-revision:此冲突管理器仅选择“最新”修订,最新由修订的字符串比较定义。 latest-compatible:此冲突管理器选择冲突中的最新版本,这可能会导致一组兼容的依赖项。这意味着最终这个冲突管理器不允许任何冲突(如严格的冲突管理器),除非它遵循尽力而为的策略来尝试找到一组兼容的模块(根据版本约束); strict:只要发现冲突,此冲突管理器就会抛出异常(即导致构建失败)。如果需要,可以provide your own conflict manager。
【讨论】:
【参考方案2】:差不多就是这样。 maven 依赖系统(Ivy 或多或少遵循)让各个项目为它们的依赖项添加必要的元数据做好工作。大多数没有。
如果您走这条路,预计会花时间设置排除项。
对于推荐 OSGi 的海报,OP 说他不愿意为 Maven 重新构建他的构建系统,我认为他不想重新构建他的 应用程序符合 OSGi 标准。此外,许多符合 OSGi 的 OSS 项目(并且没有您希望的那么多)的元数据与 Maven 中的一样差或更差
【讨论】:
我认为说“大多数人不这样做”是非常夸张的,你真的能举出任何配置不当的主要项目吗?我使用过的绝大多数 Maven 项目都表现良好。大多数用户不了解系统的所有细微差别并被它们绊倒的情况更多。如果用户不使用 RTFM,这仍然可能是个问题,但对于复杂系统来说,情况总是如此。 Ivy 并不“主要遵循”Maven 依赖系统,它只是有一个适配器系统。诚然,从 Maven 导入的 POM 很糟糕,但如果您花时间手动重做 Ivy 的依赖项设置,您实际上最终会得到 Maven 可以提供的更好的东西。 @Rich 除了 OP 提到的 ActiveMQ?我遇到了 CXF 没有正确地将依赖项声明为“可选”或不需要的问题。【参考方案3】:在您列出的依赖项中,以下在 activemq-core
pom 中定义为 optional
(另请参阅 Maven 书中的 relevant section)。
我没有看到对 Jetty 的直接依赖,因此它可能是从可选依赖项之一中传递包含的。
在 Maven 中,可选依赖项是自动处理的。本质上,任何声明为可选的依赖项都必须在您的 pom 中重新声明才能使用。从上面链接的文档中:
当确实不可能(无论出于何种原因)将项目拆分为子模块时,使用可选依赖项。这个想法是,某些依赖项仅用于项目中的某些功能,如果不使用该功能,则不需要。理想情况下,这样的功能将被拆分为依赖于核心功能项目的子模块......这个新的子项目将只有非可选依赖项,因为如果您决定使用子项目的功能,您将需要它们。
但是,由于项目无法拆分(同样,无论出于何种原因),这些依赖项被声明为可选。如果用户想要使用与可选依赖相关的功能,他们将不得不在自己的项目中重新声明该可选依赖。这不是处理这种情况的最明确的方法,但同样,可选依赖项和依赖项排除都是权宜之计。
我不确定是否可以将 Ivy 配置为忽略可选依赖项,但可以将其配置为 exclude dependencies。例如:
<dependency name="A" rev="1.0">
<exclude module="B"/>
</dependency>
我知道这并不完全令人满意。可能 Ivy 确实支持可选依赖项(如果我发现任何东西,我会进一步查看和更新),但排除机制至少允许您管理它们。
关于你问题的最后一部分。 Maven 将解析 log4j 的依赖版本,如果版本兼容,它将自动选择列出的版本中的“最近”版本。
来自Introduction to the Dependency Mechanism:
依赖中介——这决定了当遇到工件的多个版本时将使用哪个版本的依赖。目前,Maven 2.0 仅支持使用“最近定义”,这意味着它将使用依赖树中与您的项目最接近的依赖版本。您始终可以通过在项目的 POM 中明确声明来保证版本。请注意,如果两个依赖版本在依赖树中的深度相同,则在 Maven 2.0.8 之前没有定义哪个会获胜,但从 Maven 2.0.9 开始,声明中的顺序很重要:第一个声明获胜。
“最近的定义”表示使用的版本将是依赖关系树中与您的项目最接近的版本,例如。如果 A、B 和 C 的依赖项定义为 A -> B -> C -> D 2.0 和 A -> E -> D 1.0,则构建 A 时将使用 D 1.0,因为从 A 到 D 的路径通过E 更短。您可以在 A 中显式添加对 D 2.0 的依赖项以强制使用 D 2.0
如果版本不兼容,您会遇到比依赖解析更大的问题。我相信 Ivy 的运作模式类似,但我不是专家。
【讨论】:
【参考方案4】:我认为这确实是目前的情况。 OSGi 和为 java 1.7 提议的新打包系统(已经讨论过那个已经得出结论了吗?)试图至少解决依赖于不同版本的库问题,但我不'认为他们现在无法解决您的问题。
【讨论】:
你有关于“Java 1.7 建议的新包装系统”的更多信息的链接吗? 我在考虑 JSR 277:jcp.org/en/jsr/detail?id=277 -- google 了很多意见 ;-) ...我认为这是为了进入 1.7 但显然它不会发生。【参考方案5】:我目前正在使用 Ivy 为多个项目(一些独立项目,一些依赖项目)管理 120 多个 OSS 和专有库。早在 2005 年(当时 Ivy 还来自 Jayasoft),我决定(或不得不)为每个集成包编写 ivy.xml 文件。
最大的优势是我可以完全控制各种配置。这对某些人来说可能听起来有点过头了,但我们的构建系统已经可靠运行了 4 年多,添加一个新库通常需要 5 分钟。
【讨论】:
【参考方案6】:“这是目前的情况吗?”
不适用于 OSGi。您可能想查看 OSGi 容器和捆绑包。一个包就像一个 jar 文件,但它支持详细说明其版本的元数据,以及它需要的相关包的版本(通过将属性放在 META-INF 文件中)。所以你的 log4j 包会指明它的版本,而依赖的包会详细说明它们需要什么版本的 log4j。
此外,支持非分层类加载器机制,这样您可以加载多个版本的 log4j,并且不同的包可以指定和绑定到这些不同的版本。
Javaworld 有很好的介绍here。
【讨论】:
【参考方案7】:存在依赖注入的整个想法 - 这总是会导致程序需要重组。我一直听到一些关于 GUICE 在这方面做得很好的声音。从部署的角度来看,我只部署了我们构建的 .jar,依赖项 .jar 是通过 jnlp 从原始项目中获取的。这背后的构建系统涉及手动跟踪新版本的依赖项并在构建系统中更新。
【讨论】:
【参考方案8】:“这是目前的状况吗”
是的。
这是开源的权衡。
一个闭源框架(即 .Net)将为您解决所有这些问题。
开源解决方案意味着你必须一直解决它(并解决它)。
您也许可以找到一些预先构建的配置,然后花钱请人让这些配置保持最新。例如,您可以选择使用 Red Hat Enterprise Linux。如果你坚持他们所支持的(仅此而已),那么配置就解决了。
但是,没有打包配置满足您的要求的可能性很大。
【讨论】:
我强烈不同意你的封闭源代码更好,开源正在不断投入努力。你有这个问题的闭源替代方案吗? .NET 除外。 我不同意这种情况。有问题的依赖项是可选的,并且在 Maven 中正确管理(有关更多详细信息,请参阅我的答案)。可能是 Ivy 无法处理可选属性,但它仍然有办法处理那些依赖项。那么您提出这一主张的依据是什么? @Robert Munteanu:我并没有说封闭源代码解决方案以模糊、笼统的方式更好。它提供了预集成的组件。这就是它的唯一优势。在大多数情况下,这实际上是一种负担,因为预集成的东西发展缓慢并且落后于最先进的技术。 这与“开源”与“闭源”无关。 @Jim Garrison:微软的无能不是一个很好的例子。甲骨文、IBM、惠普、Sun 和众多其他供应商都能够提供闭源解决方案,而不会出现微软的 DLL 地狱问题。以上是关于大型 Java 系统依赖管理的主要内容,如果未能解决你的问题,请参考以下文章