Maven父POM:循环依赖

时间:2014-01-28 20:59:59

标签: maven dependency-management circular-dependency

我们有一个包含大约10件工件的模块化项目:

parent
 +- artifact1
 +- artifact2
 +- artifact3
 +- ...
 +- artifact10

此外,一些工件彼此之间存在依赖关系:

artifact1
 +-> artifact2
 +-> artifact3
 +-> ...
 +-> artifact10

artifact2
 +-> artifact3

artifact4
 +-> artifact3

artifact4
 +-> artifact5

artifact5
 +-> artifact6

我们目前的设置如下:

  • parent是包含父POM的工件。
  • 此父POM定义了所有必需的依赖项(如Spring,JPA,...)。
  • 我们所有的工件都是定义的。
  • 我们的工件将父工件引用为 - 说明显而易见的 - 父。
  • 只有父POM定义版本。所有其他POM都没有。

我们使用带有三个数字的版本控制方案:

<major version>.<minor version>.<patch level>

例如:

0.1.0-SNAPSHOT (a young artifact in development)
0.1.0 (the same artifact once it has been released)
0.1.1 (the same artifact after a hotfix)

问题:

一旦我们更改了工件的版本(例如:0.1.0 =&gt; 0.1.1),我们的父工件版本(12.7.3)需要更新,因为它引用旧工件版本(0.1.0) 。由于我们在父POM中更改此引用(0.1.0 => 0.1.1),我们也需要增加父POM的版本(12.7.3 =&gt; 12.7.4)。现在,我们的工件仍然引用了先前的父版本(12.7.3),即我们需要再次更新它...这是循环的。

解决此类循环亲子关系的最佳方法是什么?我们可以从父POM中删除我们自己的依赖项,并在所有其他工件的POM中定义它们的版本,但这意味着我们需要在依赖项更改后更新所有工件。

修改

包含我们工件的简化目录结构:

.
├── [api:0.14.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java ...
│       │   └── webapp ...
│       └── test
├── [dao:1.21.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [parent:0.11.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [pdf-exporter:0.2.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [docx-exporter:0.3.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [exporter-commons:0.9.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
└── [security:0.6.0-SNAPSHOT]
    ├── pom.xml
    └── src
        ├── main ...
        └── test ...

工件目录(在方括号中;与工件版本一起)彼此独立,即为方便起见,它们仅在公共根目录(“。”)中。每个工件都有自己的git存储库。 “api”是部署在应用程序服务器上的工件。所有工件都像这样引用“父”(在开发期间):

<parent>
    <groupId>com.acme</groupId>
    <artifactId>parent</artifactId>
    <version>0.11.0-SNAPSHOT</version>
</parent>

<artifactId>api</artifactId>
<version>0.14.0-SNAPSHOT</version>

情景:

  • exporter-commons获取更新:0.9.0-SNAPSHOT =&gt; 0.9.1快照。
  • docx-exporter和pdf-exporter引用exporter-commons没有版本,即无需更改。
  • parent需要更新以反映exporter-commons的更新:0.11.0-SNAPSHOT =&gt; 0.12.0快照。

问题:api:0.14.0-SNAPSHOT引用父:0.11.0-SNAPSHOT。 api:0.14.0-SNAPSHOT然后更新为引用父:0.12.0-SNAPSHOT。 api:0.14.0-SNAPSHOT变为api:0.15.0-SNAPSHOT。但是父级中的pom.xml:0.12.0-SNAPSHOT引用api:0.14.0-SNAPSHOT。 =&GT;恶性循环。

(注意:工件名称是为了简单起见。)

3 个答案:

答案 0 :(得分:1)

建议

为了简化依赖关系配置,请使用versions ranges

例如,工件A需要具有版本B的工件0.1.0。将依赖项配置为范围<version>[0.1.0, 0.2.0)</version>

这意味着A需要B版本大于或等于0.1.0且小于0.2.0(因此所有修补程序都适用于此工件)。

这有帮助,因为当发布修补程序时,不需要更改工件A依赖项。只需重建父项目,并将修复后的B附加到项目A

此技术需要在发布修补程序时释放父项目通过父项目,我的意思是像WAR一样考虑库或EAR,或者包含所有工件的分发存档。

更多:3.4.3. Dependency Version Ranges

答案 1 :(得分:1)

Maven混淆的一个主要原因是parent pom实际上可以包含两种不同类型的关系:

  • 亲子关系:
    • 在每个孩子的<parent>标记
    • 中声明一次
    • 继承pom.xml
    • 中声明的插件,依赖项,属性,版本等
  • 聚合器 - 子模块关系:
    • 通过<modules>标记
    • 在顶级pom中声明一次
    • 通过cmd行传递的目标(例如mvn clean install)将传递给子模块

如果所有模块保持在同一版本(即始终从顶层执行发布),则此区别无关紧要。但是只要版本开始碎片化(即释放一个子模块而不释放另一个子模块),就有必要为每个任务创建2个独立的poms。

project/
  parent/
    parent_pom.xml      # declare dependency versions as ranges [0.1.0, 0.2.0)
  children/
    aggregator_pom.xml  # <modules> section lists api/dao/etc
    api/
      pom.xml           # declare parent_pom as parent
    dao/
      pom.xml           # declare parent_pom as parent

为什么需要这种复杂的结构?

为什么不跟MariuszS建议在顶级父母使用范围?

想象一下,基本组件之一,api,非常稳定。如果可以避免,您不想重建或重新发布它。

与此同时,假设有两个相互依赖的组件,例如pdf-exporterdocs,它们经常发布和/或分支,以便您经常更改版本范围:0.1.x - &gt; 0.2.x - &gt; 0.3.x等。

然后你会被迫修改和释放你的父pom以反映pdf-exporterdocs之间的关系,但你不一定要释放api它并不关心这些变化。因此,需要将父方放在一边并确保释放它不会触发api子模块的不必要的重新发布。

答案 2 :(得分:0)

如果存在所有其他模块(“子”)所依赖的公共根模块(“父”),那么子项的 none 应该由父项依赖。< / p>

这是因为Maven以一种非常简单的方式工作:构建一个模块,你首先构建它所依赖的模块的所有(使用传递闭包模型)尚未建成。如果你有一个循环依赖,你最终会遇到这样的情况:为了构建A,你必须首先建立A。这很疯狂,显然无法以有用的方式发挥作用。

但是,您也可以让父母将其子女作为子模块。这是一个不同的不可继承的关系,它导致子模块在超级模块之后构建。这样可行。使超级模块的子模块与超级模块没有其他关系也是完全合理的。

简而言之,无论您将模块安排在磁盘上还是存储库中, 都不会引入循环依赖 。 (我已经看到它认为循环依赖应该在逻辑上将一组模块转换为单个模块,因为这是明确定义正确包含运算符操作的唯一方法。我不确定我是否完全赞同,但这并非完全错误......)