主要介绍maven的基本概念和工作机制,基于Maven实战
依赖管理的基础是坐标,maven仓库也基于maven坐标管理
依赖声明可以包含如下内容:
<dependencies>
<dependency>
<groupId>...groupId>
<artifactId>...artifactId>
<version>...version>
<type>...type>
<scope>...scope>
<optional>...optional>
<exclusions>
<exclusion>
...
<exclusion>
<exclusions>
dependency>
dependencies>
* 当第二直接依赖范围为compile的时候,传递性依赖的范围与第一直接依赖范围一致
* 当第二直接依赖范围为test的时候,依赖不会传递
* 当第二直接依赖范围为provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖的范围也是provided
* 当第二直接依赖范围为runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递依赖的范围为runtime。
解决传递性依赖时,引入同一包的不同版本的问题。
这种情况下maven选择依赖的原则是1)路径最近这优先,2)路径层数相同的情况下,声明在前者优先。
中央仓库,定义在超级pom中,位于 M2HOME/lib/maven−model−builder− version.jar中,超级pom是所有maven项目都要继承的pom。
<project>
...
<repositories>
<repository>
<id>jbossid>
<name>JBoss reposiotryname>
<url>http://repository.jboss.com/maven2/url>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>falseenabled>
snapshots>
<layout>defaultlayout>
repository>
repositories>
...
project>
认证信息必须配置在settings.xml文件中,不能像仓库一样配置在pom中。这样比较安全,因为settings是在本机的,pom是在代码库中的。
仓库信息认证配置举例,配置中的id要与仓库的id保持一致
<settings>
<servers>
<server>
<id>repository id hereid>
<username>repo-userusername>
<password>repo-pwdpassword>
server>
servers>
settings>
可以在pom中进行配置,将项目生成的构件部署至指定的远程仓库,这里利用的是pom中的子元素
<project>
<distributionManagement>
<repository>
<id>proj-releasesid>
<name>project release repositoryname>
<url>http://x.x.x.x/maven2/repositories/proj-releasesurl>
repository>
<snapshotRepository>
<id>proj-snapshotsid>
<name>project snapshot repositoryname>
<url>http://x.x.x.x/maven2/repositories/proj-snapshotsurl>
snapshotRepository>
distributionManagement>
project>
在pom中配置好要部署到的远程仓库后,执行maven clean deploy就可以将输出的构件部署到对应的远程仓库了。
快照版本是为了协同开发时,需要频繁从远程仓库下载最新构件而又不希望更新版本号而发明的手段。快照版本会由maven自动为构件打上时间戳,这样就能永远获取最新的构件。快照版本只应该在组织内部的项目或者模块间依赖使用。
镜像和私服不同,镜像是原始仓库的完全拷贝,能够连接远程仓库的私服则像是个带有缓存的代理。配置了镜像后会完全屏蔽对被镜像库的访问,因此镜像库的稳定性很重要。
镜像配置举例,在settings.xml中配置
<settings>
<mirrors>
<mirror>
<id>nexus-oscid>
<mirrorOf>centralmirrorOf>
<name>Nexus oscname>
<url>http://maven.oschina.net/content/groups/public/url>
mirror>
mirrors>
settings>
其中元素配置被镜像库的id,可以使用通配符。匹配所有仓库,external:匹配所有远程仓库,repo1,repo2可以指定多个仓库,*,!repo1,匹配所有远程仓库,repo1除外,使用!将仓库从匹配中排除。使用通配符的mirrorOf可以配合私服使用,在私服上配置所有远程库,本地只需要配置镜像为私服即可访问多个远程库。
maven的生命周期对构件过程进行抽象,主要包含项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等构建步骤。生命周期抽象了构建的各个步骤,定义了它们的次序,但是没有提供具体的实现,具体的实现通过插件机制来完成。每个构建步骤可以绑定一个或者多个插件行为。
maven日常使用中,命令行的输入往往对应了生命周期,maven的生命周期是抽象的,生命周期本身并不做任何实际工作,实际行为都由插件来完成。maven为大部分生命周期绑定了默认插件,用户可以配置插件的行为来控制构建过程。
maven有三套生命周期,分别是clean、default和site。每个生命周期都包含一些有顺序的阶段,后面的阶段依赖于前面的阶段,用户和maven最直接的交互方式就是调用这些生命周期的阶段。各套生命周期之间是项目独立的,而一个生命周期的阶段是有前后依赖关系的。
* clean生命周期,目的是项目清理,包含的阶段有pre-clean、clean和post-clean三个阶段。
* pre-clean,执行一些清理前需要完成的工作
* clean,清理上一次构建生成的文件
* post-clean,执行一些清理后需要完成的工作
* default生命周期,目的是构建项目,定义了构建时所需要执行的所有步骤,包含如下阶段
* validate,校验项目是否正确以及所有必须的信息是否齐备
* initialize
* generate-sources
* process-sources
* generate-resources
* process-resources,处理项目主资源文件,一般来说是对src/main/resources目录的内容进行变量替换等工作,然后复制到项目输出的主classpath目录中
* compile,编译项目的主源码,一般来说是编译src/main/java目录下的java文件至项目输出的主classpath目录下。
* process-classes
* generate-test-sources
* process-test-sources
* generate-test-resources
* process-test-resources
* test-compile,编译项目的测试代码
* process-test-classes
* test,使用单元测试框架进行测试,测试代码不会被打包或部署
* prepare-package
* package,接受编译好的代码,打包成可发布的格式,比如JAR、WAR
* pre-integration-test
* integration-test
* post-integration-test
* verify,运行并检查集成测试结果来保证质量
* install,将包安装到Maven本地仓库,供本地其它maven项目使用
* deploy,将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。
* site生命周期,目的是建立和发布项目站点,maven能够基于POM所包含的信息,自动生成一个项目站点,方便团队交流和发布项目信息,包括以下阶段
* pre-site,执行一些在生成项目站点之前需要完成的工作
* site,生成项目站点文档
* post-site,执行一些在生成项目站点之后需要完成的工作
* site-deploy,将生成的项目站点发布到服务器上
Maven各个生命周期的详细描述参见:官方文档
从命令行执行maven是,主要就是调用的生命周期的阶段,由于各个生命周期阶段依赖于前面的阶段,因此执行时会将该阶段前面的阶段也一并顺序执行。
直观上来讲,插件目标就是mvn dependency:tree,这里的tree就是插件目标(Plugin Goal)。每个插件目标对应一个功能,冒号前面是插件前缀,冒号后面是插件目标。
maven的生命周期和插件相互绑定,具体来说是生命周期的阶段与插件目标绑定。
为了实现零配置使用,maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段时,对应的插件目标就会执行相应的任务。
默认生命周期阶段与插件目标的绑定关系如下表:
生命周期阶段 | 插件目标 |
---|---|
pre-clean | |
clean | maven-clean-plugin:clean |
post-clean | |
pre-site | |
site | maven-site-plugin: site |
post-site | |
site-deploy | maven-site-plugin:deploy |
process-resources | maven-resources-plugin:resources |
compile | maven-compiler-plugin:compile |
process-test-resources | maven-resources-plugin:testResources |
test-compile | maven-compiler-plugin:testCompile |
test | maven-surefire-plugin:test |
package | maven-jar-plugin:jar |
install | maven-install-plugin:install |
deploy | maven-deploy-plugin:deploy |
可以在插件中配置phase将插件目标绑定到某个特定的生命周期阶段。
代码示例如下,如下将maven-source-plugin:jar-no-fork目标绑定到default生命周期的verify阶段
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins<groupId>
<artifactId>maven-source-pluginartifactId>
<version>2.1.1version>
<executions>
<execution>
<id>attach-sourcesid>
<phase>verifyphase>
<goals>
<goal>jar-no-forkgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
一般插件编写时也会定义默认绑定阶段,可以通过mvn-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:3.3 -Ddetail
当多个插件目标绑定到同一个阶段时,这些插件声明的先后顺序决定了目标的执行顺序。
除了配置插件目标绑定外,还可以对目标的参数进行一些配置。用户可以通过命令行和POM来配置这些参数。
可以在插件配置中定义插件任务,是插件配置仅应用到任务,而不是全局。
基本上所有的插件都来自Apache和Codehaus
插件信息可以访问插件站点来获取,也可以使用maven-help-plugin插件来获取帮助。
为了方便用户使用和配置插件,maven不需要用户提供插件的完整坐标就可以解析得到正确的插件,使用插件前缀可以简单的引用插件。
与普通构件一样插件构件也基于坐标存储在maven仓库中。插件的远程仓库配置与普通构件的远程仓库配置不同,插件的远程仓库使用元素来定义,在配置插件时,如果是官方插件可以省略groupId定义,不推荐这样使用。当没有配置版本时,会拉取所有远程插件仓库和本地仓库的元数据库中获取最新版本,这里maven 3不会获取快照版本。
用户可以使用插件前缀来简洁的访问插件,这里插件前缀与groupId:artifactId一一对应,对应关系存储在仓库元数据库中,位于groupId/maven-metadata.xml中。这里的groupId默认指的是org.apache.maven.plugins和org.codehaus.mojo这两个,也可以通过配置settings.xml,通过元素配置自定义的插件扫描的groupId。
在上述的maven-metadata.xml中存在元素描述插件的前缀。
可以用一个项目聚合其它项目,实现一起编译打包。聚合项目的packaging值为pom,使用元素来包含各个子模块。这里每个module的值都是一个当前pom的相对目录。一般为了快速定位内容,模块所处的目标名称应该与其artifactId一致。为了方便用户构建项目,一般将聚合模块放在项目目录的最顶层,其它模块作为聚合目录的子目录存在。
pom可以继承,作为父模块的pom,其packaging类型必须为pom。父模块的目的是为了消除重复的配置,它本身不包含除POM之外的项目文件,也不需要src/main/java之类的文件夹。可以在子模块中使用元素来声明父模块,子元素声明父pom的位置,默认是../pom.xml。
可以继承的pom元素包括:
* groupId和version
* description,项目描述信息
* organizition
* inceptionYear, 项目的创始年份
* url,项目的URL地址
* developers,项目的开发者信息
* contributors,项目的贡献者信息
* distributionManagement,项目的部署配置
* issueManagement,项目的缺陷跟踪系统信息
* ciManagement,项目的持续集成系统信息
* scm,项目的版本控制系统信息
* mailingLists,项目的邮件列表信息
* properties,自定义的maven属性
* dependencies,项目的依赖
* dependencyManagement,项目的依赖管理配置
* repositories,项目的仓库配置
* build,包括项目的源码配置,输出目录配置,插件配置,插件管理配置等
* reporting,包括项目的报告输出配置,报告插件配置等。
maven提供的dependencyManagement元素能够让子模块集成到父模块的依赖配置,还能保证子模块依赖使用的灵活性。在dependencyManagement下声明的依赖不会引入实际的依赖,但它能够约束dependencies下的依赖使用。可以简化子模块dependencies的编写,在父模块中通过元素声明过的依赖,在子模块中只需要指明groupId和artifactId即可,其它配置可以从父模块继承。还可以配置import依赖范围使用,指的就是将指定pom的配置导入。import依赖范围一般指向packaging值为pom的模块。
除了元素用来管理普通依赖外,maven还提供了元素来管理插件。该元素配置的插件依赖不会导致实际的插件调用行为,只有pom中配置了真正的元素并且groupId和artifactId与中声明的一致时,的配置才会影响插件的行为。
原因:使用约定可以大量减少配置,大量的约定配置从超级pom来继承,比如源码的位置,输出目录,默认打包方式等。
maven 3的超级pom位置是$M2_HOME/lib/maven-model-builder-x.x.x.jar中的org.apache.maven.model.pomx-4.0.0.xml。
超级pom的内容包括:
* 中央仓库的定义
* 中央插件仓库的定义
* 项目结构的定义,由元素定义,包括
* 项目的主输出目录,由子元素定义
* 项目主代码输出目录,由子元素定义
* 最终构件的名称格式,由子元素定义
* 测试代码输出目录,由子元素定义
* 主源码目录,由子元素定义
* 脚本源码目录,由子元素定义
* 测试源码目录,由子元素定义
* 主资源目录,由子元素的子元素的子元素定义
* 测试资源目录,由子元素的子元素的子元素定义
* 插件版本定义,由pluginManagement元素定义
* 项目报告输出目录的配置,项目发布profile等。
就是多模块聚合在一起的构建项目,需要关注的是各个模块的构建顺序。
构建顺序的计算规则如下,maven按照module声明的顺序读取pom,如果该pom没有依赖模块,那么就构建该模块,否则先构建其依赖模块,如果该依赖模块还依赖于其它模块,则进一步构建其它依赖模块。模块间的依赖关系构成一个有向非循环图,依赖之间不允许出现循环,否则会报错。