Maven父POM与模块POM的区别

296

在多项目构建中,有好几种结构父POM的方式,我想知道每种方式的优缺点。

最简单的方式是将父POM放在项目的根目录中,例如:

myproject/
  myproject-core/
  myproject-api/
  myproject-app/
  pom.xml

这里的 pom.xml 同时充当了父项目以及描述 -core,-api 和 -app 模块的角色。

下一种方法是将父项目分离到自己的子目录中,如下所示:

myproject/
  mypoject-parent/
    pom.xml
  myproject-core/
  myproject-api/
  myproject-app/

当父POM文件仍然包含模块信息但是它们采用相对路径时,例如../myproject-core

最后一种情况是模块定义和父POM分离,例如:

myproject/
  mypoject-parent/
    pom.xml
  myproject-core/
  myproject-api/
  myproject-app/
  pom.xml
父POM包含了任何"共享"配置 (dependencyManagement、properties等),而 myproject/pom.xml 则包含了模块列表。目的是为了支持大型构建,因此应该能够扩展到大量项目和构件。
一些额外的问题:
- 最好在哪里定义各种共享配置,例如源代码控制、部署目录、常用插件等 (我假设是父POM,但我经常会遇到问题,它们最终会出现在每个项目中,而非一个公共的地方)。 - Maven-release 插件、Hudson 和 Nexus 如何处理多项目的设置 (这可能是一个很大的问题,更多的是关于任何人是否被设置多项目构建所困扰)?
编辑:每个子项目都有自己的 pom.xml,我将其省略以保持简洁。

模块是否也有它们自己的 pom 文件?我的项目有一个父级 pom,但每个模块也有自己的 pom 文件(或许是你描述的第四种方式)。 - harschware
啊,好的,我会编辑和更新。每个子模块也有自己的pom文件。 - Jamie McCrindle
4
更新一下,我能看出第二个选项的优点在于,在Eclipse中更容易管理。如果子模块是单独的项目,则在第一个和第三个示例中包含根pom.xml会很困难。 - Jamie McCrindle
4个回答

170
依我之见,要回答这个问题,你需要从项目生命周期和版本控制的角度去考虑。换句话说,父POM是否有自己的生命周期,即是否可以单独发布而不影响其他模块?
如果答案是“是”(对于大多数在问题或评论中提到的项目来说都是这种情况),那么父POM需要从VCS和Maven的角度拥有自己的模块,你将会得到在VCS层面上像这样的东西:
root
|-- parent-pom
|   |-- branches
|   |-- tags
|   `-- trunk
|       `-- pom.xml
`-- projectA
    |-- branches
    |-- tags
    `-- trunk
        |-- module1
        |   `-- pom.xml
        |-- moduleN
        |   `-- pom.xml
        `-- pom.xml

这使得检出变得有点痛苦,常见的解决方法是使用 svn:externals。例如,添加一个trunks目录:

root
|-- parent-pom
|   |-- branches
|   |-- tags
|   `-- trunk
|       `-- pom.xml
|-- projectA
|   |-- branches
|   |-- tags
|   `-- trunk
|       |-- module1
|       |   `-- pom.xml
|       |-- moduleN
|       |   `-- pom.xml
|       `-- pom.xml
`-- trunks

使用以下 externals 定义:

parent-pom http://host/svn/parent-pom/trunk
projectA http://host/svn/projectA/trunk

如果检出trunks,则会得到以下本地结构(模式#2):

root/
  parent-pom/
    pom.xml
  projectA/

你甚至可以在trunks目录中添加一个pom.xml

root
|-- parent-pom
|   |-- branches
|   |-- tags
|   `-- trunk
|       `-- pom.xml
|-- projectA
|   |-- branches
|   |-- tags
|   `-- trunk
|       |-- module1
|       |   `-- pom.xml
|       |-- moduleN
|       |   `-- pom.xml
|       `-- pom.xml
`-- trunks
    `-- pom.xml

这个 pom.xml 是一种“假”pom文件:它从未发布过,不包含真正的版本号,因为该文件从未发布,它只包含一个模块列表。使用此文件进行检出将导致此结构(模式#3):

root/
  parent-pom/
    pom.xml
  projectA/
  pom.xml

这个“hack”允许在检出后从根目录启动反应堆构建,使事情更加方便。实际上,这就是我喜欢为大型构建设置Maven项目和VCS存储库的方式:它只需运行即可,可以很好地扩展,提供了您可能需要的所有灵活性。

如果回答是否定的(回到最初的问题),那么我认为你可以采用第一种模式(尽可能做最简单的事情)。

现在,关于奖励问题:

  
        
  • 在哪里定义各种共享配置的最佳位置,例如源代码控制、部署目录、常用插件等(我假设父母知道这一点,但我经常被这些东西所困扰,它们已经出现在每个项目而不是一个公共项目中)。
  •   

老实说,在这里我不知道如何不给出一般性的答案(比如“使用您认为合理的级别来相互协作”)。无论如何,子POM始终可以覆盖继承的设置。

  
        
  • Maven-release插件、Hudson和Nexus如何处理您设置的多项目(可能是一个巨大的问题,更多的是如果有人被设置的多项目构建所困扰)?
  •   

我使用的设置效果很好,没有什么特别需要提到的。

实际上,我想知道Maven-release-plugin如何处理模式#1(尤其是<parent>部分,因为在发布时不能具有SNAPSHOT依赖项)。这听起来像是一个先有鸡还是先有蛋的问题,但我只是记不起来它是否有效,并且太懒得测试。


这非常好,因为我可以看到它如何扩展。svn:externals回答了我的担忧,即它会变得繁琐。 - Jamie McCrindle
@Jamie,很高兴你觉得它有帮助。 - Pascal Thivent
1
你能详细说明如何让“hack”起作用吗?我已经拿到了trunks项目,并预计您会使用“启动反应堆构建”的-pl选项,但是在对结构进行发布时,这会成为一个问题,因为该选项无法从根级别的构建传递,也不能将其放置在release-Plugin的<arguments/>选项中。因此,release:perform失败,因为它尝试在根级别pom上工作... - Jan
svn:externals hack 是我长期以来见过的最有用的 svn hack。对于在同一代码库中具有每个项目分支的项目非常有帮助。 - omerkudat
我也是“虚假”根POM在大型多仓库项目中的忠实粉丝,但由于Maven 3不再支持从反应堆构建中解析父模块,因此您可能需要更新您的帖子,说明这仅适用于Maven 2.x。 - mikewse
1
嗨Pascal,如果模块部分提到的项目没有使用parent-pom作为其<parent>,而是使用其他项目的parent-pom,那么你会如何处理版本号?这些项目的构件将获得哪个版本号(这些项目<parent>部分的构件版本号还是列出这些项目作为模块的项目的版本号)?http://stackoverflow.com/questions/25918593/interesting-maven-questions-pom-types-and-how-to-handle-maven-parents-uncl - AKS

40

根据我的经验和Maven最佳实践,有两种“父POM”:

  • “公司”父POM - 此POM包含您的公司特定信息和配置,继承每个POM并且不需要复制。这些信息包括:

    • 仓库
    • 分发管理部分
    • 常见插件配置(如maven-compiler-plugin的源代码和目标版本)
    • 组织、开发人员等

    准备此父POM需要小心,因为所有公司POM都将从中继承,所以这个POM必须是成熟和稳定的(发布父POM的版本不应影响发布所有公司项目!)

  • 第二种父POM是多模块父POM。我更喜欢您的第一解决方案——这是多模块项目的默认Maven约定,很常见也代表VCS代码结构。

旨在支持大规模构建,因此应可扩展到大量项目和构件。

多项目具有树状结构 - 因此您不会被限制在一个级别的父POM上。尝试为您的需求找到合适的项目结构 - 典型的例子是如何分发多模块项目。

distibution/
documentation/
myproject/
  myproject-core/
  myproject-api/
  myproject-app/
  pom.xml
pom.xml

一些额外的问题:

  • 在哪里定义各种共享配置最好,例如源代码控制、部署目录、常用插件等(我假设是父级,但我经常会被这个问题困扰,它们最终出现在每个项目中而不是一个公共的地方)。

这些配置应该明智地分成“公司”父POM和项目父POM。与所有项目相关的内容放在“公司”父级中,与当前项目相关的内容放在项目POM中。

  • maven-release插件、Hudson和Nexus如何处理多项目构建的设置(可能是一个巨大的问题,更多的是是否有人因为设置多项目构建方式而被卡住)?

必须先发布公司父POM。对于多项目构建,使用标准规则即可。CI服务器需要了解所有信息以正确构建项目。


1
因此,存在两种类型的父项目/pom。一种没有<modules>声明。这是用于继承的那个。还有一个带有<multimodule>声明的父项目。子模块不从中继承,它只是用来管理整个项目的构建。我说得对吗? - lisak

24
  1. 独立的父项目是在不同组件之间共享配置和选项的最佳实践。Apache有一个父POM项目来共享法律声明和一些常见的打包选项。

  2. 如果您的顶层项目中有真正的工作,例如聚合javadoc或打包发布,则需要设置冲突,因为您想要共享的设置与完成这项工作所需的设置相冲突。只有父级项目避免了这种情况。

  3. 一个常见的模式(暂时忽略#1)是让具有代码的项目使用父级项目作为其父级,并将其作为顶级父级。这使得所有核心内容都可以被所有项目共享,但避免了#2中描述的问题。

  4. 如果父结构与目录结构不同,站点插件会变得非常混乱。如果您想构建一个汇总站点,则需要进行一些修改才能解决这个问题。

  5. Apache CXF是#2模式的一个示例。


2
我已经查看了Ibilio,并且许多项目似乎更喜欢“独立”的父POM。如Hibernate、ActiveMQ、Jetty、XStream等,这表明它是事实上的机制。 - Jamie McCrindle
2
独立父级的另一个缺点似乎是Maven发布插件似乎希望在进行发布之前将父级固定到某个版本。这可能不是太大的问题,因为父级不应该发生太多变化。 - Jamie McCrindle

10

第三种方法有一个小问题。由于聚合POM(myproject/pom.xml)通常根本没有父级,因此它们不共享配置。这意味着所有这些聚合POM将只有默认仓库。

如果您只使用来自中央仓库的插件,那就不是问题。但是,如果您从内部仓库运行插件,使用插件:目标格式,那么这将失败。例如,您可以拥有groupId为org.example,提供generate-foo目标的foo-maven-plugin。如果您尝试在项目根目录下使用类似mvn org.example:foo-maven-plugin:generate-foo的命令运行它,则它将无法在聚合模块上运行(请参见compatibility note)。

有几种解决方案:

  1. 将插件部署到Maven Central(并非总是可行)。
  2. 在所有聚合POM中指定存储库部分(违反DRY原则)。
  3. 在settings.xml中配置此内部存储库(可以在本地设置的~/.m2/settings.xml或全局设置的/conf/settings.xml中)。没有这些settings.xml,将导致构建失败(对于永远不会在公司外部构建的大型内部项目可能没有问题)。
  4. 在聚合POM中使用具有存储库设置的父级(可能有太多父级POM?)。

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