maven从入门到精通
maven的作用
- 自动化构建,管理整个构建周期:清理、编译、测试、生成报告、打包、部署
- 依赖管理
- 项目信息管理:项目描述、开发者、版本、许可证等,可以将信息自动生成站点
- 仓库管理:提供免费的中央仓库
- 敏捷实践:测试驱动开发(TDD)、持续集成等
安装
- maven官网下载安装包,解压
- 配置环境变量 M2_HOME=安装目录 PATH=$M2_HOME/bin
- 配置settings.xml
- 把安装包内的settings.xml复制到$HOME/.m2/settings.xml
- 修改localRepository为本地仓库目录
提示
IntellJ IDEA中已经集成了maven,在本地开发构建时也可以直接使用IDEA内置的maven插件。
构件
在项目中每一个pom(Project Object Model) 文件通过package命令打包后都会生成一个构件(artifact 也可翻译为制品),构件的唯一性通过坐标来确定。而构件之间又有依赖关系,在pom文件中通过dependencies来管理依赖,有些人也会不太规范地把构件称为依赖 dependency。
提示
maven中有一类比较特殊的构件是插件,在插件章节中说明
坐标
Maven中通过坐标来标识构件,构成坐标的元素(4+1):groupId,artifactId,version,packaging,classifier。
- groupId 一般是项目名称
- artifactId 是项目中的模块名称
- version 版本,关于快照版本和发布版本见仓库分类
- packaging 打包方式,可以是jar/war/pom等,默认值是jar
- classifier 当上述4要素无法唯一确定坐标时就要用到这个字段,可以简单理解为标签或分类,例如可以是:javadoc、sources、jdk7
构件的文件名规则:artifactId-version[-classifier].packaging
提示
4要素通过pom文件直接定义,但classifier不能直接定义,需要通过附加插件生成。例如javadoc插件可以生成javadoc的classifier。
依赖范围
maven在引入依赖时是需要指定依赖范围的,也可以理解为作用域(scope),依赖只在指定作用域内生效。
依赖范围一共有以下几种:compile,test,provided,runtime,system,import
maven项目过程中有三种classpath:编译期classpath,测试期classpath,运行期classpath
- compile 默认的依赖范围,在编译、测试、运行阶段都产生作用
- test 只在测试阶段有用,例如junit,在编译和运行阶段都无法使用
- provided 只在编译和测试阶段有效,运行时无效(即不参与打包)。例如servlet-api,因运行时容器已提供,所以运行时无需引入
- runtime 只对测试和运行阶段有效
- system 与provided相同,不同的是需要用systemPath引入本地jar包,不推荐使用
- import 只在
dependencyManagement结点中使用,用于import另一个pom的dependencyManagement
依赖传递性
依赖的传递性可以简单理解为:A依赖了B,B依赖C,那么A会自动依赖C
依赖范围会对传递性的影响,如下表,
第一列表示A对B的依赖范围,第一行表示B对C的依赖范围,中间结果表示A对C的依赖范围
| compile | test | provided | runtime | |
|---|---|---|---|---|
| compile | compile | - | - | runtime |
| test | test | - | - | test |
| provided | provided | - | provided | provided |
| runtime | runtime | - | - | runtime |
注意
- test依赖不会传递
依赖冲突
有依赖就会产生依赖冲突,例如存在C的两个版本C1和C2,现有以下两个依赖链路:
A -> B -> C1
A -> C2
这时C存在依赖冲突,maven有一个依赖仲裁机制:
- 路径最短优先
- 路径长度相同时,最先声明优先
上述情况因C2的依赖路径最短,因此C2最终生效。
如果依赖仲裁结果不是预期结果,可以通过调整依赖路径长度,或使用exclusions来排除依赖
依赖分析插件
当依赖树特别复杂时,需要通过maven的dependency插件来对依赖进行分析,dependency插件是maven默认引入的,可以直接使用。
mvn dependency:list [-DoutputFile=dep.txt]可以用来查看已解析的依赖列表,只输出依赖仲裁结果mvn dependency:tree输出依赖树mvn dependency:analyze依赖分析(可以分析出使用但未声明,声明但未使用,未使用的依赖可能可以删除)
注意
dependency:analyze分析出的结果可能并不准确,因为该命令只检索java代码,不能检索到xml文件中引用的类。
仓库
仓库(repository) 是保存构件的地方。
仓库分类
按仓库所处位置来分类:
- 本地仓库 位于本地的仓库,可在settings.xml中配置
- 中央仓库 maven官方提供的中央仓库central
- 私服 个人或公司搭建的局域网仓库,可做为中央仓库的缓存
- 其他公共库 Jboss的仓库、阿里巴巴的仓库等
常用的公共仓库
按类型分:
- host 宿主仓库,可以上传或部署构件
- proxy 代理仓库,用于代理其他远程仓库
- group 仓库组,将多个仓库合并成仓库组
- virtual 虚拟仓库,只在maven1使用
按存储策略分:
- release 只存储release构件
- snapshot 只存储snapshot构件
release和snapshot
构件分为发布版本和快照版本,在version中如果以-SNAPSHOT结尾,则为快照版本,否则是发布版本。
发布版本是稳定版,使用mvn deploy部署到远程仓库时会自动部署到release仓库中,并且只能部署一次,再次部署会直接失败。
快照版本使用mvn deploy部署到远程仓库时会部署到snapshot仓库中,每次部署都会生成一个带时间戳的快照版本。
仓库配置
本地仓库配置已在安装中说明,这里介绍远程仓库配置。
在POM文件中配置仓库
仓库在pom文件的repositories结点进行配置,对一个仓库的配置示例如下:
<repository>
<id>releases</id> <!-- 仓库唯一标识 -->
<name>Releases</name> <!-- 仓库名称 -->
<releases> <!-- 配置发布版本策略 -->
<enabled>true</enabled> <!-- 允许从该仓库下载release的构件 -->
<updatePolicy>never</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</releases>
<snapshots> <!-- 配置快照版本策略 -->
<enabled>false</enabled> <!-- 禁止从该仓库下载snapshot的构件 -->
</snapshots>
<url>http://repo.yourdomain.com/nexus/content/repositories/releases/</url>
<layout>default</layout> <!-- 布局,使用默认 -->
</repository>- updatePolicy 更新策略,表示从该仓库检查更新的频率,默认值daily。其他可选值有
daily:每天检查一次
never:从不检查更新
always:每次构建都检查更新
interval:X:每隔X分钟检查一次更新 - checksumPolicy 校验和检查策略
在下载构件的时候,maven会验证校验和文件,如果验证失败就做相应处理。可选值有
warn:默认值,输出警告信息
fail:直接构建失败
ignore:忽略校验和错误
强制更新
有时会遇到jar包更新不到本地的情况,可以在执行maven命令时加上-U参数强制检查更新,如mvn clean package -U
插件仓库
远程插件仓库配置:与普通仓库相同,只是要配置到pluginRepositories结点下
在settings.xml中配置仓库
POM中配置的仓库只能给当前项目使用,如果要配置全局仓库,可以在settings.xml中配置profile和repositories,配置方式与pom相同
配置仓库镜像
仓库镜像:如果仓库X可以提供仓库Y存储的所有内容,则X是Y的一个镜像。
如果公司有搭建私服,可以在私服上配置代理仓库,另外再配置一个仓库组,这个仓库组就可以作为所有仓库的镜像。
本地开发时只需将这一个仓库配置为镜像即可。
在settings中配置:
<mirrors>
<mirror>
<id>public</id>
<mirrorOf>*</mirrorOf>
<name>Public Repositories</name>
<url>http://repo.yourdomain.com/nexus/content/groups/public/</url>
</mirror>
</mirrors>mirrorOf表示镜像的是哪个仓库,多个仓库用逗号隔开,*号表示镜像所有仓库,如上配置以后对所有其他仓库的访问都会转到该仓库上。
镜像仓库与其他仓库的区别
- 配置不同,镜像仓库不能配置更新策略
- 镜像仓库会完全屏蔽被镜像仓库,例如配置了central镜像,当镜像仓库挂掉后,maven也不会尝试再访问central(被完全屏蔽了)
- 仓库检索顺序不同
仓库的检索顺序
local_repo > settings_profile_repo > pom_profile_repo > pom_repositories > settings_mirror > central
将构件部署到远程仓库
如果需要用mvn deploy命令将构件发布到远程仓库,就需要在POM中配置distributionManagement元素,需要同时配置repository和snapshotRepository,分别用于部署发布版本和快照版本。
<distributionManagement>
<repository>
<id>proj-release</id>
<name>Release</name>
<url>http://xxxxxxx</url>
</repository>
<snapshotRepository>
<id>proj-snapshot</id>
<name>Snapshot</name>
<url>http://xxxxxxx</url>
</snapshotRepository>
</distributionManagement>仓库认证:通常发布构件需要私服的上传和发布权限,以nexus私服为例,默认发布账号是deployment,需要配置settings.xml中的servers结点,server id即仓库ID
<servers>
<server>
<id>releases</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>snapshots</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>构件在仓库中的存储
存储位置:groupId/artifactId/version/artifactId-version[-classifier].packaging
解析依赖的机制:
- 当依赖范围是system时,maven直接从本地文件系统解析
- 根据依赖的坐标计算构件路径,先尝试从本地仓库寻找
- 如果本地仓库中不存在,则遍历所有远程仓库(如果没有配置远程仓库,默认会查找中央仓库)
- 如果仓库配置了镜像,则从镜像仓库中查找
- 如果依赖的是RELEASE或LATEST或快照版本时,需要根据更新策略来检查是否有版本更新,如果有则将远程仓库中的构件更新到本地仓库
构件的最新版本信息存储于仓库的元数据 中(maven-metadata.xml)
release构件元数据:groupId/artifactId/maven-metadata.xml
snapshot构件元数据:groupId/artifactId/version/maven-metadata.xml
生命周期
maven将软件生命周期进行了抽象,生命周期是相对固定的,由一组阶段(Phase) 组成,每个阶段绑定插件来完成相应任务。
有点像设计模式中的模板方法(Template Method),maven定好了生命周期的主体框架,保证框架的一致性,但每个阶段又能灵活定制。
maven有三套生命周期,每个生命周期由一组有顺序的阶段(Phase)组成,后面的阶段依赖前面的阶段,三套生命周期之间互相独立。
clean生命周期
用于清理项目,有3个阶段组成:
- pre-clean
- clean:清除上次构建生成的文件(删除target目录)
- post-clean
default生命周期
用于构建项目,这个生命周期使用最多
- validate:校验项目必须的信息是否正确
- initialize
- generate-sources
- process-sources
- generate-resources
- process-resources:处理项目主资源文件。一般是将src/main/resources中的内容进行变量替换后复制到classpath中
- compile:编译源码,输出到classpath中
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources
- test-compile
- process-test-classes
- test:使用单元测试框架运行测试,测试代码不会被打包或部署
- prepare-package
- package:将编译好的代码打包成可发布的格式
- pre-integration-test
- integration-test
- post-integration-test
- verify:基于集成测试的结果进行检查以确保质量标准
- install:将包安装到本地仓库,提供给本地其他项目使用
- deploy:将包部署到远程仓库,供其他项目使用
site生命周期
用于建立项目站点,一般很少使用,有以下4个阶段组成
- pre-site
- site:生成项目站点文档
- post-site
- site-deploy:将生成的项目站点发布到服务器上
maven生命周期参考文档:http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
maven允许直接调用阶段,如mvn clean install site,因三个生命周期相互独立,所以可以同时调用。
插件
maven通过在生命周期的阶段上绑定插件目标的方式,把具体任务交给插件来完成。
每个插件一般都会提供多个插件目标(Goal) ,例如前文提到的dependency插件就有list、tree、analyze等goal,
以及maven-compiler-plugin插件提供的compilegoal,把goal与阶段绑定即可实现对插件的调用。
maven提供了一些阶段和插件目标的内置绑定关系,可以应对绝大多数日常使用,因此调用mvn compile实际上是调用的maven-compiler-plugin插件的compilegoal。
提示
插件的目标也可以直接调用,即使用mvn 插件名:目标的方式,例如上面提到的mvn dependency:list。
但直接调用目标就脱离了maven的生命周期,一般只用于一些工具类的插件。
插件的默认阶段
有一些插件的目标在编写时会绑定到默认阶段,例如上面提到的compile就是默认绑定的。
使用maven-help-plugin插件来查看其它插件的详细信息以及默认绑定信息(help插件和被查询的插件都要在工程里),命令:
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1.1 -Ddetail
自定义绑定
例如,把maven-help-plugin插件的describe目标绑定到verify阶段上(只是举个例子,一般不会这么绑定):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>help</id>
<phase>verify</phase>
<goals>
<goal>describe</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>插件groupId可以省略
有些项目中会看到引入插件并没有配置groupId,那是因为maven内置了两个groupId:org.apache.maven.plugins 和 org.codehaus.mojo,即这两个groupId可以省略不写。
在settings.xml中的pluginGroups可以配置额外的插件groupId
另,插件的元数据比较特殊,存储于groupId/maven-metadata.xml中
模块
开发maven项目,通常都不会是单模块项目,其最佳实践是新建一个父模块,packaging设置为pom,并在父模块POM文件中配置modules,如:
<groupId>com.xxx</groupId>
<artifactId>yyy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>yyy-controller</module>
<module>yyy-service</module>
<module>yyy-dao</module>
</modules>每个module既是子模块,又是一个与当前pom同级目录,添加子模块后,只需构建顶级模块,子模块会同时构建
模块的继承
子模块的好处:
- 可以一个命令构建完整个项目,而不必每个模块都单独构建
- 子模块可继承父模块的POM元素
能够被继承的元素有:
- groupId
- version
- description
- organization
- inceptionYear:项目创始年份
- url:项目的URL地址
- developers:开发者
- contributors:贡献者
- distributionManagement:项目部署配置
- issueManagement:项目缺陷跟踪系统信息
- ciManagement:项目持续集成系统信息
- scm:项目版本控制系统信息
- mailingLists:项目邮件列表
- properties:自定义maven属性
- dependencies:依赖配置
- dependencyManagement:依赖管理配置
- repositories:仓库配置
- build:构建配置,包括源码目录、输出目录、插件、插件管理
- reporting:项目报告配置,包括输出目录,报告插件配置
注意
reporting配置在maven 3.0已废弃,整合到maven-site-plugin的配置中去了
提示
任何maven项目都隐式继承超级POM,类似Java的Object。超级POM位置:$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml
一些元素说明:
dependencyManagement:该元素下的依赖声明不会引入实际的依赖,只是把声明继承给子模块,子模块在配置依赖时只需配置groupId和artifactId即可,未配置的属性(version、scope等)都从dependencyManagement中继承。
pluginManagement:与dependencyManagement类似
构建反应堆
如果把多模块maven项目中的所有模块的依赖关系用图来表示,这个图结构即为构建反应堆(Reactor) 。反应堆应该是一个有向非循环图,如果模块间出现循环依赖则会报错。
反应堆的构建顺序:
- 按声明顺序构建;
- 如果一个模块依赖与另一个模块,先构建被依赖模块。
裁剪反应堆:有些项目非常大,构建时可以选择只构建某些模块以提高构建速度,通过在mvn命令中指定以下参数可以对反应堆进行裁剪。
-pl p1 [,p2 ,p3 ......] 选择指定模块
-am also make,同时构建所选模块的依赖模块
-amd also make dependecies,同时构建依赖于所选模块的模块
测试
maven的default生命周期中有一个test阶段专门用于执行单元测试,默认单元测试插件:maven-surefire-plugin,绑定目标test
该插件会自动检测src/test/java下以Test开头的类、以Test结尾的类、以TestCase结尾的类用于执行单元测试。
在插件的includes和excludes,可以配置额外包含或排除的测试类。
使用命令mvn test -Dtest=测试类名可以只测试某个类
使用-DskipTests或-Dmaven.test.skip=true参数可以跳过单元测试
生成测试覆盖率报告(与其他报告相同,输出在site目录下)
使用到插件:cobertura-maven-plugin
生成站点
这里的站点是指包含本项目信息的一些静态界面,包括项目的基本信息、依赖信息、测试报告、代码检查报告等
生成站点
使用mvn site命令可以将项目信息生成站点到target/site
提示
针对多模块项目,可能更希望把所有模块的信息汇总一个目录,可以使用mvn site:stage,默认汇总到target/staging,但必须先执行mvn site
在站点中增加静态检查报告:(findbugs必须先package生成classes)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<locales>zh_CN</locales>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.17</version>
<configuration>
<configLocation>mucfc-checkstyle-1.0.xml</configLocation>
</configuration>
</reportPlugin>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>2.7.1</version>
<configuration>
<targetJdk>${jdk.version}</targetJdk>
</configuration>
</reportPlugin>
<reportPlugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.4</version>
</reportPlugin>
<!-- 在报告中查看源码插件 -->
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.5</version>
</reportPlugin>
</reportPlugins>
</configuration>
</plugin>自定义生成其他项目信息:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.9</version>
<reportSets>
<reportSet>
<reports>
<report>dependencies</report>
</reports>
</reportSet>
</reportSets>
</plugin>report可配置如下值:
- cim:持续集成报告
- dependencies:项目依赖报告
- dependency-convergence:依赖聚合报告(分析依赖问题)
- dependency-info:为其他构建工具生成本项目的依赖代码片段(方便复制,没什么用)
- dependency-management:依赖管理报告
- distribution-management:分发管理报告
- help:生成帮助文档
- index:生成首页
- issue-tracking:问题跟踪管理报告
- license:生成证书报告
- mailing-list:邮件列表
- modules:模块
- plugin-management:插件管理
- plugins:插件
- project-team:项目成员
- scm:项目源码管理报告
- summary:摘要
发布站点
使用mvn site-deploy命令可以生成站点,并部署到远程服务器
把站点部署到本地文件系统
<distributionManagement>
<site>
<id>site</id>
<url>file:E:\\site</url>
</site>
</distributionManagement>部署到远程(远程服务器需要安装ssh)
<distributionManagement>
<site>
<id>stagingSite</id>
<url>scp://192.168.0.100/home/report/test</url>
</site>
</distributionManagement>maven属性
在maven pom文件中可以通过${}来引用属性,属性分为以下几类
内置属性
${basedir}:项目根目录,pom所在目录
POM属性
可以引用POM文件中对应元素的值,如${project.groupId}对应project->groupId的值
- ${project.build.sourceDirectory}:源码目录,默认src/main/java
- ${project.build.testSourceDirectory}:测试源码目录,默认src/test/java、
- ${project.build.directory}:项目构建输出目录,默认target/
- ${project.outputDirectory}:项目主代码编译输出目录,默认target/classes/
- ${project.testOutputDirectory}:测试编译输出目录,默认target/test-classes/
- ${project.build.finalName}:打包输出的文件名,默认${project.artifactId}-${version}
settings属性
以settings开头,引用settings.xml中的元素
如${settings.localRepository}:本地仓库地址
Java系统属性
如$
环境变量属性
以env.开头,如$
自定义属性
在pom的properties结点中定义的属性
Java系统属性和环境变量属性都可以用mvn help:system查看
提示
在springboot中,properties文件中可以通过@xxx@直接引用pom属性
一些最佳实践
配置编码格式
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.8</jdk.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>maven 版本号约定
版本号:主版本.次版本.增量版本-里程碑版本。
如2.0.1-alpha-1,3.1.0-release。
增量版本和里程碑版本可以省略。
通常快照版本以-SNAPSHOT结尾
让父子工程的版本号保持一致
一般父工程pom定义如下:
<groupId>com.xxx</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>子工程pom定义:
<parent>
<groupId>com.xxx</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child</artifactId>这样子工程可以与父工程保持一致的版本,但有个问题是子工程需显式引用父工程的版本号,每次版本变更时需要把所有子工程的parent-version字段同时更新一遍。
在maven3.5以后提供了对revision的支持,可以让项目版本号只需配置一次。
父工程定义:
<groupId>com.xxx</groupId>
<artifactId>parent</artifactId>
<version>${revision}</version>
<properties>
<revision>1.0.0</revision>
</properties>子工程定义:
<parent>
<groupId>com.xxx</groupId>
<artifactId>parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>child</artifactId>