zoukankan      html  css  js  c++  java
  • 【转】Maven多模块项目构建

    划分多模块项目的原因

    多人协作的Maven管理的真实的项目可以选择分模块,每个模块都对应着一个pom.xml。它们之间通过继承和聚合(也称作多模块,multi-module)相互关联。那么,为什么要这么做呢?我们明明在开发一个项目,划分模块后,导入Eclipse变成了N个项目,这会带来复杂度,给开发带来不便。
    为了解释原因,假设有这样一个项目,很常见的Java Web应用。在这个应用中,我们分了几层:

    • Dao层负责数据库交互,封装了Mysql交互的接口 。
    • Service层处理业务逻辑,放一些Service接口和实现相关的Bean。
    • Web层负责与客户端交互,主要有一些Control类。

    对应的,在一个项目中,我们会看到一些包名:

    • hans.app.dao
    • hans.app.service
    • hans.app.web
    • hans.app.util

    这样整个项目的框架就清晰了,但随着项目的进行,你可能会遇到如下问题:
    这个应用可能需要有一个前台和一个后台管理端(admin),你发现大部分dao,一些service,和大部分util是在两个应用中可。这样的问题,你一周内遇到了好几次。
    pom.xml中的依赖列表越来越长以重用的,但是,由于目前只有一个项目(WAR),你不得不新建一个项目依赖这个WAR,这变得非常的恶心,因为在Maven中配置对WAR的依赖远不如依赖JAR那样简单明了,而且你根本不需要hans.app.web。有人修改了dao,提交到git并且不小心导致build失败了,你在编写service的代码,发现编译不过,只能等那人把dao修复了,你才能继续进行,很多人都在修改,到后来你根本就不清楚哪个依赖是谁需要的,渐渐的,很多不必要的依赖被引入。甚至出现了一个依赖有多个版本存在。
    build整个项目的时间越来越长,尽管你只是一直在web层工作,但你不得不build整个项目。
    某个模块,比如util,你只想让一些经验丰富的人来维护,可是,现在这种情况,每个开发者都能修改,这导致关键模块的代码质量不能达到你的要求。

    我们会发现,其实这里实际上没有遵守一个设计模式原则:“高内聚,低耦合”。虽然我们通过包名划分了层次,并且你还会说,这些包的依赖都是单向的,没有包的环依赖。这很好,但还不够,因为就构建层次来说,所有东西都被耦合在一起了。因此我们需要使用Maven划分模块。

    聚合

    一个简单的Maven模块结构是这样的:

    ---- app-parent
    |-- pom.xml (pom)
    |
    |-- app-util
    | |-- pom.xml (jar)
    |
    |-- app-dao
    | |-- pom.xml (jar)
    |
    |-- app-service
    | |-- pom.xml (jar)
    |
    |-- app-web
    |-- pom.xml (war)

    上述简单示意图中,有一个父项目(app-parent)聚合很多子项目(app-util, app-dao, app-service, app-web)。每个项目,不管是父子,都含有一个pom.xml文件。而且要注意的是,小括号中标出了每个项目的打包类型。父项目是pom,也只能是pom。子项目有jar,或者war。根据它包含的内容具体考虑。

    这些模块的依赖关系如下:

    app-dao --> app-util
    app-service --> app-mapper
    app-web --> app-service

    注意依赖的传递性(大部分情况是传递的,除非你配置了特殊的依赖scope),app-dao依赖于app-util,app-service依赖于app-dao,于是app-service也依赖于app-util。同理,app-web依赖于app-dao,app-util。

    用项目层次的划分替代包层次的划分能给我们带来如下好处:
    方便重用,如果你有一个新的项目需要用到app-dao和app-service,添加对它们的依赖即可,你不再需要去依赖一个WAR。而有些模块,如app-util,完全可以渐渐进化成公司的一份基础工具类库,供所有项目使用。这是模块化最重要的一个目的。
    由于你现在划分了模块,每个模块的配置都在各自的pom.xml里,不用再到一个混乱的纷繁复杂的总的POM中寻找自己的配置。
    某些模块,如app-util被所有人依赖,但你不想给所有人修改,现在你完全可以从这个项目结构出来,做成另外一个项目,svn只给特定的人访问,但仍提供jar给别人使用。
    多模块的Maven项目结构支持一些Maven的更多的特性(如DepencencyManagement)

    接下来讨论一下POM配置细节,实际上非常简单,先看app-parent的pom.xml:

    1.  
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2.  
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    3.  
      <modelVersion>4.0.0</modelVersion>
    4.  
      <groupId>com.test.hans</groupId>
    5.  
      <artifactId>app-parent</artifactId>
    6.  
      <packaging>pom</packaging>
    7.  
      <version>1.0-SNAPSHOT</version>
    8.  
      <modules>
    9.  
      <module>app-util</module>
    10.  
      <module>app-dao</module>
    11.  
      <module>app-service</module>
    12.  
      <module>app-web</module>
    13.  
      </modules>
    14.  
      </project>

    Maven的坐标GAV(groupId, artifactId, version)在这里进行配置,这些都是必须的。特殊的地方在于,这里的packaging为pom。所有带有子模块的项目的packaging都为pom。packaging如果不进行配置,它的默认值是jar,代表Maven会将项目打成一个jar包。
    该配置重要的地方在于modules,例子中包含的子模块有app-util, app-dao, app-service, app-war。在Maven build app-parent的时候,它会根据子模块的相互依赖关系整理一个build顺序,然后依次build。这里model中的名称应与子模块所在目录名保持一致。
    父模块的内容仅是一个pom.xml文件,它不想其他模块那样有src/main/java等目录。这也是容易理解的,父模块仅仅是帮助聚合其他模块构建的工具,它本身并无实际的内容。
    父模块与其他模块的目录结构并非一定是父子关系,还可以是平行关系。那样的话只需要更改一下modules

    1.  
      <modules>
    2.  
      <module>../app-util</module>
    3.  
      <module>../app-mapper</module>
    4.  
      <module>../app-service</module>
    5.  
      <module>../app-web</module>
    6.  
      </modules>

    在父模块运行mvn clean install命令 会得到:


    命令行输出

    上述输出中显示的是各模块的name,而不是artifactId,这也解释了为什么要在POM中配置合理的name字段,其目的是让Maven的构建输出更清晰。

    继承

    多模块构建的另一个特性就继承。继承解决最大的问题就是,重复。重复往往意味着更多的劳动和更多潜在的问题。POM的继承机制能让我们抽取重复的配置。

    1.  
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2.  
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    3.  
      <parent>
    4.  
      <artifactId>app-parent</artifactId>
    5.  
      <groupId>com.test.hans</groupId>
    6.  
      <version>1.0-SNAPSHOT</version>
    7.  
      </parent>
    8.  
      <modelVersion>4.0.0</modelVersion>
    9.  
      <artifactId>app-util</artifactId>
    10.  
      </project>

    这个POM没有为app-util声明groupId和version,不过这并不代表app-util没有groupId和version。实际上,这个子模块隐式的从父模块继承了这两个元素,这也就消除了一些不必要的配置。如果子模块需要使用和父模块不一样的groupId和version完全可以显示声明。
    还有哪些POM元素可以被继承呢?以下是一个常见的列表:

    • url:项目的URL地址
    • dependencies:项目的依赖配置
    • dependencyManagement:项目的依赖管理配置
    • repositories:项目的仓库配置
    • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
    • reporting:包括项目的报告输出目录配置、报告插件配置等

    上面的列表中包含了dependencies,说明依赖是会被继承的,这时我们会想,将spring等我们依赖的jar包就都可以配置到父模块中,这样子模块就能移除这些依赖,简化配置。但这种做法是存在问题的。当其中一个子模块不需要这些依赖的时候,强制它依赖spring等显然是不合理的。Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。下面是测试项目的POM文件:
    父模块:

    1.  
      <dependencyManagement>
    2.  
      <dependencies>
    3.  
      <dependency>
    4.  
      <groupId>com.test.hans</groupId>
    5.  
      <artifactId>org-remote-service</artifactId>
    6.  
      <version>1.0.26</version>
    7.  
      </dependency>
    8.  
      </dependencies>
    9.  
      </dependencyManagement>

    子模块

    1.  
      <dependencies>
    2.  
      <dependency>
    3.  
      <groupId>com.test.hans</groupId>
    4.  
      <artifactId>org-remote-service</artifactId>
    5.  
      </dependency>
    6.  
      </dependencies>

    而且还可以将这些依赖的版本会Maven变量的形式提取了出来,这样所有模块的依赖的版本处于更加明显的位置。这样可以很好的解决多个子模块使用的依赖版本不一致的情况。可以降低依赖冲突的几率。如果子模块没有声明,即使该依赖已经在父POM中,子模块也不会引用。
    但是如果其中的一个子模块想使用和父模块一样的POM,只要像下面这么写就可以了

    1.  
      <dependencyManagement>
    2.  
      <dependencies>
    3.  
      <dependency>
    4.  
      <groupId>com.test.hans</groupId>
    5.  
      <artifactId>app-parent</artifactId>
    6.  
      <version>1.0-SNAPSHOT</version>
    7.  
      <type>pom</type>
    8.  
      <scope>import</scope>
    9.  
      </dependency>
    10.  
      </dependencies>
    11.  
      </dependencyManagement>

    Maven提供了dependencyManagement元素帮助管理项目依赖,类似的,Maven也提供了pluginManagement元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
    父模块:

    1.  
      <build>
    2.  
      <pluginManagement>
    3.  
      <plugins>
    4.  
      <plugin>
    5.  
      <groupId>org.apache.maven.plugins</groupId>
    6.  
      <artifactId>maven-resources-plugin</artifactId>
    7.  
      <version>2.5</version>
    8.  
      <configuration>
    9.  
      <encoding>${encoding}</encoding>
    10.  
      </configuration>
    11.  
      </plugin>
    12.  
      </plugins>
    13.  
      </pluginManagement>
    14.  
      </build>

    子模块

    1.  
      <build>
    2.  
      <plugins>
    3.  
      <plugin>
    4.  
      <groupId>org.apache.maven.plugins</groupId>
    5.  
      <artifactId>maven-resources-plugin</artifactId>
    6.  
      </plugin>
    7.  
      </plugins>
    8.  
      </build>

    聚合与继承的关系

    如果用一句话总结两者的关系就是没有关系。聚合主要是为了方便构建项目,继承主要是为了消除重复配置。
    对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
    对于继承关系的父pom来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
    它们之间的共同点就是,聚合POM与继承关系中的父POM的packaging都必须是pom,同时聚合模块与继承关系中的父模块出了POM之外都没有实际内容。
    在现有的实际项目中,一般是一个POM即是聚合POM,又是父POM,这么做十分方便同时也是Maven的默认约定。



    文/hansjason(简书作者)
    原文链接:http://www.jianshu.com/p/79cc250be1b2
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
    每天进步一点点,快乐生活多一点。
  • 相关阅读:
    [论文笔记] On Construction of Cloud IaaS for VM Live Migration Using KVM and OpenNebula (ICA3PP, 2012)
    [论文笔记] Energyaware resource allocation heuristics for efficient management of data centers for Cloud computing (FGCS, 2012)
    [概念学习] hypervisor
    [论文泛读] Dynamic placement of virtual machines for managing sla violations (IM, 2007)
    [论文笔记] Costs of virtual machine live migration A survey (SERVICES, 2012)
    [论文笔记] Live Migration of Multiple Virtual Machines with Resource Reservation in Cloud Computing Environments (CLOUD, 2011)
    vscode如何搭建vue项目
    jenkins部署
    yum命令Header V3 RSA/SHA1 Signature, key ID c105b9de: NOKEY
    Spring 笔记——核心数据规则篇
  • 原文地址:https://www.cnblogs.com/yiruliu/p/15634053.html
Copyright © 2011-2022 走看看