目录
- 原文迁移
- 一、依赖管理
- 二、插件管理
- 三、约定优于配置
- 四、反应堆
在实际项目的使用中,常常会对项目进行模块划分,以降低耦合。如服务接口模块,各业务模块,web模块等。而模块间共享一些相同的依赖,彼此间也紧密联系。此时我们就可以通过maven的聚合和继承来管理模块。
比如现在我们有以下模块:
- example-api
- example-service
- example-web
模块间的关系是example-web和example-service通过example-api相连接。
example-web -> example-api <- example-service
此时我们可以创建一个聚合模块example-parent,也可以称作父模块。它的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.ioteye.maven</groupId>
<artifactId>example-parent</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>example</name>
<modules>
<module>example-api</module>
<module>example-service</module>
<module>example-web</module>
</modules>
</project>
对于聚合模块来说,其打包方式packaging的值必须为pom,否则无法构建。一般聚合模块的内容仅是一个pom.xml,它就是帮助聚合其他模块构建的工具。
而对于子模块来说,通过继承父pom,将公共的配置统一在父pom中管理,比如依赖的版本以及插件的管理。
以example-api为例
<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>
<parent>
<groupId>com.lcifn.maven</groupId>
<artifactId>example-parent</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>example-api</artifactId>
<version>0.0.1</version>
</project>
parent标签内的子元素groupId,artifactId和version指定父模块的坐标。对于子模块,父模块的pom中很多元素是可以继承的
- groupId
- version
- distributionManagement 项目部署配置
- properties 自定义maven属性
- dependencies 项目依赖配置
- dependencyManagement 项目依赖管理配置
- repositories 项目的仓库配置
- build 包括项目源码目录配置,输出目录配置,插件配置,插件管理配置
以上只是列举了常用的一些元素
实际使用时,聚合模块和父模块往往是同一模块。
一、依赖管理
项目中依赖的spring多个组件希望都是同一版本的,这样能避免因版本问题出现的奇怪错误,可以通过maven的dependencyManagement来管理。在dependencyManagement元素下的依赖声明不会引入实际的依赖,而它可以约束dependencies下的依赖的使用。
在父模块的pom中加入配置
<properties>
<spring.version>4.1.6.RELEASE</spring.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
将常用的依赖版本以maven变量提取出来,消除重复,并方便以后升级。
在子模块的pom文件中,只需要配置groupId和artifactId就能获得对应的依赖信息,从而可以让多个模块统一使用统一版本的依赖,减少依赖冲突。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
</dependencies>
二、插件管理
maven也提供了pluginManagement元素帮助管理插件,在该元素中配置的依赖不会造成实际的插件调用行为,当子模块的pom中配置了真正的plugin元素,并且groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会产生真正的影响。
父模块的pom文件,配置maven-source-plugin,将jar-no-fork目标绑定到verify生命周期阶段,生成项目源码包。
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
当子模块需要生成源码包时,只需要简单配置
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
三、约定优于配置
maven提倡“约定优于配置”,从而可以减少大量的配置。一个项目构建要完成的最基本的事情,包括清除构建目录,创建目录,编译代码,复制依赖至目标目录,最后打包。如果使用ant,需要在配置文件中指定源码目录是什么,编译目标目录是什么,分发目录是什么等等。而使用maven后,用户付出一定的代价来遵守manven的约定,即可用非常简单的pom文件来达到相同的目的。
maven对用户目录的约定如下
- 源码目录为src/main/java/
- 编译输出目录为target/classes/
- 打包方式为jar
- 包输出目录为target/
这些默认的约定定义在maven的超级pom文件中,任何一个Maven项目都隐式继承该pom,有点类似java类都继承于Object类。对于maven3,超级pom文件在$MAVEN_HOME$/lib/maven-model-builder-3.3.1.jar中的org\apache\maven\model\pom-4.0.0.xml。
简单列举下超级pom中对项目结构的定义
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources>
</build>
四、反应堆
在一个多模块的Maven项目中,反应堆(Reactor)指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是模块自身,而对于多模块项目,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算合理的模块构建顺序。
上述example-parent的构建顺序就是
- example-parent
- example-api
- example-service
- example-web
模块间的依赖关系会将反应堆构成一个有向非循环图。当出现两个模块相互依赖时,maven会报错。
有些时候,用户会想仅仅构建完整反应堆的某些个模块,即裁剪反应堆。maven提供命令支持裁剪反应堆。
- -am,–also-make 同时构建所列模块的依赖模块
- -amd,-alos-make-dependents 同时构建依赖于所列模块的模块
- -pl,–projects 构建指定的模块,模块间用逗号隔开
- -rf,–resume-from 从指定的模块恢复反应堆
可以使用-pl指定构建某几个模块
mvn clean install -pl example-api,example-service
使用-am同时构建所列模块的依赖模块
mvn clean install -pl example-service -am
使用-amd构建依赖于所列模块的模块
mvn clean install -pl example-api -amd
使用-rf在完整反应堆构建顺序基础上指定从哪个模块开始构建
mvn clean install -rf example-service
在开发过程中,灵活应用上述参数,可以帮助我们跳过无须构建的模块,从而加速构建,尤其在项目庞大,模块众多的时候。