多模块的项目构建时很容易出现一个问题:在聚合实例中,我们可以发现account-persist和account-email的POM配置有很多重复部分,比如相同的spring-core,spring-beans等依赖,还有相同的maven-compiler-plugin和maven-resources-plugin插件配置。特别是如果版本不一的话,在test时控制台会输出警告。另外重复在项目中往往意味更多的劳动和更多的潜在问题。在面向对象世界中,程序员可以使用类继承在一定程度上消除重复,在Maven的世界中,也有类似的机制能让我们抽取重复的配置,这就是POM的继承。
在面向对象的程序设计中,程序员可以建立一种类的父子结构,然后在父类中声明一些字段,然后在父类中声明一些字段和方法供子类继承,这样可以做到“一处声明,多处使用”。类似地,我们需要创建POM的父子结构,然后在父POM中声明一些配置供子POM继承,以实现“一处继承,多处使用”的目的。
我们继续以账户注册服务为基础,在account-aggregator下创建一个名为account-parent的子目录,然后在该子目录下建立一个所有除account-aggerator之外模块的父模块。为此,在该子目录创建一个pom.xml文件,内容如下
//account-parent的POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.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>
<name>Account Parent</name>
<build />
</project>
该POM使用与其他模块一致的groupId和version,这里需要注意它的packaging为pom,作为父模块的POM打包类型必须为pom。由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java/之类的文件夹。有了父模块,就需要让其他模块继承它。首先修改account-email的POM如下:
//account-email的pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>account-email</artifactId>
<name>Account Email</name>
<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
<version>2.3.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
<version>2.5</version>
</plugin>
</plugins>
</build>
</project>
可以看到实际是将该pom中添加parent元素声明父模块,注意添加后groupId和version元素会警告,因为它与父模块相同,一般会隐式继承自父模块,所以可以去掉。如果遇到版本与父模块不同的groupId或version,则要在子模块中显示声明。对于artifactId元素来说,子元素应该显式声明。一方面,如果完全继承groupId,artifactId和version会造成坐标冲突;另一方面,即使使用不同的groupId或version,同样的artifactId容易造成混淆。
与上述POM相类似,以下是account-persist更新后的POM
//account-persist的pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>account-persist</artifactId>
<name>Account Persist</name>
<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
<version>2.3.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
<version>2.5</version>
</plugin>
</plugins>
</build>
</project>
更改和account-email一样。这里的元素relativePath表示父模块POM的相对路径。当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePath的默认值为../pom.xml,也就是说Maven默认父POM在上一层目录下。
上述POM中省略了依赖配置和插件配置。之后会介绍如何将共同的依赖配置提取到父模块中。
最后,需要把account-parent加入到聚合模块account-aggregator中
//account-aggergator的pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-aggregator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Aggregator</name>
<modules>
<module>../account-email</module>
<module>../account-persist</module>
<module>../account-parent</module>
</modules>
</project>