Maven父POM:循环依赖

Maven父POM:循环依赖,maven,dependency-management,circular-dependency,Maven,Dependency Management,Circular Dependency,我们有一个模块化项目,包含大约10个工件: parent +- artifact1 +- artifact2 +- artifact3 +- ... +- artifact10 . ├── [api:0.14.0-SNAPSHOT] │   ├── pom.xml │   └── src │      ├── main │      │ ├── java ... │      │ └── webapp ... │      └── test ├── [dao:1.21

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

parent
 +- artifact1
 +- artifact2
 +- artifact3
 +- ...
 +- artifact10
.
├── [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 ...
此外,一些工件之间存在依赖关系:

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=>0.1.1),我们的父工件版本(12.7.3)需要更新,因为它引用了旧工件版本(0.1.0)。因为我们在父POM中更改了这个引用(0.1.0=>0.1.1),所以我们也需要增加父POM的版本(12.7.3=>12.7.4)。现在,我们的工件仍然引用以前的父版本(12.7.3),即我们需要再次更新它。。。那是循环的

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

编辑

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

parent
 +- artifact1
 +- artifact2
 +- artifact3
 +- ...
 +- artifact10
.
├── [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”是部署在应用服务器上的工件。所有工件都像这样引用“父对象”(在开发期间):


com.acme
父母亲
0.11.0-快照
应用程序编程接口
0.14.0-快照
情景:

  • exporter commons获得更新:0.9.0-SNAPSHOT=>0.9.1-SNAPSHOT
  • docx exporter和pdf exporter参考exporter commons,无版本,即无需更改
  • 父项需要更新以反映exporter commons的更新:0.11.0-SNAPSHOT=>0.12.0-SNAPSHOT
问题:api:0.14.0-快照引用父级:0.11.0-快照。api:0.14.0-SNAPSHOT随后更新为引用父级:0.12.0-SNAPSHOT。api:0.14.0-SNAPSHOT变为api:0.15.0-SNAPSHOT。但是parent:0.12.0-SNAPSHOT中的pom.xml引用了api:0.14.0-SNAPSHOT。 =>恶性循环

(注意:工件名称是为了简单而组成的。)

建议 为了简化依赖项配置,请使用

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

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

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

这项技术要求在发布修补程序时发布父项目。父项目指的是与库或EAR的战争,或内部包含所有工件的分发存档


更多信息:

如果所有其他模块(“子模块”)都依赖于一个公共根模块(“父模块”),则父模块不应依赖任何子模块

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

但是,您也可以让父模块的子模块成为子模块。这是一种不同的、不可继承的关系,它会导致子模块在超级模块之后构建。这是可行的。让超级模块的子模块在超级模块上除了简单的包含之外没有其他关系也是完全合理的。


简而言之,无论您以何种方式在磁盘和存储库中排列模块,都不要引入循环依赖关系。(我看到有人认为,循环依赖应该在逻辑上将一组模块转换为单个模块,因为这是合理定义适当包含运算符操作的唯一方法。我不确定我是否完全同意这一点,但这并不是完全错误的…)Maven中的一个主要混淆源是
父级
pom实际上可以包含两种不同类型的关系:

  • 亲子关系:
    • 在其
      标记中为每个子级声明一次
    • 继承pom.xml中声明的插件、依赖项、属性、版本等
  • 聚合器子模块关系:
    • 通过
      标记在顶级pom声明一次
    • 通过cmd行传入的目标(例如
      mvn clean install
      )将传递给子模块
如果所有模块都保持在同一版本上(即始终从顶层执行发布),则这种区别是不相关的。但一旦版本开始分段(即发布一个子模块而不是另一个子模块),则有必要为每个任务创建两个单独的POM

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
项目/
母公司/
parent_pom.xml#将依赖项版本声明为范围[0.1.0,0.2.0]
孩子们/
聚合器_pom.xml#部分列出了api/dao/etc
原料药/
pom.xml#将parent_pom声明为parent
刀/
pom.xml#将parent_pom声明为parent
为什么需要这种复杂的结构