目录

  • 原文迁移
  • 一、依赖管理
  • 二、插件管理
  • 三、约定优于配置
  • 四、反应堆


在实际项目的使用中,常常会对项目进行模块划分,以降低耦合。如服务接口模块,各业务模块,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

在开发过程中,灵活应用上述参数,可以帮助我们跳过无须构建的模块,从而加速构建,尤其在项目庞大,模块众多的时候。