maven实战(四)聚合与继承

来源:互联网 发布:域名更新 编辑:程序博客网 时间:2024/05/23 12:58

我们知道maven构建项目是根据pom文件来构建的,一般一个应用是分成多个模块,每个模块都有自己的pom.xml,这样是不是就需要分多次构建,maven提供了聚合功能,可以使一个父类模块聚合所有模块。例子如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi:schemaLocation="http://maven.apache.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.juvenxu.mvnbook.account</groupId>  <artifactId>accout-aggregator</artifactId>  <version>1.0.0-SNAPSHOT</version>  <packing>pom</packing>  <name>Accout Aggregator</name>  <modules>    <module>account-email</module>    <module>accout-persist</module>  <modules></project>

上述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:\...\code\ch-8\account-aggregator\pom.xml,那么account-email就对应了目录D;\...\code\ch-8\account-aggregato\account-email,而accout-persist对应于目录D:\...\code\ch-8\account-aggregator\account-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文件,内容如下:
<modelVersion>4.0.0</modelVersion><grouanpId>com.juvenxu.mvnbook.account</groupId><artifactId>account-parent</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>Account Parent</name>

该pom十分简单,它使用了与其他模块一致的groupId和version,需要特别注意的是它的packaging为pom。这一点与聚合模块一样,作为父模块的pom,其打包类型必须为pom。由于父模块只是为了帮助消除配置的重复,因此它本身并不包含POM之外的项目文件,也就不需要src/main/java之类的文件夹了。有了父模块就要有其他模块来继承它,首先将account-email的POM修改如下:
<modelVersion>4.0.0</modelVersion><parent>  <groupId>com.juvenxu.mvnbook.accout</groupId>  <artifactId>account-parent</artifactId>  <version>1.0.0-SNAPSHOT</version>  <relativePath>../account-parent/pom.xml</relativePath></parent><artifactId>account-email</artifactId><dependencies>  ...</dependencies>

上述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配置,如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://ww.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.juvenxu.mvnbook.account</groupId>  <artifactId>account-parent</artifactId>  <version>1.0.0-SNAPSHOT</version>  <packaging>pom</packaging>  <properties>    <springframework.version>2.5.6</springframework.version>    <junit.version>4.7</junit.version>  </properties>  <dependencyManagement>    <dependencies>      <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-core</artifactId>        <version>${springframework.version}</version>      </dependency>      <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-beans</artifactId>        <version>${springframework.version}</version>      </dependency>      <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>${springframework.version}</version>      </dependency>      <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context-support</artifactId>        <version>${springframework.version}</version>      </dependency>             <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>${junit.version}</version>        <scope>test</scope>      </dependency> </dependencies> </dependencyManagement></project>


这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。现在修改account-emai的POM如下:

<project>...  <modelVersion>4.0.0</modelVersion>  <parent>    <groupId>com.juvenxu.mvnbook.account</groupId>    <parent>account-parent</parent>    <version>1.0.0-SNAPSHOT</version>    <relativePath>../account-parent/pom.xml</relativePath>  </parent>  <artifactId>account-email</artifactId>  <properties>    <javax.mail.version>1.4.1</javax.mail.version>    <greenmail.version>1.3.1b</greenmail.version>  </properties>  <dependencies>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-core</artifactId>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-beans</artifactId>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-context</artifactId>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-context-support</artifactId>    </dependency>    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>    </dependency>    <dependency>      <groupId>javax.mail</groupId>      <artifactId>mail</artifactId>      <version>${javax.mail.version}</version>    </dependency>    <dependency>      <groupId>com.icegreen</groupId>      <artifactId>greenmail</artifactId>      <version>${greenmail.version}</version>      <scope>test</scope>    </dependency>  </dependencies>...</project>



上述POM中的依赖配置较原来简单了些,所有的springframework依赖只配置了groupId和artifactId,省去了version。这些信息可以省略是因为account-emai继承了account-parent中的dependencyManagement配置,完整的依赖声明已经包含在父pom中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
使用这种依赖管理机制似乎不能减少太多的pom配置,不过还是强烈推荐使用这种方法。主要原因是在父pom中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖在父pom中声明后,子模块在使用的时候就无需声明版本,也就不会发生多个子模块版本不一致的情况。可以帮助降低依赖冲突的几率。而且如果在子模块不声明依赖的使用,只在父模块证明不会产生任何的效果。

一般我们都是聚合和继承两个功能融合到一个模块中,此时,就不需要再声明relativePath,因为当pom文件在上层目录的时候,maven能自动识别父模块位置,因此不再需要配置relativePath,示例如下:
<parent>  <groupId>com.juvenxu.mvnbook.account</groupId>  <artifactId>account-parent</artifactId>  <version>1.0.0-SNAPSHOT</version></parent><artifactId>account-email</artifactId><name>Account Email</name>...


超级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的具体版本。现在分段看下:
<repositories>  <repository>    <id>central</id>    <name>Maven Repository Switchboard</name>    <url>http://repo1.maven.org/maven2</url>    <layout>default</layout>    <snapshots>      <enabled>false</enabled>    </snapshots>  </repository></repositories><pluginRepositories>  <pluginRepository>    <id>central</id>    <name>Maven Plugin Repository</name>    <url>http://repo1.maven.org/maven2</url>    <layout>default</layout>    <snapshots>      <enabled>false</enabled>    </snapshots>    <releases>      <updatePolicy>never</updatePolicy>    </releases>  </pluginRepository></pluginRepositories>
首先超级POM定义了仓库及插件仓库,两者的地址都为中央仓库http://repo1.maven.org/maven2,并且都关闭了SNAPSHOT的支持。这也解释了为什么Maven默认可以按需要从中央仓库下载构件。
再接着看:
<build>  <directory>${project.basedir}/target<directory>  <outputDirectory>${project.build.directory}/classed</outputDirectory>  <filename>${project.artifactId}-${project.version}</filename>  <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>  <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>  <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory>  <testSourceDirectory>${project.basedir}/src/test/java</testSoutceDirectory>  <resources>    <resource>      <directory>${project.basedir}/src/main/resources</directory>    </resource>  </resources>  <testResources>    <testResource>      <directory>${project.basedir}/src/test/resources</directory>    </testResoutce>  </testResources>
这里一次定义了项目的主输出目录、主代码输出目录、最终构件的名称格式、测试代码输出目录、主源码目录、脚本源码目录、测试源码目录、主资源目录和测试资源目录。这就是maven项目结构的约定。
紧接着超级pom为核心插件设定版本。
<pluginManagement>  <plugins>    <plugin>      <artifactId>maven-antrun-plugin</artifactId>      <version>1.3</version>    </plugin>    <plugin>      <artifactId>maven-assembly-plugin</artifactId>      <version>2.2-beta-4</version>    </plugin>    <plugin>      <artifactId>maven-clean-plugin</artifactId>      <version>2.3</version>    </plugin>    <plugin>      <artifactId>maven-compiler-plugin</artifactId>      <version>2.0.2</version>    </plugin>    ...  </plugins></pluginManagement></build>

这里就不一一列举了。

maven反应堆

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


原创粉丝点击