Maven实战
Maven简介
何为Maven
Maven 是 Apache 组织下的一个跨平台的项目管理工具,它包含了:
a.一个项目对象模型 (Project Object Model),
b.一组标准集合,
c.一个项目生命周期(ProjectLifecycle),
d.一个依赖管理系统(Dependency Management System),
e.用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。
Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。无论是小型的开源类库项目,还是大型的企业级应用;无论是传统的瀑布式开发,还是流行的敏捷模式,Maven都能大显身手。
何为构建
构建是每一位程序员每天都在做的事情。
编译、单元测试、生成文档、打包和部署等繁琐的步骤,就是构建。
Maven是优秀的构建工具
Maven能帮我们自动化构建过程,从清理、编译、测试到生成报告,再到打包和部署。
Maven是跨平台的,这意味着无论是在Windows上,还是Linux或者Mac上,都可以使用同样的命令。
Maven抽象了一个完整的构建生命周期模型。
Maven帮助我们标准化构建过程。在Maven之前,十个项目可能有十种构建方式;有了Maven之后,所有项目的构建命令都是简单一致的,降低了学习成本,促进项目团队的标准化。
Maven不仅仅是构建工具
一个依赖管理工具和项目信息管理工具:
1.Maven通过一组坐标准确定位每一个Java类库(又叫构件artifact),有序管理并解决依赖问题。
2.Maven帮助我们管理原本分散在项目中各个角落的项目信息,包括项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等;通过Maven自动生成的站点,以及一些已有的插件,还能获得项目文档、测试报告、静态分析报告、源码版本日志报告等项目信息。
Maven提供了一个免费的中央仓库,中央仓库地址: http://repo1.maven.org/maven2 。
Maven对于项目目录结构、测试用例命名方式等内容都有既定的规定,可以说约定优于配置,
为什么需要Maven
省时省力
使用脚本建立高度自定义的构建系统就像购买组装PC,在此之前,你需要了解各类配件的优劣,CPU、内存、主板、显卡,甚至声卡,跑了很多商家,终于组装好,然后自己装操作系统和驱动程序……结果,花了大量时间,机器稳定性又不好,耗时耗力,实际项目中无法给你那么多时间。使用Maven就像购买品牌PC,不同的是Maven是开源免费的,你可以去了解Maven是如何工作的,而我们无法知道那些PC巨头的商业秘密。
IDE不是万能的
IDE依赖大量的手工操作。编译、测试、代码生成等工作都是相互独立的,很难一键完成所有工作。
很难在项目中统一所有的IDE配置,一个在机器A上可以成功运行的任务,到了机器B的IDE中可能就会失败。
应合理利用IDE,而不是过度依赖。在IDE中一次次的点击鼠标是愚蠢的行为。主流IDE都集成了Maven,在IDE中可以方便地运行Maven执行构建。
不重复发明轮子
Maven管理:
搭建Web项目时需要很多jar包,很少人能一个不少地找出来,即使找出来也耗时耗力,项目还没开始,WEB-INF/lib下已经有几百个jar包了,带版本号的、不带版本号的、有用的、没用的、冲突的,怎一个乱字了得。想要删除没用的jar包,很困难。
Maven轻松管理这些jar包。
Maven规范了目录结构。
总结:
Maven构建:
1.Maven是跨平台的
2. 自动化构建过程,从清理、编译、测试到生成报告,再到打包和部署。
Maven管理:
1. 管理并解决依赖问题。
2. 管理项目信息,包括项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等;通过Maven自动生成的站点,以及一些已有的插件,还能获得项目文档、测试报告、静态分析报告、源码版本日志报告等项目信息。
Maven的安装和配置
在Windows上安装Maven
检查JDK安装
检查Java安装:
1 | echo %JAVA_HOME% # 输出JDK安装路径 |
下载Maven
历史版本: https://archive.apache.org/dist/maven/maven-3/
在Apache官网 http://maven.apache.org/download.cgi 下载最新版本:
本地安装
a.解压到本地
b.配置环境变量
1).添加环境变量MAVEN_HOME(或M2_HOME,maven版本不同,变量名可能不同,具体要看bin目录下配置java命令的脚本中使用的是什么),值为maven包的路径;
例: MAVEN_HOME = D:\apache-maven-3.5.4
2).在环境变量PATH里面添加maven的bin的路径;
%MAVEN_HOME%\bin; mvn执行脚本的位置
c.检查安装情况
echo %MAVEN_HOME% 检查环境变量是否指向正确的安装目录
mvn –v 检查能否找到正确的执行脚本
帮助:
在cmd中输入命令时,Windows首先会在当前目录中寻找可执行文件或脚本,如果没找到,Windows会遍历环境变量Path中定义的路径。
打开新的cmd窗口检查安装情况,新的环境变量配置需要新的cmd窗口才能生效。
升级Maven
下载新的Maven,解压到本地,更新MAVEN_HOME环境变量即可。
安装目录分析
MAVEN_HOME
bin: 包含mvn运行的脚本,用来配置Java命令,不带后缀的是UNIX平台脚本,带后缀的是Windows平台。
boot: 只包含一个文件plexus-classworlds-2.5.2.jar,plexus-classworlds是一个类加载器框架,提供了更丰富的语法以方便配置,Maven使用该框架加载自己的类库。对一般的Maven用户来说,不必关心该文件。
conf: settings.xml,全局定制Maven行为。
toolchains.xml,设置工具链安装路径的配置文件;例如希望使用不同的JDK版本构建项目,首先在pom.xml中通过插件配置要使用的版本,
当工具链插件执行时,它将在toolchains.xml中查找符合要求的工具链,把找到的JDK工具链设置到MavenSession中,从而使用该工具链(即安装在 /path/to/jdk/1.5 下的JDK)编译源代码。
lib: 包含所有Maven运行时需要的Java类库,Maven是分模块开发的,因此用户能看到诸如maven-core-3.5.4.jar、maven-model-3.5.4.jar之类的文件。
还包含Maven用到的第三方依赖,如commons-io-2.5.jar、commons-lang3-3.5.jar等。
lib目录就是真正的Maven。
用户可以在这个目录中找到Maven内置的超级POM,后面详细讲解。
LICENSE.txt: 记录了Maven使用的软件许可证
NOTICE.txt: 记录了Maven包含的第三方软件
README.txt: Maven的简要介绍,包括安装需求及如何安装的简要指令等。
设置HTTP代理
有时公司基于安全考虑,要求使用通过安全认证的代理访问因特网。这时就要为Maven配置HTTP代理,才能让它正常访问外部仓库。
1、确认无法直接访问中央仓库:ping repol.maven.org 检查网络
2、检查代理服务器是否畅通:telnet 本机IP 代理端口
如果telnet连接正确,则输入ctrl+],然后q,回车,退出即可。
3、编辑settings.xml文件,添加代理配置如下:
1 | <proxies> |
帮助:
代理服务器(Proxy Server)是一种重要的服务器安全功能,它的工作主要在开放系统互联(OSI)模型的会话层,从而起到防火墙的作用。代理服务器大多被用来连接INTERNET(国际互联网)和Local Area Network(局域网)。
代理(英语:Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。[1]
具体详情请网上搜索“代理服务器”、“HTTP代理”。
安装m2eclipse
下载地址:http://www.eclipse.org/m2e/m2e-downloads.html
m2eclipse就是集成Maven的eclipse。
新版本的eclipse集成了Maven,不需要安装。
Maven安装最佳实践
本节介绍一些在安装Maven过程中不是必须的,但十分有用的实践。
设置MAVEN_OPTS环境变量
当项目比较大时,使用Maven生成项目需要占用大量内存,如果超过Java默认的最大可用内存,则会报 java.lang.OutOfMemeoryError。
解决此问题有两种方法:
一种为修改Maven的修改脚本文件,此方法比较麻烦,且容易忘记,不推荐;
另一种方法为设置 MAVEN_OPTS 环境变量,此方法一次设定,一劳永逸,推荐。
MAVEN_OPTS 环境变量设置方法:
在环境变量中添加一个新变量名为 MAVEN_OPTS,值为 -Xms128m -Xmx512m
(数值可以自定义,Xms为初始内存,即最小内存,Xmx为最大内存)
配置用户范围settings.xml
Maven用户可以选择配置
%MAVEN_HOME%/conf/settings.xml 全局范围,整台机器上的所有用户都会受该配置影响
~/.m2/settings.xml 用户范围,只有当前用户才会受该配置影响
配置用户范围的settings.xml文件便于Maven升级,而且升级时无需修改conf下的settings.xml。
不要使用IDE内嵌的Maven
无论Eclipse还是NetBeans,当集成Maven时,都会安装上一个内嵌的Maven:
潜在问题:
a.较新版本的Maven存在很多不稳定因素,容易造成一些难以理解的问题
b.除了IDE,也经常使用命令行的Maven,如果版本不一致,容易造成构建行为的不一致
在eclipse中使用使用外部Maven:
Maven使用入门
编写POM
首先创建一个名为hello-mavenpom.xml
1 |
|
编写主代码
项目主代码和测试代码不同,主代码会被打包到最终构件中(如jar),而测试代码只在测试时用到,不会被打包。遵循Maven的约定,创建目录com/main/java,Maven会自动搜索该目录找到项目主代码。然后在该目录下创建文件com/jonsnows/hellomaven/HelloMaven.java,内容如下:
1 | package com.jonsnows.hellomaven; |
Java类的包名是com.jonsnows.hellomaven,与之前在POM中定义的groupId和artifactId相吻合。一般来说,项目中Java类的包都应基于项目的groupId和artifactId,这样更加清晰,更加符合逻辑,也方便搜索构建或者Java类。
代码编写完毕后,使用Maven进行编译,在项目根目录(POM文件所在目录)下运行命令mvn clean compile会得到如下输出:
1 | [INFO] Scanning for projects... |
clean:clean插件的clean目标。删除target/目录。Maven构建默认输出在target/目录中。
compile:compiler插件的compile目标。编译项目主代码至target/calsses目录。编译好的类为com/jonsnows/hellomaven/HelloMaven.class。
后文详细介绍Maven插件及其编写方法。
编写测试代码
为了使项目结构清晰,主代码与测试代码应该分别位于独立的目录中。
默认主代码目录:src/main/java,默认测试代码目录:src/test/java。
Junit是单元测试标准,要使用Junit,需为hello-maven项目添加Junit依赖,
在project元素下添加:
1 | <dependencies> |
编写测试类,在src/test/java目录下创建文件:
1 | package com.jonsnows.hellomaven; |
一个典型的单元测试包含三步:
1.准备测试类及数据;
2.执行要测试的行为;
3.检查结果。
调用Maven执行测试。运行mvn clean test,而Maven实际执行的不止这两个任务,包括
clean:clean、 删除target/目录
resources:resources、 主资源处理
compiler:compile、 主代码编译
resources:testResources、 测试资源处理
compiler:testCompile。 测试代码编译 // 输出测试报告
等等,这是Maven生命周期的一个特性。surefire是Maven中负责执行测试的插件。
1 | [INFO] Scanning for projects... |
打包和运行
项目进行编译、测试之后就是打包(package)。Hello Maven的POM中没有指定打包类型,使用默认打包类型jar。执行命令mvn clean package打包,输出如下:
1 | [INFO] Scanning for projects... |
类似的,Maven在打包之前执行编译、测试等操作。可看到jar:jar任务负责打包,jar插件的jar目标将项目主代码打包成一个名为hello-maven-1.0-SNAPSHOT.jar的文件,位于target/输出目录,是根据artifact-version.jar规则进行命名的,还可以使用finalName自定义该文件名称。
复制之个jar到其他项目的classpath中可直接使用。但是,**如何让其他的Maven项目直接引用这个jar呢?**还需要安装,执行mvn clean install:
1 | [INFO] Scanning for projects... |
由输出可知,该任务(安装任务install:install)将项目输出的jar安装到了Maven本地仓库,在相应文件夹可看到Hello Maven项目的pom和jar。构件安装到本地仓库后,其他Maven项目就可使用它了。
我们已经体验了Maven最主要的命令:
1 | mvn clean compile |
默认打包生成的jar不能直接运行
因为带有main方法的类信息不会添加到manifest中(打开jar文件中的META-INF/MANIFEST.MF文件,将无法看到Main-Class一行)
为了生成可执行的jar文件,需要借助maven-shade-plugin,配置该插件如下:
1 | <build> |
plugin元素在POM中的 <project> <build> <plugins>
下面,我们配置了mainClass为com.jonsnows.hellomaven.HelloMaven,项目在打包时会将该信息放到MANIFEST中。现在执行mvn clean install,构建完成后打开target/目录如下图:
打开前面jar的META-INF/MANIFEST.MF如下:
在项目根目录(POM文件所在目录)执行该jar文件:
使用Archetype(原型)生成项目骨架
Hello Maven中有一些Maven约定:在项目根目录放置pom.xml,在src/main/java目录放置主代码,在src/test/java目录放置测试代码。这些基本的目录结构和pom.xml文件内容称为项目的骨架。
Maven提供了Archetype帮助我们快速勾勒出项目骨架。
如果是Maven3,简单地运行:
mvn archetype:generate
如果是Maven2,最好运行如下命令:
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate
上面的命令实际上是在运行插件maven-archetype-plugin,注意冒号的分隔,其格式为
groupId:artifactId:version:goal
org.apache.maven.plugins // maven官方插件的groupId
maven-archetype-plugin // Archetype插件的artifactId
2.0-alpha-5 // 当时插件最新的稳定版(现在已不是最新稳定版)
Generate // 要使用的插件目标
在Maven2中简单地运行mvn archetype:generate命令不是安全的,没有指定Archetype插件的版本,Maven就会自动下载最新的版本,可能是不稳定的SNAPSHOT版本,导致运行失败。而在Maven3中,会解析最新的稳定版本,是安全的。
运行命令mvn archetype:generate后,会有一段长长的输出,有很多可用的Archetype供选择,包括著名的Appfuse项目的Archetype、JPA项目的Archetype等。每一个Archetype前面都有编号,命令会提示一个默认的编号,其对应的Archetype为maven-archetype-quickstart,直接回车选择该Archetype,接着Maven提示要创建项目的groupId、artifactId、version以及包名package。如下输入并确认:
Archetype插件会创建一个名为hello-maven(我们定义的artifactId)的目录,pom.xml已被创建,主代码和测试用例也创建好。请观察创建后的项目,此处略。
6.m2eclipse简单使用
6.1导入项目
6.2创建Maven项目
6.3运行Maven命令
eclipse下,右键项目或项目的POM文件,选择 Run As
就会看到可运行的 maven 命令。
坐标和依赖
何为Maven坐标
Maven坐标是一个部件的唯一标识,为构件引入秩序。
坐标详解
Maven坐标是通过一些元素定义的,它们是groupId、artifactId、version、packaging、classifier。下面是一组坐标:
1 | <groupId>org.sonatype.nexus</groupId> |
这是nexus-indexer的坐标定义,nexus-indexer是一个对Maven仓库编纂索引并提供搜索功能的类库,它是Nexus项目的一个子模块。
groupId:
命名:定义当前Maven项目隶属的实际项目。Maven项目和实际项目不一定是一对一的关系。比如SpringFramework是实际项目,对应的Mavne项目:spring-core、spring-context等模块。不应该对应项目隶属的组织或公司,如果groupId只定义到组织级别,artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义。
表示方式:通常与域名反向意义对应。org.sonatype表示Sonatype公司建立的一个非盈利性组织,nexus表示Nexus这一实际项目,该groupId与域名nexus.sonatype.org对应
artifactId:
定义实际项目中的一个Maven项目(模块),推荐做法是使用实际项目名称作为artifactId的前缀,便于寻找实际构件。默认情况下,Maven生成的构件,其文件名会以artifactId开头,如nexus-indexer-2.0.0.jar,使用前缀后能方便从一个lib文件夹中找到某个项目的一组构件。5个项目,每个项目都有一个core模块,若没有前缀,无法区分各模块属于哪个项目。
version:
定义Maven项目当前所处的版本,
packaging:
定义Maven项目的打包方式,默认值jar。
packaging为jar,生成.jar文件;为war,生成.war文件;但并非一定与构件扩展名对应,比如packaging为maven-plugin的构件扩展名为jar。
打包方式会影响构建的生命周期,比如jar打包和war打包会使用不同的命令。
classifier:
帮助定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是nexus-indexer-2.0.0.jar,该项目还可能会通过一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件,其包含了Java文档和源代码。这时候,javadoc和sources就是这两个附属构件的classifier。
groupId、artifactId、version 必须定义的
packaging 可选的(默认为jar)
classifier 不能直接定义的
依赖的配置
1 | <project> <!-- 根元素 --> |
依赖范围
用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系
complie:编译依赖范围。若不指定,默认使用该依赖范围。对编译、测试、运行三种classpath都有效。例如spring-core,在这三个阶段都需要使用。
test:测试依赖范围。只对测试classpath有效,在编译主代码或运行项目时无法使用。例如Junit,只在编译测试代码及运行测试的时候使用。
provided:已提供依赖范围。对于编译和测试classpath有效,运行时无效。例如servlet-api,在运行项目时,由于容器已经提供,就不需要Maven重复引入。
runtime:运行时依赖范围。对于测试和运行classpath有效,但在编译主代码时无效。例如,JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system:系统依赖范围。对于编译和测试classpath有效,运行时无效。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此需谨慎使用。systemPath元素可以引用环境变量:(Maven读取环境变量)
1 | <dependency> |
import:导入依赖范围。
各种依赖范围(除import)与三种classpath的关系图:
传递性依赖
何为传递性依赖
project有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么project就会有一个compile范围的commons-logging依赖。Commons-logging是project的一个传递性依赖。
Spring Framework会依赖其他开源类库,spring-framework-5.0.0.RELEASE-dist.zip包含了所有Spring Framework的jar包,以及所有它依赖的其他jar包,很容易引入不必要的依赖;另一种做法是只下载Spring Framework的压缩包,不包含其他相关依赖,到实际使用时,根据报错信息或查询相关文档,引入需要的其他依赖。这样会非常麻烦。
有了传递性依赖机制,在使用Spring Framework的时候就不用过去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前项目中。
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。
假设A依赖于B,B依赖于C,即 AB
C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。如下图,
规律:
当第二直接依赖的范围是compile时,传递性依赖的范围与第一直接依赖的范围一致;
当第二直接依赖的范围是test时,依赖不会得以传递;
当第二直接依赖的范围是provided时,只传递第一直接依赖范围为provided的依赖,且传递性依赖的范围同样为provided;
当第二直接依赖的范围是runtime时,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。
Mave依赖调解
第一原则:短路优先
**第二原则:**路径相同则先声明优先
原则目的:确保任何一个构件只有唯一的版本在依赖中存在。
例:
A -> B -> C -> X(1.0)
A -> D -> X(2.0)
X(2.0)会被解析使用。
A -> B -> Y(1.0)
A -> C -> Y(2.0)
依赖路径长度一样,如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。
其它情况:覆盖策略
若一个组件不同版本的依赖,存在于同一个pom文件,依赖调解两大原则都不起作用,需要采用覆盖策略来调解依赖冲突,最终会引入最后声明的依赖。
可选依赖
一般用不到。
例:
A依赖B,B依赖C,但不想让A依赖C,我们可以在B中这样配置
1 | <project> |
在A中正常引入B的依赖即可,这个时候,A就不会依赖C了。如果A用到了B中涉及C的功能,则需要在A的 pom.xml
文件中额外配置对C的依赖。
**应用场景:**假设有一个项目X,它实现了类似Hibernate的功能,支持很多种数据库驱动,例如:mysql,oracle等。我们在构建项目X的时候,确实需要所有这些依赖。但设想,假如有一个项目Y想使用项目X提供的功能,那么它极有可能只使用其中的一种数据库驱动。这个时候,就需要我们在项目X中将所有和驱动相关的依赖设置为可选依赖。只有这样,在项目Y中声明项目X为直接依赖的时候,才不会将项目X的所有关于驱动的依赖自动引入。这时,项目Y只需要额外声明自己真正需要依赖的驱动即可。
排除依赖
可选依赖也能够达到排除依赖的效果。
1 | <project> |
归类依赖
在唯一的地方定义版本,并且在dependency声明中引用这一版本。这样,在升级Spring Framework时只需修改一处。
1 | <project> |
优化依赖
优化依赖,除多余的依赖,显式声明某些必要的依赖。
使用 mvn dependency:list
和 mvn dependency:tree
可以帮助我们详细了解项目中所有的依赖的具体详细,在此基础上,还有 mvn dependency:analyze
工具可以帮助分析当前项目的依赖。
已解析依赖
maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围。对于一些依赖冲突,也能进行调解,以确保任何一个构件只能唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
查看项目的已解析依赖:mvn dependency:list
依赖树
将直接在当前POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经过Maven解析之后,就会构成一个依赖树,通过这颗依赖树,就能够很清楚地看到某个依赖是通过哪条路径引入的。
查看项目的依赖树:mvn dependency:tree
仓库
何为Maven仓库
Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。
为了实现重用,项目构建完毕后生成的构件也可以安装或者部署到仓库中,供其他项目使用。
仓库的布局
任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,这便是Maven的仓库布局方式。
路径与坐标的大致对应关系为: groupId/artifactId/version/artifactId-version.packaging
。
仓库的分类
中央仓库
Maven内置了一个中央仓库的地址: https://repo.maven.apache.org/maven2/
私服
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。
优点
节省组织的外网带宽:消除重复构件的下载,降低外网带宽的压力。
加速Maven构建:Maven的一些内部机制(如快照更新检查)要求Maven在执行构建的时候不停地检查远程仓库数据。
部署第三方构件:将一些有版权的构件(如Oracle的JDBC驱动)和组织内部生成的私有构件部署到是私服,供内部的Maven项目使用。
提高稳定性,增强控制:在私服中缓存大量构件,即使没有Internet连接,Maven也可以正常运行。一些私服软件(如Nexus)还提供了很多额外的功能,如权限管理、RELEASE/SNAPSHOT分区等,管理员可以对仓库进行一些更高级的控制。
降低中央仓库的负荷:一个公司一个私服对中央仓库只有一次下载,开发人员们的下载只发生在内网。
私服软件
建立私服是用好Maven十分关键的一步,应学会使用最流行的Maven私服软件——Nexus。
远程仓库的配置
项目需要的构件存在于另外一个远程仓库,如Jboss Maven仓库,这时默认的中央仓库无法满足项目的需求。可在POM中配置该仓库。
1 | <project> |
1 | <project> |
repository:在repositories元素下,可以使用repository子元素声明一个或者多个远程仓库。
id:仓库声明的唯一id,尤其需要注意的是,Maven自带的中央仓库使用的id为central,如果其他仓库声明也使用该id,就会覆盖中央仓库的配置。
name:仓库的名称,为了方便人阅读,无其他作用。
url:指向了仓库的地址,一般来说,该地址都基于http协议,Maven用户都可以在浏览器中打开仓库地址浏览构件。
releases、snapshots:
用来控制Maven对于发布版构件和快照版构件的下载。需要注意的是enabled子元素,该例中releases的enabled值为true,表示开启JBoss仓库的发布版本下载支持,而snapshots的enabled值为false,表示关闭JBoss仓库的快照版本下载支持。根据该配置,Maven只会从JBoss仓库下载发布版的构件,而不会下载快照版的构件。
除了enabled,它们还包含另外两个子元素 updatePolicy 和 checksumPolicy 。
updatePolicy:配置Maven从远处仓库检查更新的频率,默认值是daily,表示Maven每天检查一次。其他可用的值包括:never-从不检查更新;always-每次构建都检查更新;interval:X-每隔X分钟检查一次更新(X为任意整数)。
checksumPolicy:配置Maven检查校验和文件的策略。当构建被部署到Maven仓库中时,会同时部署对应的检验和文件。在下载构件的时候,Maven会验证校验和文件,如果校验和验证失败,当checksumPolicy的值为默认的warn时,Maven会在执行构建时输出警告信息,其他可用的值包括:fail-Maven遇到校验和错误就让构建失败;ignore-使Maven完全忽略校验和错误。
layout:元素值default表示仓库的布局是Maven2及Maven3的默认布局,而不是Maven1的布局。
远程仓库的认证
大部分公共的远程仓库无须认证就可以直接访问,但我们在平时的开发中往往会架设自己的Maven远程仓库,出于安全方面的考虑,我们需要提供认证信息才能访问这样的远程仓库。配置认证信息和配置远程仓库不同,远程仓库可以直接在pom.xml中配置,但是认证信息必须配置在settings.xml文件中。这是因为pom往往是被提交到代码仓库中供所有成员访问的,而settings.xml一般只存在于本机。因此,在settings.xml中配置认证信息更为安全。
1 | <settings> |
部署构件至远程仓库
私服的一大作用是部署第三方构件,包括组织内部生成的构件以及一些无法从外部仓库直接获取的构件(一些有版权的构件),这些构件都需要部署到仓库中,供其他团队成员使用。
Maven除了能对项目进行编译、测试、打包之外,还能将项目生成的构件部署到远程仓库中。首先,需要编辑项目的 pom.xml
文件。配置 distributionManagement
元素,代码如下:
1 | <project> |
往远程仓库部署构件的时候,往往需要认证,不论从远程仓库下载构件,还是部署构件至远程仓库,配置认证的方式是一样的。
配置正确后,运行命令mvn clean deploy,Maven就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。即除了快照版本,其他都部署到发布版本。
发布版和快照版可以是同一个仓库地址吗?
快照版本
Maven为什么要区分发布版和快照版?
分模块开发时,模块之间会有依赖关系,快照版本在发布到私服的过程中,Maven会自动为构件打上时间戳。比如模块A的版本设定为2.1-SNAPSHOT,打上时间戳后:2.1-20180901.083052-15,表示2018年9月1日8点30分52秒的第15次快照。当构建依赖于模块B的模块A时,Maven会自动从仓库中检查模块B的2.1-SNAPSHOT版本的最新构件,有更新便下载。
从仓库解析依赖的机制
Maven是根据怎样的规则从仓库解析并使用依赖构件的呢?
当本地没有依赖构件时,Maven会自动从远程仓库下载;当依赖版本为快照版本时,Maven会自动找到最新的快照。
依赖解析机制概括如下:
A、依赖范围是system时,Maven直接从本地文件系统解析构件。
B、根据依赖坐标计算仓库路径,尝试从本地仓库寻找构件,若发现相应构件,则解析成功。
C、本地仓库未找到:
a.依赖版本是显式的发布版本构件,如1.2、5.1-beta-1等,则遍历所有的远程仓库,若发现,则下载并解析使用。
b.依赖版本是RELEASE或者LATEST,则基于更新策略读取所有远程仓库的元数据(路径格式: groupId/artifactId/maven-metadata.xml
),将其与本地仓库的对应元数据合并后,计算出RELEASE或者LATEST的真实值,然后基于这个真实的值检查本地和远程仓库。
c.依赖版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据(路径格式:groupId/artifactId/maven-metadata.xml),将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或从远程仓库下载。
RELEASE、LATEST和SNAPSHOT版本分别对应了仓库中存在的该构件的最新发布版本、最新版本(包含快照)和最新快照版本,这个“最新”是基于 groupId/artifactId/maven-metadata.xml
计算出来的,该文件内容如下:
1 | <metadata> |
以下是快照版本仓库 groupId/artifactId/maven-metadata.xml
文件内容:
1 | <metadata> |
依赖版本可如下配置:
Maven3不再支持在插件配置中使用LATEST和RELEASE,如果不设置插件版本,其效果和RELEASE一样。
1 | <project> |
镜像
如果仓库X可以提供仓库Y存储的所有内容,那么X就是Y的一个镜像。任何一个可以从仓库Y获得的构件,都能够从它的镜像中获取。
例如 http://maven.net.cn/content/groups/public/ 是中央仓库 http://repo1.maven.org/maven2/ 在中国的镜像,该镜像往往能够提供比中央仓库更快的服务。因此,可以配置Maven使用该镜像来代替中央仓库。编辑setting.xml:
1 | <mirrors> |
关于镜像更常见的用法是结合私服。私服代理所有外部的公共仓库(包括中央仓库),组织内部的Maven用户使用一个私服地址就可以,这样就将配置集中到私服,从而简化Maven本身的配置。私服就是所有仓库的镜像。可以在setting.xml中配置这样一个镜像:
1 | <mirrors> |
如果该镜像仓库需要认证,则配置一个id为internal-repository的
Maven的镜像配置:
1 | <mirrorOf>*</mirrorOf> <!-- 匹配所有远程仓库 --> |
仓库搜索服务
使用maven进行日常开发的时候,一个常见问题就是如何寻找需要的依赖,我们可能只知道需要使用类库的项目名称,但是添加maven依赖要求提供确切的maven坐标,这时就可以使用仓库搜索服务来根据关键字得到maven坐标。
在中央仓库搜索: https://mvnrepository.com/ 。
在最流行的Maven私服软件Nexus中搜索。
生命周期和插件
Maven的生命周期是抽象的,其实际行为都由插件来完成,如package阶段的任务可能就会由maven-jar-plugin完成。生命周期和插件两者协同工作,密不可分。
何为生命周期
项目构建的生命周期一直存在。Mavne出现之前,公司和公司间、项目和项目间,往往使用不同的方式做类似的工作。Maven的生命周期就是为了对所有的构建过程进行抽象和统一,这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。几乎所有项目的构建,都能映射到这样一个生命周期上。
Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,实际的任务都交有插件来完成。这种思想与设计模式中的模板方法非常相似。如下的模板方法抽象类能够很好地体现Maven生命周期的概念:
1 | public abstract class AbstractBuild{ |
上述代码和Maven实际代码相差甚远,Maven的生命周期包含更多的步骤和更复杂的逻辑,但他们的基本理念是相同的。
生命周期抽象了构建的各个步骤,定义了它们的次序,但没有提供具体实现。每个构建步骤都可以绑定一个或者多个插件行为,Maven为大多数构建步骤编写并绑定了默认插件。用户几乎不会察觉到插件的存在,但实际上编译是由maven-compiler-plugin完成的,而测试是由maven-surefire-plugin完成的。当用户有特殊需要时,可以配置插件定制构建行为,甚至自己编写插件。
Maven定义的生命周期和插件机制一方面保证了所有Mavne项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外,该机制还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来自定义构建行为。
生命周期详解
三套生命周期
三套项目独立的生命周期:
clean
清理项目
default
构建项目
site
建立项目站点
用户可以仅仅调用 clean
生命周期的某个阶段,而不会对其他生命周期产生任何影响。同理…
clean 生命周期
阶段
pre-clean
执行一些清理前需要完成的工作
clean
清理上一次构建生成的文件
post-clean
执行一些清理后需要完成的工作
default 生命周期
阶段
validate
initialize
generate-sources
process-sources
处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
generate-resources
process-resources
compile
编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
process-classes
generate-test-sources
process-test-sources
处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
generate-test-resources
process-test-resources
test-compile
编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
process-test-classes
test
使用单元测试框架运行测试,测试代码不会被打包或部署。
prepare-package
package
获取编译后的代码,打包成可发布的格式,如 JAR。
pre-integration-test
integration-test
post-integration-test
verify
install
将包安装到Mavne本地仓库,供本地其他Maven项目使用。
deploy
将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。
site 生命周期
site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。
阶段
pre-site
执行一些在生成项目站点之前需要完成的工作。
site
生成项目站点文档。
post-site
执行一些在生成项目站点之后需要完成的工作。
site-deploy
将生成的项目站点发布到服务器上。
命令行与生命周期
命令行执行Maven任务的方式:调用Maven的生命周期阶段。各个生命周期相互独立,一个生命周期的阶段有前后依赖关系。以常见的Maven为例,解释其执行的生命周期阶段:
$mvn clean :调用clean生命周期的clean阶。实际执行的阶段为clean生命周期的pre-clean和clean阶段。
$mvn test :调用default生命周期的test阶段。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段。所以在执行测试的时候,项目代码也会被编译。
$mvn clean install :调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为clean生命周期的pre-clean、clean阶段,以及default生命周期的从validate至install的所有阶段。该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践。
$mvn clean deploy site-deploy :推理可知。
Maven中主要的生命周期阶段并不多,常用的Maven命令都是基于这些阶段简单组合而成,因此对Maven生命周期有一个基本的理解,就可以正确而熟练地使用Maven命令。
插件目标
为每一个功能(即任务)编写一个独立的插件是不可取的,因为有些功能(即任务)之间有可复用的代码。
将一些功能(即任务)聚集在一个插件里(为了复用代码),每个功能(即任务)就是一个插件目标。
帮助理解:
一个类(插件)有很多方法(功能),每个方法看作一个插件目标。
一个 jar 包(插件)有很多类(功能),每个类看作一个插件目标。
使用示例:
maven-dependency-plugin插件
dependency:analyze 分析项目依赖,找出潜在的无用依赖。
dependency:tree 列出项目的依赖树,分析依赖来源。
dependency:list 列出项目所有已解析的依赖。
maven-compiler-plugin插件
compiler:compile
通用写法
冒号前:插件前缀(可看作类名或 jar 包名)
冒号后:插件的目标(可看作方法名或类名)。
插件绑定
Maven的生命周期与插件相互绑定,用以完成实际的构建任务。(实质:生命周期的某个阶段与插件的某个目标相互绑定,以完成某个具体的构建任务)。
例如:项目编译,它对应default生命周期的compile阶段,而maven-compiler-plugin插件的compile目标能完成该任务。因此,将它们绑定,就能实现项目编译的目的,如图:
内置绑定
Maven在核心已经为一些主要的生命周期阶段绑定了插件目标,当用户通过命令行调用生命周期阶段时,对应的插件目标就会执行相应的任务,从命令行输出中可看到项目构建过程执行了哪些插件目标。
default生命周期还有很多其他阶段,默认它们没有绑定任何插件,因此也没有任何实际行为。
default生命周期的阶段与插件目标的绑定关系由项目打包类型决定。jar 打成 JAR 包,war 打成 WAR 包。
例如执行 mvn clean install :
1 | [INFO] Scanning for projects... |
自定义绑定(创建源码jar)
自己选择将某个插件目标绑定到生命周期的某个阶段上。
例子:创建项目的源码jar包。
内置的插件绑定关系中并没有涉及该任务,因此需要用户自行配置。
maven-source-plugin 插件的 jar-no-fork 目标能够将项目的主代码打包成 jar 文件,将其绑定到 default 生命周期的 verify 阶段上,在 package(创建项目jar包)后和 install(安装构件)前创建源码 jar 包。
1 | <build> |
自定义插件绑定完成,运行 mvn verify 。有如下输出:
1 | [INFO] --- maven-source-plugin:2.2.1:jar-no-fork (attach-sources) @ project-util --- |
可以看到,maven-source-plugin:jar-no-fork
会创建一个以 –sources.jar
结尾的源码文件包。
不通过phase元素配置生命周期阶段,插件目标也能绑定到生命周期中。有很多插件的目标在编写时已经定义了默认的绑定阶段。可以使用 maven-help-plugin
查看插件详情信息,了解插件目标的默认绑定阶段。命令如下:
1 | $ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.2.1 -Ddetail |