如何重构Maven多模块项目?

13

很抱歉这篇文章有点长,但我很难在不附上图片的情况下让它更加简洁。最近我接手了一个maven 3.0多模块项目的构建工作。问题是,项目/模块的结构很混乱。从源代码控制中的存储方式(我们使用RTC)到模块的pom结构,每次都要尝试让整个构建周期完成,我都快被搞疯了。

就项目层次结构而言,所有模块都是"平面"存储的;即:所有东西都在同一级别。我有一个父POM文件,所有模块都依赖于该父文件。然而,该父文件与我的其他所有模块处于同一级别。

例如:

c:\dev\MyEarProject
 + parent-pom
   - pom.xml
 + module1
   - pom.xml (depends on parent-pom)
   - src
   -   main
   -     ...
 + module2
   - pom.xml (depends on parent-pom)
   - src
   -   main
   -     ...
 + module3
   - pom.xml (depends on parent-pom)
   - src
   -   main
   -     ...

父pom定义了构建项目所需的所有模块,以及在不同子模块中使用的一堆构件版本号的属性:

<modules>
  <module>../module1</module>
  <module>../module2</module>
  <module>../module3</module>
</modules>

<properties>
    <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
    <slf4j.version>1.6.4</slf4j.version>
    <repositoryAddress>${snapshots.repo.url}</repositoryAddress>
    <my.hibernate-module.dao.impl>1.2.3</my.hibernate-module.dao.impl>
    <my.hibernate-module.dao.api>1.2.3</my.hibernate-module.dao.api>
</properties>

每个模块的pom依次通过pom的artifact编号依赖于父pom:

<parent>
    <groupId>com.cws.cs.lendingsimulationservice</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0.6</version>
</parent>
为了让事情更加混乱,实际构件的名称可能与模块路径匹配,也可能不匹配(这取决于模块)。例如,module1 可能位于路径 c:\dev\MyEarProject\module1 中,但具有 artifact 名称 hibernate-module。但是,由于它在 RTC 中存储的方式,当它被签出时,目录名为 module1。
当然,最简单的构建方式是进入 c:\dev\MyEarProject\parent-pom\ 并运行 mvn clean deploy。在 SNAPSHOT 模式下,这可以正常工作,因为 SNAPSHOT 存储库允许多次部署同一构件版本。但在 release 模式下,会失败。
这种结构给我带来了两个问题:
1.每次需要更改父级属性的版本时,我都必须更新 parent-pom 版本号以及所有子模块的 parent pom 版本号和子模块版本号(因为父级已更改)。
2.每当我需要部署一个 release cycle 时,如果其中一个模块自上一个周期以来没有更改,并且因此无法重新部署到同一个 repo (该 repo 不允许覆盖现有构件),则 mvn 将抛出错误。
所以我正在寻找重新组织此项目以避免这些问题的最佳方法。对于 parent pom,我知道可以使用相对路径指向父级。然而,鉴于模块的“平面”结构,这是否是推荐的方法(例如:parent pom 相对路径将是 ../parent-pom/pom.xml-对我来说似乎有点奇怪)。此外,考虑到父级的版本控制独立于模块,使用相对路径是否只会开启另一种混乱的方式(例如:没有办法知道父级 pom 的哪个版本与子模块的哪个版本相关联)。
其次,我如何构建整个 ear 而不遇到我遇到的部署错误?由于 artifact 已经存在于存储库中,所以我不需要重建和重新部署它。我尝试使用 --projects,但涉及的模块数量非常大,管理起来非常困难。
3个回答

13

我真正推荐的第一件事是重新组织项目文件夹...这意味着让项目文件夹代表结构,这意味着不要使结构扁平化。

  +-- parent-pom (pom.xml)
       +--- module1 (pom.xml)
       +--- module2 (pom.xml)
       +--- module3 (pom.xml)

由此,您的父级模块部分将简化为以下内容:

<modules>
  <module>module1</module>
  <module>module2</module>
  <module>module3</module>
</modules>

此外,您模块中的父级条目也可以像这样简化:

<parent>
  <groupId>com.cws.cs.lendingsimulationservice</groupId>
  <artifactId>parent-pom</artifactId>
  <version>1.0.6</version>
</parent>

这让我想到下一个观点:

如果你所有当前的项目都将它们的父级定义为上面提到的方式,那么这是错误的,因为它会在仓库内而不是上一级文件夹中查找父级。换句话说,这导致了很多发布等问题。

如果我们要解决这个问题,它应该像这样,但我不建议这样做:

<parent>
  <groupId>com.cws.cs.lendingsimulationservice</groupId>
  <artifactId>parent-pom</artifactId>
  <version>1.0.6</version>
  <relativePath>../parent-pom/pom.xml</relativePath>
</parent>

我注意到的另一件事是,你没有使用SNAPSHOT,而这将在发布阶段被发布插件替换。与此相关的是,它会自动更改所有适当父级等中的版本。

在理想情况下,你的模块应该看起来像这样:

<parent>
  <groupId>com.cws.cs.lendingsimulationservice</groupId>
  <artifactId>parent-pom</artifactId>
  <version>1.0.6</version>
</parent>

<artifactId>module-1</artifactId>
<!-- No Version or groupId -->
因为所有模块将继承其父模块的版本和groupId,所以有时需要更改模块的groupId,但这是一个例外情况。
我重新阅读的一个问题是关于父模块分离版本控制的,这根本没有意义,因为它是子模块的父模块,所以应该放到相同的结构和当然是相同的 VCS 中。
如果您想要创建一些配置/插件版本、依赖项,并且这些依赖项应该用于其他项目,那么请创建一个单独的企业级 pom.xml 文件,它是一个单独的项目,并将单独发布等。
在完成结构更改后,您可以进入父pom目录,从该文件夹执行mvn clean packagemvn release:prepare release:perform,一切都会变得更简单。

2
很遗憾,由于RTC和组件/模块的设置方式,重构为树形结构不是一个可行的选择。因此,我必须接受当前的结构/布局,并尽力应对这种情况。将版本放在父级别以被所有子模块继承,实质上意味着即使只有单个模块发生更改,也需要重新构建/测试/部署所有模块(这可能很昂贵)。实质上,我失去了独立版本化一个模块的能力。虽然我同意,这将解决我的部署问题。 - Eric B.
1
如果你想继续对抗Maven,那就轮到你了,但我建议改变结构,因为这将使你的生活更轻松。但是,如果单独部署每个模块的想法没有意义,无论它们是否相关。如果它们不相关,那么你应该真正地将它们分开,并且不使用多模块构建。 - khmarbaise
1
我在理解如何仅在父级中定义版本号时遇到了麻烦。在我的当前设置中,我可以更改模块中的接口,而不会影响构建的其余部分,直到重新集成新版本为止。但是,如果只有父级中的版本号,则更改模块的接口将破坏构建的其余部分,因为所有模块都将依赖于源树而不是预部署的二进制文件。我该如何避免这个问题?我仍然需要为每个模块单独进行版本控制,不是吗? - Eric B.
1
如果你真的需要分开的版本,也就是说不同的发布版本,那么你需要有单独的模块而不是一个多模块构建。 - khmarbaise
如果我们要解决这个问题,它必须看起来像我无法推荐的方式 - 那么你会推荐什么? - Kalpesh Soni

2
如果你要发布你的POM文件,你需要发布任何更新,但是你不需要手动修改POM版本——你可以使用versions插件或者release插件来自动更新版本。我倾向于使用release插件,因为它可以自动提交到SCM。
mvn versions:set 

http://mojo.codehaus.org/versions-maven-plugin/

mvn release:prepare release:perform

http://maven.apache.org/plugins/maven-release-plugin/

您的仓库管理器可能允许覆盖现有版本,但最好的做法是发布一个新版本。

我倾向于使用扁平的模块结构,因为它允许使用父文件夹来存储公共文件,例如checkstyle配置。我也发现将组ID共享到模块中,然后将模块目录命名为artifactId相同非常有用。


我已经有一段时间没有使用发布插件了,但是我不明白当我需要将单个模块升级到新版本时,它如何解决我的问题。如果module3需要更新(从1.1到1.2-SNAPSHOT),并且module1依赖于module3,则我会更新module3 pom、parent-pom属性、parent-pom版本、module3 parent版本以及最后更新module1 parent-version和module1版本。发布插件如何为我完成所有这些操作呢?请注意,在此时module2不需要更改任何内容。 - Eric B.
版本插件应该能够为您更新依赖项,然后您只需提交并部署新的构件。 - cvanes

2
您提出了矛盾的要求。您想重组项目,但不能移动物品。您想简化部署和发布周期,但不想使用单个版本。
考虑到一个模块的更改将不可避免地影响所有相关模块,我建议使用简单的版本控制方案,其中所有子模块都继承其父版本。Maven release:prepare和release cycles变得简单。使用发布说明跟踪您的更改,并证明跳过不必要的未更改模块的测试(版本更改不会更改构建/二进制输出的构建过程,因此您可以将其用作主要论据)。
祝您的项目好运。

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