首先必须推荐的这本书《Maven实战》 许晓斌,机械工业出版社
Maven简介
其实使用Maven也有很久时间了,大部分都是别人建好了工程我使用一下,实际上并没有非常详细的使用经验,这次到新公司来,逼着自己从头开始搭建一个Maven工程,但有了以前的经验,上手还是很快的。
Maven是在Ant之后出现的,能够自动下载构建并管理依赖,这是它与Ant最大的区别。Ant也能实现生命周期的管理,但与Maven相比,付出的成本要更高一下。
安装和配置
下载Maven
http://maven.apache.org/download.html
安装Maven
- 配置M2_HOME
在系统中新建环境变量,名为M2_HOME,变量值为Maven的安装目录,如D:inapache-maven-3.0。
- 配置Path
Path变量末尾加上%M2_HOME%in,多个值之间要用分号分开
- 测试
在命令行下,运行mvn –v,如果出现maven版本号信息则安装成功
安装最佳实现
- 设置MAVEN_OPTS环境变量
通常需要设置MAVEN_OPTS为-Xms128m –Xmx512m,用于防止Maven编译大文件时出错
- 配置用户范围settings.xml
Maven用户可以配置$M2_HOME/conf/settings.xml或者~/.m2/setting.xml,前者为全局范围,后者为用户范围
- 不使用IDE内嵌的Maven
设置eclipse关联Maven到安装的maven上
新建Maven工程
我们可以选择使用eclipse,用其提供的Archetype生成项目骨架,Archetype有很多,通常情况下使用最简单的就可以。
(1)
(2)
(3)
编写自己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.emcc.changedig</groupId>
<artifactId>extract-engine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>extract-engine</name>
<url>http://maven.apache.org</url>
<!--在POM范围内生效的变量-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.11</junit.version>
<mybatis.version>3.1.1</mybatis.version>
<mysql.driver.version>5.1.35</mysql.driver.version>
</properties>
<!--所有的依赖都配置在这个地方-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.2.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<!--用于指定打包后的main函数-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>false</addClasspath>
<mainClass>com.emcc.changedig.extractengine.ContentExtractMain</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!--the shade plugin can package all the dependency to the jar,可以将所有的依赖打成一个包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration> <atri
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.emcc.changedig.extractengine.App</mainClass> </transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Main函数的处理与打包
Maven打包时通常不会将main函数打入到manifest中,需要使用package过程插件,shade插件可以将所有的class打入到一个jar中,在storm的使用中非常用于。默认的打包插件maven-jar-plugin可以将main函数打入manifest中。
坐标和依赖
什么是Maven坐标
Maven中的坐标是用来定义一个构件位置的条件,世界上的任何一个构件都可以通过Maven的坐标唯一标识,Maven的坐标包括groupId、artifiactId、version、packaging和classifier。
现在只要提供构件的坐标,就能找到对应的构件。
我们在构件自己的项目时,也要为自己的项目定义构件。
<modelVersion>4.0.0</modelVersion>
<groupId>com.emcc.changedig</groupId>
<artifactId>extract-engine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>extract-engine</name>
<url>http://maven.apache.org</url>
坐标详解
- groupId
定义当前Maven项目所属的实际项目,groupId不应是公司或组织名,应该定位到项目名称。
如com.emcc.changedig
- artifactId
定义该实际项目中的一个Maven项目(模块),推荐做法是使用实际项目名称作为artifactId的前缀。
- verison
当前Maven项目所属的版本
- packaging
定义maven项目的打包方式,可以是jar包或者是war包,如果不配置,默认是jar包
- classifier
用来帮助定义构件输出的一些附属构件,注意:不能直接定义classifier,因附属构件不是项目直接生成的,而是由插件生成的。
可以使用插件,打包javcdoc和源码包等。
依赖的配置
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
- groupId,artificatId,version
依赖的基本坐标,Maven根据该坐标找到所需要的依赖
- type
依赖的类型,对应于项目坐标定义的packaging,通常情况下不必声明,默认为jar
- scope
依赖的范围
- optional
标记依赖是否可选
- exclusions
用来排除传递性依赖
依赖范围
也就是依赖的scope属性的值,只所以需要scope属性,是因为在整个开发周期中,使用的依赖可能不同。
Maven在编译项目主代码的时候需要使用一套classpath;在编译和执行测试时需要另外一套classpath;在实际运行时又会使用另外一套classpath。
依赖范围就是用来控制依赖于这三种classpath(编译classpath,测试classpath,运行classpath)的关系,maven有如下几种依赖范围:
- compile
编译依赖范围,如果没有指定则默认使用该范围,使用此范围的依赖,对编译、测试和运行均有效。典型的为spring-core。
- test
测试依赖范围,使用该范围的依赖仅对测试classpath有效,在编译期或运行期均无法使用此类依赖,典型的为junit。
- provided
已经提供依赖范围,对于编译和测试有效,对运行时无效,典型的例子是servlet-api,因容器已经提供,不需要maven来引入了。
- runtime
运行时依赖范围,改用该范围的maven依赖,对测试和运行有效,对编译无效。典型例子是jdbc实现。
- system
系统依赖范围,和provided依赖范围完全一致,但使用system依赖范围时必须通过systemPath元素显式指定依赖文件的路径,由于此类依赖不是maven解析的,而且往往与本机系统绑定,因此需要慎用。其中systemPath可以引用环境变量。
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdex</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
- import
导入依赖范围,不会对三种classpath产生实际影响。
传递性依赖
考虑这样一种场景:
在自己工程中pom.xml中所依赖的组件,其又依赖了他自己的依赖,像这种情况应该怎样处理呢。
Maven的传递性机制可以很好的解决这个问题。
如本地项目依赖了org.springframework:spring-core:2.5.6,其在中央仓库也有自己的pox.xml,maven会读取该pom.xml并将依赖加入到本地项目中。
其中依赖的scope也是可以传递过来的。
Maven解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式加入到当期的项目中。
依赖范围有它自己的计算方式:
如果A依赖于B,B依赖于C,那么A对于B是第一依赖,B对C是第二依赖,A对C是传递性依赖。
第一列表示第一直接依赖,第一行表示第二直接依赖,交叉点为传递性依赖。
依赖调解
如果项目中存在这样的依赖关系A->B->C-X(1.0),A->B->X(2.0),X是A的传递性依赖。那么项目中究竟会选择哪个X作为依赖呢。
实际上,Maven处理这种情况有两个原则:
- 路径最近者优先
如上面的依赖路径长度一个是3,一个是2,就取路径为2的依赖,也就是X(2.0)
- 第一声明优先
在路径长度相同的情况下,谁先声明,就解析谁。
可选依赖
假设存在这样一个依赖关系,项目A依赖项目B,项目B依赖项目X和Y,且B对于X和Y都是可选依赖:A->B,B->X(可选),B->Y(可选)。
选择依赖应用于特殊的场景,如B实现了两种特性,且这两种特性是互斥的,用户不能同时使用。所以,在构建B时,只会依赖一种数据库。
如B的依赖如下:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-b</artifact>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
上面的pom中,使用optional元素表示的依赖是可选依赖,该依赖不会根据依赖传递原则传递到A,如果要使用,需要在项目A的pom.xml中声明依赖。
最后需要说明的是,可选依赖,在理想情况下,是不被推荐的,最好是将一个项目拆分为两个项目共同管理。
最佳实践
- 排除依赖
传递性依赖会隐式的给项目带来很多依赖,这简化了项目管理,但有时候也会带来问题,如依赖了某个SNAPSHOT版本,此时,就需要显示的排除该依赖,并重新声明依赖。
可以使用exclusions元素声明排除依赖,一个exclusions元素中可以包含多个exclusion子元素。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxm.mvnbook</groupId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-c<artifactId>
<version>1.0.0</version>
<exclusions>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-c</artifactId>
<exclusions>
</dependency>
<dependency>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-c</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
</project>
通过上面的pom,排除了project-c并引入了1.1.版本。
- 归类依赖
如对于同一个版本号的依赖,可以通过建立宏类似的归类依赖,来管理同一个版本号的依赖。
这样升级时较为简便和可靠。
可以在pom.xml中声明<properties>配置,如:
<properties>
<springframework.version>2.5.6</springframework.version>
</properties>
<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>
</dependencies>
- 优化依赖
3.1 查看已解析依赖 mvn dependency:list
显示了项目中所有的依赖和依赖状态。
3.2 查看依赖树 dependency:tree
3.3 分析依赖 dependency:analyze
可以使用该命令分析项目中的依赖,需要注意的是
l 尽量显示声明项目中用的依赖
l 未使用的依赖可能在runtime期使用,需要慎重删除
仓库
何为maven仓库
得益于坐标机制,任何maven项目使用的任何一个构件都是完全相同的,在此基础上,Maven可以在某个位置统一存储所有Maven项目所共享的构件,这个统一位置就是Maven仓库。
实际的Maven项目将不再各自存储其依赖文件,而是只需要声明这些依赖的坐标。
仓库的布局和分类
Maven仓库中构件的存储是大致对应于groupId/artificatId/version/artifactId-version来存储的,在出现问题时,我们可以很方便的找到构件。
对Maven来说,仓库只分为两类,本地仓库和远程仓库。Maven根据坐标寻找构件的时候,先会查看本地仓库,如果本地仓库没有,则会去远程仓库下载,如果远程仓库也没有就会报错。
远程仓库可以分为中央仓库和私服两种。中央仓库是Maven自带的远程仓库,包含了大多数开源构件,在默认配置情况下,如果没有配置本地仓库,则会从远程仓库下载。
私服是一种特殊的远程仓库,为了节省带宽和时间,可以在局域网内搭建一台私有的仓库服务器,用其代理远程仓库,称为私服。
远程仓库的配置
很多情况下,默认的中央仓库无法满足项目需求,可以构件存在于另外一个远程仓库中,如JBoss Maven库,这时,可以在POM中配置该仓库。
<project>
…
<repositories>
<repository>
<id>jboss</id> //id唯一,否则会覆盖其他仓库
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url> //基于http协议,可以浏览器访问
<release>
<enable>true</enable> //开启发布版本下载支持
</release>
<snapshots>
<enable>false</enable> //关闭快照版本下载支持
<updatePolicy>daily</updatePolicy> //每天检查下载(never,always,interval:10)
<checksumPolicy>ignore</checksumPolicy> //文件校验策略
</snapshots>
<layout>default</layout> //maven2或maven3的布局
</repository>
</repositories>
…
</project>
远程仓库认证
为防止非法访问,管理员可以为maven库配置访问密码。
与仓库系统不同,仓库的认证配置信息必须配置到setting.xml中,因为pom.xml一般会被提交,而settings.xml是本机配置,更为安全。
<settings>
…
<servers>
<server>
<id>my-proj</id>
<username>repo-user</username>
<passward>repo-pwd</password>
<server>
</servers>
…
</settings>
部署至远程仓库
Maven除了能将项目进行编译、测试和打包外,还能将项目生成的构件部署到仓库中,首先要配置编辑项目的pom.xml,并配置distributeManagement元素。
<project>
…
<distributionManagement>
<repository> //发布版仓库
<id>project-release</id>
<name>Proj-release</name>
<url>http://192.168.1.100/content/repositories/proj-release</url>
</repository>
<snapshotRepository> //快照版仓库
<id>proj-shapshots</id>
<name>Proj Snapshot Repository</name>
<url>http://192.168.1.100/content/repositories/proj-shapshots</url>
<snapshotRepository>
<snapshotRepository>
<snapshotRepository>
</distributionManagement>
…
</project>
在部署时,可能也需要用户名和密码,可以在settings.xml中进行配置,之后运行命令:
mvn clean deploy,maven就会把构件部署到远程仓库,如果是快照版本则部署到快照仓库。
快照版本
Maven的快照机制是为了解决协同开发时用于相互依赖不稳定的构件导致的协调困难而解决的。
可以通过定义快照版本,让写作者使用,待稳定后改为release版本发布。
从仓库解析依赖的机制
当本地仓库没有依赖构件的时候,Maven会自动从远程仓库下载;当依赖为快照版本的时候,会自动找到最新的快照。可以总结如下:
- 当依赖的范围是system时,直接从本地文件系统解析构件
- 当依赖坐标计算仓库路径之后,尝试从本地路径寻找构件,如果发现相应的构件则解析成功
- 在本地仓库不存在相应构件时,如果依赖版本是显式的发布版本构件,如1.2,2.1等,则遍历远程仓库,发现后下载并使用
- 如果依赖版本是RELEASE或LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,然后合并本地仓库,计算出RELEASE和LATEST的真实值,然后基于该值检查本地和远程仓库。
- 如果解析后为快照带时间,则更名为SNAPSHOT版本并使用
镜像
如果仓库X可以提供仓库Y的所有内容,那么就可以认为X是Y的镜像。
有时候由于地理原因,镜像可以提供比中央仓库更快的服务,因此可以配置Maven镜像来代替中央仓库,编辑settings.xml。
<settings>
…
<mirrors>
<mirror>
<id>maven.net.cn</id>
<name>one of the central mirrors in China</name>
<url>http://maven.net.cn/content/groups/public</url>
<mirrorof>central<mirrorof>
</mirror>
</mirrors>
…
</settings>
通常使用镜像是结合私服,可以通过配置镜像代理任何外部公共仓库(包括中央仓库),可以将配置集中到私服,从而简化maven本身的配置。
<settings>
…
<mirrors>
<mirror>
<id>internal-repository</id>
<name>Internal Repository Manager</name>
<url>http://192.168.1.110/maven2/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
…
</settings>
- <mirrorOf>*</mirrorOf>匹配所有远程仓库
- <mirrorOf>external:*</mirrorOf>匹配所有远程仓库,匹配所有不在本机的仓库
- <mirrorOf>repo1,repo2</mirrorOf>,匹配仓库repo1和repo2
- <mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1
- Sonatype Nexus
仓库搜索服务
- Jarvana
http://www.jarvana.com/jarvana/
- MVNbrowser
- MVNrepository
生命周期和插件
何为生命周期
Maven的生命周期就是为了对所有的构建过程进行抽象和统一,Maven从其他项目和开发中总结了一套高度完善的、易扩展的生命周期机制。
该生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和生成站点等所有的步骤。
Maven的生命周期是抽闲的,实际的任务交由插件完成,这种思想与设计模式中的模板方法类似,父类中定义整体的处理架构,子类来完成具体实现。
Maven为大多数构建步骤编写了默认插件,如maven-compiler-plugin和maven-surefire-plugin等。
生命周期详解
Maven拥有三套生命周期,相互独立,分别为clean、default和site,clean生命周期的目的是清理项目,default的生命周期目的是构建项目,而site生命周期是为了建立项目站点。
每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖前面的阶段,如clean包含pre-clean,clean和post-clean。
- Clean生命周期
1.1 pre-clean执行一些清理前需要完成的工作
1.2 clean执行上一次构件生成的文件
1.3 post-clean执行一些清理后需要完成的工作
- default生命周期
default生命周期包含了真正需要构件时所需要执行的所有步骤,它是所有生命周期中最为核心的部分。
2.1 validate
2.2 initialize
2.3 generate-sources
2.4 process-sources
处理项目的主资源文件,一般来说,是对/src/main/resources目录的内容进行变量替换之后,复制和输出到主的classpath目录去。
2.5 generate-resources
2.6 process-resources
2.7 compile 编译项目主代码,一般来说是编译src/main/java目录下的java文件到项目输出的主classpath中去
2.8 process-classes
2.9 generate-test-sources
2.10 process-test-resources 处理项目的测试资源文件,一般来说,是对src/test/resources目录的内容进行变量替换工作后,复制到项目输出的测试classpath目录中
2.11 generate-test-resources
2.12 process-test-resources
2.13 test-compile 编译项目的测试代码,一般来说是编译src/test/java目录下的java文件至项目输出的测试classpath目录中
2.14 process-test-classes
2.15 test 使用单元测试框架运行测试,测试代码不会被打包和部署
2.16 prepare-package
2.17 package 接受编译好的代码,打成可发布的格式,如JAR
2.18 pre-integration-test
2.19 integration-test
2.20 post-integration-test
2.21 verify
2.22 install 将安装包部署至Maven本地仓库,供本地开发人员使用
2.23 deploy 将最终发布包复制到远程仓库,供其他开发人员和Maven项目使用
- site生命周期
site生命周期的目的是建立和发布站点,Maven能够根据pom的信息,自动生成一个友好的站点,方便团队交流好发布项目信息。
3.1 pre-site 执行一些在生成项目之前需要完成的工作
3.2 site 生成项目站点文档
3.3 post-site 执行一些在项目站点生成之后需要完成的工作
3.4 site-deploy 将项目站点发布到服务器上
命令行和生命周期
- $mvn clean
- $mvn test
- $mvn clean install
- $mvn clean deploy site-deploy
插件目标
Maven的核心仅仅定义了抽象的生命周期,具体任务是交由插件完成的,插件以独立构件存在。
对于插件本身,为了能够复用代码,往往可以完成多个任务。
maven-dependency-plugin有10多个目标,每个目标对应了一个功能,例如:
dependency:analyze,dependency:tree等
插件绑定
Maven的生命周期于插件相互绑定,用以完成实际的构件任务,具体而言,是生命周期阶段与插件目标的相互绑定,以完成某个具体的构建任务。
插件绑定有内部绑定和自定义绑定两周。具体不表。
插件配置
命令行插件配置
使用-D参数。
$mvn install –Dmaven.test.skip=true
POM中插件全局配置
如果某个插件的配置值对整个项目生效,且不经常改变,可以将其配置到pom.xml中,如我们配置编译插件编译1.5版本的源文件并生成与JVM1.5兼容的字节码。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compile-plugin</artifactId>
<version>2.1</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
POM中插件任务的配置
除了为插件配置全局参数,用户还可以为某个插件任务配置特定的参数。如maven-antrun-plugin,它有一个run目标,可以调用Ant任务,用户将maven-antrun-plugin:run绑定到多个生命周期阶段上,再加上不同的配置,就可以实现在不同的生命周期执行不同的任务。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>ant-validate</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo>I’m bound to validate phase.</echo>
</tasks>
</configuration>
</execution>
<execution>
<id>ant-verify</id>
<phase>verify</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo>I’m bound to verify phase</echo>
</tasks>
</configuration>
<execution>
</executions>
</plugin>
</plugins>
</build>
获取插件信息
- 在线插件
Apache http://maven.apache.org/plugins/index.html
Codehaus http://mojo.codehaus.org/plugins.html
需要注意的是Codehaus可靠性较差
插件解析机制
与依赖构件一样,插件构件同样依赖于坐标存放在Maven仓库中,在需要的时候,Maven会到本地仓库查找,如果不存在则从远程仓库下载。
插件远程仓库的配置使用pluginRepositories和pluginRepository。
一般来说中央插件库能够完全满足需要,我们没有必要去配置其他插件库。
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enable>false</enable>
</snapshots>
<release>
<updatePolicy>never</updatePolicy>
</release>
</pluginRepository>
</pluginRepositories>
默认可以不填写groupId,如果插件是Maven官方插件。
聚合与集成
聚合和集成对Maven来说是非常必要的,Maven于其他面向对象编程语言一样,都试图减少重复代码并提供继承机制,来减少对pom.xml的配置工作量。
聚合
想象这样一种应用场景,如果我们的工程有两个模块,我们想通过一次maven命令的执行来构建这两个模块,Maven的聚合特性就是为这种需求服务的。
为了能够通过执行一次命令就构件两个模块,我们需要创建一个额外的模块,然后通过该模块来构件整个项目。
相关代码如下:
<project xmlns=”http://maven.apache.org/POM/4.0.0 “ xmlns=”xsi=”http://www.w3.org/2001/XMLSchemma-instance” ..>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-aggregator</artifactId>
<packaging>pom</packaging>
<name>Account Aggregator</name>
<modules>
<module>account-email</module>
<module>account-persist</module>
</modules>
</project>
Modules配置节配置的是子模块的位置,注意需要制定子模块的位置,目录是相对该pom来说的。
继承
为了解决重复问题,Maven设计了pom的继承规则,普通pom继承超级pom,同样的,普通的pom也可以继承依赖和插件等信息,可以继承的项有一个专项列表,这里不赘述。
我们需要创建POM的父子结构,然后提供一些元素给子POM继承,来实现一处生命,多处使用的目的。
<project>
<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>
</project>
同样的,子模块也需要修改才能继承父模块的pom,主要是子pom需要加入parent这一配置节,如下所示:
<parent>
<groupId>com.juvenxu.mvnbook.account<groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
其中parent中groupId,artifactId和version指明了父模块的坐标,元素relativePath表示父模块的相对路径。
最后,还需要把account-parent加入到聚合模块account-aggreagator中去。
<modules>
<module>account-parent</module>
<module>account-email</module>
<module>account-persist</module>
</modules>
另外,用户可以在一个POM中,同时提供聚合和集成的功能。
但是还有一种情况:我们在父pom中定义的依赖,不能全部被子pom继承,这样会引入大量的不要的引用,基于此,maven提供了依赖声明和插件声明,用户在父pom中进行依赖生命和插件声明,并定义其版本号,在子pom中继承该pom并声明具体使用哪个依赖。
如此,可以将依赖的版本号设置为全局可控,通常而言,这样的设置已经可以满足要求。
实例如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
这里使用的dependencyManagement不会给子pom引入依赖,但这些配置是可以被继承的,除了继承父pom中的dependencyManagement外,还可以继承其他子工程的dependencyManagement。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
反应堆
在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是其本身,但是多模块而言,反应堆就包含了各个模块的构件关系,maven在构建时,可以自动判别依赖,并调整构建顺序。
另外,在构建时,maven还提供了多种命令,来指定具体的构件模块和构建顺序。