maven实战(四)聚合与继承

2019-04-14 16:32发布

我们知道maven构建项目是根据pom文件来构建的,一般一个应用是分成多个模块,每个模块都有自己的pom.xml,这样是不是就需要分多次构建,maven提供了聚合功能,可以使一个父类模块聚合所有模块。例子如下: 4.0.0 com.juvenxu.mvnbook.account accout-aggregator 1.0.0-SNAPSHOT pom Accout Aggregator account-email accout-persist
上述POM使用了与项目中其他pom共同的groupId com.juvenxu.mvnbook.account,artifactId为独立的account-aggregator,版本也与其他两个模块一致,为1.0.0-SNAPSHOT。这里第一个特殊的地方为packaging,其值为POM。一般我们不声明packaging,默认就是jar类型。对于聚合模式来说,其packaging必须是POM否则就无法构建。 之后是modules,这是实现聚合模式的最核心配置。用户可以在一个打包方式为pom的文件中声明任意数量的module元素来实现模块聚合。这里每个module的值都是一个当前POM的相对目录,譬如该例中,account-aggregator的POM路径为D:...codech-8account-aggregatorpom.xml,那么account-email就对应了目录D;...codech-8account-aggregatoaccount-email,而accout-persist对应于目录D:...codech-8account-aggregatoraccount-persist。这两个目录各自包含了pom.xml,src/main/java/,/src/test/java等内容,离开account-aggregator也能独立构建。 为了方便构建项目,通常将聚合模块放在项目的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目。下图为account-aggregator与另外两个模块的目录结构关系。
从上图可以看到,account-aggreagator的内容仅仅是一个pom.xml文件,它不像其他模块那样有src/main/java、src/test/java等目录。这也是容易理解的,聚合模块仅仅是帮助聚合其他模块构建的工具,它本身并无实质内容。

继承

如果两个模块的pom引入的太多相同的jar包,就可以使用pom的继承。 我们在account-aggregator下创建一个名为account-parent的子目录,然后在该子目录下建立一个所有出account-aggregator之外模块的父模块。为此,在该目录创建一个pom.xml文件,内容如下: 4.0.0 com.juvenxu.mvnbook.account account-parent 1.0.0-SNAPSHOT pom Account Parent
该pom十分简单,它使用了与其他模块一致的groupId和version,需要特别注意的是它的packaging为pom。这一点与聚合模块一样,作为父模块的pom,其打包类型必须为pom。由于父模块只是为了帮助消除配置的重复,因此它本身并不包含POM之外的项目文件,也就不需要src/main/java之类的文件夹了。有了父模块就要有其他模块来继承它,首先将account-email的POM修改如下: 4.0.0 com.juvenxu.mvnbook.accout account-parent 1.0.0-SNAPSHOT ../account-parent/pom.xml account-email ...
上述pom中使用parent元素声明父模块,parent下的子元素groupId、artifactId和version指定了父模块的坐标,这三个元素是必须的。元素relativePath表示父模块pom的相对路径,该例中的../account-parent/pom.xml表示父pom的位置在与account-email/目录平行的account-parent/目录下。当项目构建时,maven会首先根据relativePath检查父pom,如果找不到,再从本地仓库找。relativePath的默认值是../pom.xml,也就是说,maven默认父pom在上一层目录下。
这个更新过的pom没有为account-email声明groupId和version,不过这并不代表account-email没有groupId和version。实际上,这个子模块隐式地从父模块继承了这两个元素,也就消除了一下不必要的配置,用户如果不使用父模块的也可以自己显示声明。
其实我们大部分情况下,聚合和 继承都是放到一个pom中的,方便而且美观。

可继承的pom元素

上面我们说groupId是可以被继承的,那还有那些可以被继承的呢,以下是一个完整列表:
groupId version description:项目描述 organization:组织信息 inceptionYear:创始年份 url:项目url地址 distributionManagement:部署配置 issueManagement:缺陷跟踪系统信息 ciManagement:持续集成系统信息 scm:版本控制系统信息 mailingLists:邮件列表信息 properties:自定义的maven属性 dependencied:依赖配置 dependencyManagement:依赖管理配置 repositories:仓库配置 build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。 reporting:包括项目的报告输出目录配置、报告插件配置等。

依赖管理

其实上例子中,确实可以直接用子pom依赖父pom但却不推荐这么做,因为存在如下问题,现在两个子模块都包含了这四个依赖,但是却不能确定后面添加的模块也需要这四个依赖。假设以后项目加了个account-util,此时并不需要依赖spring。 maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies的依赖使用。例如,可以在account-parent中加入这样的dependencyManagement配置,如下: 4.0.0 com.juvenxu.mvnbook.account account-parent 1.0.0-SNAPSHOT pom 2.5.6 4.7 org.springframework spring-core ${springframework.version} org.springframework spring-beans ${springframework.version} org.springframework spring-context ${springframework.version} org.springframework spring-context-support ${springframework.version} junit junit ${junit.version} test

这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。现在修改account-emai的POM如下:
... 4.0.0 com.juvenxu.mvnbook.account account-parent 1.0.0-SNAPSHOT ../account-parent/pom.xml account-email 1.4.1 1.3.1b org.springframework spring-core org.springframework spring-beans org.springframework spring-context org.springframework spring-context-support junit junit javax.mail mail ${javax.mail.version} com.icegreen greenmail ${greenmail.version} test ...


上述POM中的依赖配置较原来简单了些,所有的springframework依赖只配置了groupId和artifactId,省去了version。这些信息可以省略是因为account-emai继承了account-parent中的dependencyManagement配置,完整的依赖声明已经包含在父pom中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。 使用这种依赖管理机制似乎不能减少太多的pom配置,不过还是强烈推荐使用这种方法。主要原因是在父pom中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖在父pom中声明后,子模块在使用的时候就无需声明版本,也就不会发生多个子模块版本不一致的情况。可以帮助降低依赖冲突的几率。而且如果在子模块不声明依赖的使用,只在父模块证明不会产生任何的效果。
一般我们都是聚合和继承两个功能融合到一个模块中,此时,就不需要再声明relativePath,因为当pom文件在上层目录的时候,maven能自动识别父模块位置,因此不再需要配置relativePath,示例如下: com.juvenxu.mvnbook.account account-parent 1.0.0-SNAPSHOT account-email Account Email ...


超级POM

任何一个maven项目都隐式地继承子超级POM,这点类似于任何java类都隐式地继承Object类。因此这也成为了maven项目的约定,例如,默认源码目录就是:src/main/java 在maven2和maven3中,超级POM在文件$MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/project/pom-4.0.0.xml目录下。这里的x.x.x表示maven的具体版本。现在分段看下: central Maven Repository Switchboard http://repo1.maven.org/maven2 default false central Maven Plugin Repository http://repo1.maven.org/maven2 default false never 首先超级POM定义了仓库及插件仓库,两者的地址都为中央仓库http://repo1.maven.org/maven2,并且都关闭了SNAPSHOT的支持。这也解释了为什么Maven默认可以按需要从中央仓库下载构件。 再接着看: ${project.basedir}/target ${project.build.directory}/classed ${project.artifactId}-${project.version} ${project.build.directory}/test-classes ${project.basedir}/src/main/java src/main/scripts ${project.basedir}/src/test/java ${project.basedir}/src/main/resources ${project.basedir}/src/test/resources 这里一次定义了项目的主输出目录、主代码输出目录、最终构件的名称格式、测试代码输出目录、主源码目录、脚本源码目录、测试源码目录、主资源目录和测试资源目录。这就是maven项目结构的约定。 紧接着超级pom为核心插件设定版本。 maven-antrun-plugin 1.3 maven-assembly-plugin 2.2-beta-4 maven-clean-plugin 2.3 maven-compiler-plugin 2.0.2 ...
这里就不一一列举了。

maven反应堆

我们应用中配置如下: account-email account-persist account-parent 通过mvn clean install发现它会先构建parent而不是按照顺序构建,这是因为maven的反应堆,当构建account-email的时候,发现它依赖了parent,所以就会先构建parent。