大型Java系统依赖管理

30
我们有一个大型的Java系统(>500,000 LOC),依赖于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获取器,需要手动构建依赖关系规范。
这里有一个更大的问题,在Windows中被称为“DLL Hell”。在某些情况下,两个直接的第一级依赖项将指向同一个传递依赖项的不同版本(例如log4j.jar)。只能在类路径中放置一个log4j.jar,因此依赖关系解析涉及手动确定哪个版本与我们系统中所有客户端兼容。
我想这一切归结于每个软件包的依赖规范(POM)的质量。在ActiveMQ的情况下,没有范围声明,因此对ActiveMQ的任何引用都将下载其所有依赖项,除非我们手动排除我们知道不需要的依赖项。
在log4j的情况下,自动依赖关系解析需要log4j的所有客户端(依赖于log4j的其他软件包)验证所有先前版本的log4j,并在POM中提供一系列(或列表)与log4j兼容的版本。这可能太多要求了。
这是目前的情况吗,还是我漏掉了什么?

8
感谢您清晰地描述了问题并提供了解决方案。我希望您能得到一个好的答案。 - Ben S
2
排名前四的点赞答案都提供了有价值的观点。Kevin的回答简明准确,Robert和Rich则提供了更多细节。Vladimir基于实际经验做出了积极的贡献。这四个答案帮助我设定了期望,并指引了解决问题的方向。我想“接受”所有四个答案,但SO不允许这样做。我选择接受Robert Munteanu的回答,因为他第一个给出了详细的回复。 - Jim Garrison
8个回答

11

你说的完全正确。

我想这归结于每个软件包的依赖规范(POM)的质量。

我唯一要补充的是,将POM或其他形式的元数据视为起点。比如,ActiveMQ为您提供了所有依赖项,这非常有用,但您可以自己选择是否适合项目。

毕竟,即使考虑到log4j版本,您会让外部依赖项选择版本还是选择您知道适合自己的版本?


至于如何选择定制依赖项,以下是使用Ivy的方法:

不需要的软件包

Ivy检索了一堆软件包(例如Jetty、Derby和Geronimo),我知道只使用ActiveMQ是不需要它们的。

这通常是应用程序模块化不良造成的。例如某些部分需要Jetty,但即使您不使用它,也会得到这种传递依赖关系。

您可能需要查看ivy排除机制

<dependency name="A" rev="1.0">
  <exclude module="B"/>
</dependency>

依赖版本

在类路径中只能有一个log4j.jar,因此依赖决议需要手动确定哪个版本与我们系统中的所有客户端兼容。

也许我理解有误,但在Ivy的冲突解决方案中没有manual元素。这里列出了一些默认的冲突管理器

  • all:此冲突管理器通过选择所有版本来解决冲突。也称为NoConflictManager,它驱逐任何模块。
  • latest-time:此冲突管理器仅选择“最新”的版本,“最新”被定义为时间上最晚的版本。请注意,计算时间上最晚的版本成本很高,因此如果可以,请使用latest-revision。
  • latest-revision:此冲突管理器仅选择“最新”的版本,“最新”由版本的字符串比较定义。
  • latest-compatible:此冲突管理器从可能导致一组兼容性依赖项的冲突中选择最新版本。这意味着最终此冲突管理器不允许任何冲突(像严格的冲突管理器一样),但它遵循最佳努力策略尝试找到一组兼容的模块(根据版本约束);
  • strict:此冲突管理器在发现冲突时引发异常(即导致构建失败)。

如果需要,您可以提供自己的冲突管理器


5
在您列出的依赖项中,以下依赖项在activemq-core pom中被定义为optional(还请参见Maven书中的相关章节)。
  • org.apache.derby:derby
  • org.apache.geronimo.specs:geronimo-jta_1.0.1B_spec

我没有看到Jetty的直接依赖关系,因此它可能是从其中一个可选依赖项中传递包含的。

在Maven中,可选依赖项会自动处理。基本上,声明为可选的任何依赖项都必须在您的pom中重新声明才能使用它。根据上面链接的文档:

当无法将项目拆分成子模块时,可选依赖项用于某些特定功能的依赖项,如果不使用该功能,则不需要这些依赖项。理想情况下,这样的功能将被拆分为依赖于核心功能项目的子模块...这个新的子项目只有非可选依赖项,因为如果您决定使用子项目的功能,则需要它们全部。

然而,由于项目无法拆分(出于任何原因),这些依赖项被声明为可选的。如果用户想要使用与可选依赖项相关的功能,则必须在自己的项目中重新声明该可选依赖项。这不是处理此情况最清晰的方式,但可选依赖项和依赖项排除都是权宜之计。

我不确定您是否可以配置Ivy来忽略可选依赖项,但您可以配置它来排除依赖项(exclude dependencies)。例如:
<dependency name="A" rev="1.0">
  <exclude module="B"/>
</dependency>

我知道这并不完全令人满意。可能Ivy支持可选依赖项(如果我找到任何内容,我会进一步查看并更新),但是排除机制至少允许您管理它们。


关于您问题的最后部分。Maven将解决log4j的依赖版本,如果这些版本是兼容的,它将自动选择列出的版本中“最近”的版本。
来自依赖机制介绍
依赖调解 - 确定当多个版本的一个构件被遇到时将使用哪个版本的依赖关系。目前,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,因为通过E从A到D的路径更短。您可以在A中显式添加对D 2.0的依赖项,以强制使用D 2.0。
如果版本不兼容,你面临的问题比依赖关系解决还要大。我相信Ivy也是基于类似的模型运作,但我不是专家。

5

基本上就是这样。Maven依赖系统(Ivy大体上也遵循这个)将添加所需的元数据留给各个项目自己完成。大多数项目都没有做好这一点。

如果您选择这种方式,请准备花费时间设置排除项。

对于建议使用OSGi的发帖者,OP说他不愿意为Maven重新架构构建系统,我认为他也不想重新架构应用程序以符合OSGi标准。此外,许多符合OSGi标准的OSS项目(并不像您希望的那么多)具有与Maven中相同或更糟糕的元数据。


我认为说“大多数不是这样”的说法夸张了,你能引用一些配置不良的主要项目吗?我使用过的绝大多数Maven项目都表现良好。更多的情况是大多数用户不理解系统的所有细节,并因此被绊倒。如果用户不阅读文档,这仍然可能是一个问题,但对于复杂的系统来说,这总是一个问题。 - Rich Seller
Ivy并不是“大多数遵循”Maven依赖系统,它只是为其提供了适配器系统。的确,从Maven导入的POM文件很糟糕,但如果你花时间手动重新设置Ivy的依赖项,你实际上会得到比Maven更好的东西。 - Esko
除了OP提到的ActiveMQ之外,还有其他选择吗?我曾经遇到过CXF未能正确声明依赖项为“可选”或不需要的问题。 - Kevin

4

我认为这确实是目前的现状。OSGi和针对Java 1.7提出的新打包系统(它的讨论已经结束了吗?)试图解决至少依赖于不同库版本的问题,但我不认为它们能够立即解决您的问题。


你有没有更多关于“Java 1.7拟议中的新包装系统”的信息链接? - matt b
我在考虑JSR 277:http://jcp.org/en/jsr/detail?id=277 -- 可以通过谷歌搜索了解很多意见;-) ... 我原以为这应该会在1.7中实现,但显然不会发生。 - Simon Groenewolt

3

我目前使用Ivy来管理超过120个开源和专有库,用于几个项目(一些是独立的,一些是依赖的)。在2005年(当时Ivy还是Jayasoft的产品)我决定(或不得不)为每个集成包编写ivy.xml文件。

最大的优点是我可以完全控制各种配置。这对于一些人来说可能听起来有些过度,但我们的构建系统已经可靠地工作了4年多,添加一个新库通常只需要5分钟的时间。


2

"这是目前的情况吗?"

不是在OSGi中。您可能需要查看OSGi容器和捆绑包。捆绑包类似于jar文件,但它支持元数据详细说明其版本以及所需的相关捆绑包的版本(通过在META-INF文件中放置属性)。因此,您的log4j捆绑包将指示其版本,并且依赖捆绑包将详细说明它们需要的log4j版本。

此外,支持非分层类加载器机制,因此您可以加载多个log4j版本,不同的捆绑包可以指定并绑定到这些不同的版本。

Javaworld在此处有一个非常好的介绍。


0

整个依赖注入的概念是必然导致需要重构程序。我听到关于GUICE在这方面表现良好的一些声音。从部署的角度来看,我已经成功地部署了仅包含我们构建的.jar文件,而依赖的.jars通过jnlp从原始项目中获取。这背后的构建系统涉及手动跟踪依赖项的新版本并在构建系统中进行更新。


-5

这是目前的情况吗?

是的。

这就是开源的权衡。

闭源框架(例如 .Net)将为您解决所有问题。

开源解决方案意味着您必须一直解决和解决它。

您可能能够找到一些预构建的配置,并支付某人来保持这些配置最新。例如,您可以选择使用 Red Hat Enterprise Linux。如果您坚持使用他们支持的内容(仅限于此),则配置问题得以解决。

然而,很有可能没有任何打包的配置符合您的要求。


2
我强烈不同意你的观点,认为闭源更好,而开源则需要不断投入努力。你有没有一个闭源的解决方案来解决这个问题?除了.NET之外。 - Robert Munteanu
1
我不同意这种情况的说法。所涉及的依赖关系是可选的,并且在Maven中得到了正确管理(请参阅我的答案以获取更多详细信息)。可能是Ivy无法处理可选属性,但它仍然有一种处理这些依赖关系的方法。那么,你对于提出这个主张的依据是什么? - Rich Seller
@Robert Munteanu:我并没有笼统地说闭源解决方案更好。它只是提供了预集成的组件。这是它唯一的优点。而且大多数时候,实际上这是一个负面因素,因为预集成的东西进化缓慢,落后于现有技术水平。 - S.Lott
3
这与“开源”与“闭源”的区别无关。 - Nate
1
@Jim Garrison:微软的无能并不是一个好的例子。Oracle、IBM、HP、Sun和其他许多供应商都可以提供闭源解决方案,而没有微软产品中那种烦人的DLL问题。 - S.Lott
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接