Maven

Table of Contents

http://maven.apache.org/

1. Introduction

1.1. maven能够用来做什么

  • 理解项目通常是如何构建的。
  • 利用其内嵌的项目知识简化和便利项目构建。
  • 利用其内嵌的项目知识来帮助用户理解复杂的项目结构和构建过程中潜在的变数。
  • 设计并实现一个经证实的依赖项管理系统,该系统顺应了当今全球化和相互联系的项目团队的需求。
  • 利用其内部知识,针对简单项目提供简单的用户体验。
  • 对于高级用户来说相当灵活;针对特别的应用场景,可以覆盖其内嵌模型,也可以通过配置、部署元数据或创建自定义插件对其内嵌模型进行改写。
  • 全面扩展现有行为之外的场景细节。
  • 捕获新出现的最佳实践和各个用户社区间的经确认的共同性,并将它们纳入到 Maven 的内嵌项目知识中,从而不断地得到改进。

1.2. maven模型

maven模型分为几个部分:

  • 项目对象模型(POM): POM 是 Maven 2 中的里程碑式的模型。该模型的一部分已经构建到 Maven 引擎(被亲切地称为反应堆 )中,其余部分则通过一个名叫 pom.xml 的基于 XML 的元数据文件来声明。
  • 依赖项管理模型: Maven 对如何管理项目的依赖项很在行。依赖项管理是一片灰色地带,典型的构建-管理工具和系统都未明确涉及。Maven 2 构建了 Maven 依赖项管理模型,该模型能够适应大多数需求。这个模型被证明是有效而高产的模型,目前,主要的开源项目都部署了该模型。
  • 构建生命周期和阶段:和 POM 相关的概念是构建生命周期 和阶段。这是 Maven 2 的内嵌概念模型和现实物理世界之间的接口。使用 Maven 时,工作主要是通过插件来执行的。在构建周期中,Maven 2 通过遵循一系列定义好的阶段,将这些插件协调起来。
  • 插件:插件中每个认为被称为mojo(构建任务)。Maven 引擎在执行构建生命周期中相应的阶段时,执行插件中的 mojo(构建任务)。插件的 mojo 和生命周期中的阶段间的关联叫做绑定 。插件开发人员能够灵活地将一个或多个生命周期阶段和一个插件关联起来。

1.3. maven生命周期

生命周期 阶段描述
验证 确保当前配置和 POM 的内容是有效的。这包含对 pom.xml 文件树的验证。
初始化 在执行构建生命周期的主任务之前可以进行初始化。
生成源码代码 生成器可以开始生成在以后阶段中处理或编译的源代码。
处理源码 提供解析、修改和转换源码。常规源码和生成的源码都可以在这里处理。
生成资源 可以生成非源码资源。通常包括元数据文件和配置文件。
处理资源 处理非源码资源。修改、转换和重定位资源都能在这阶段发生。
编译 编译源码。编译过的类被放到目标目录树中。
处理类处理 类文件转换和增强步骤。字节码交织器和常用工具常在这一阶段操作。
生成测试源码 mojo 可以生成要操作的单元测试代码。
处理测试源码 在编译前对测试源码执行任何必要的处理。在这一阶段,可以修改、转换或复制源代码。
生成测试资源 允许生成与测试相关的(非源码)资源。
处理测试资源 可以处理、转换和重新定位与测试相关的资源。
测试编译 编译单元测试的源码。
测试 运行编译过的单元测试并累计结果。
打包 将可执行的二进制文件打包到一个分布式归档文件中,如 JAR 或 WAR。
前集成测试 准备集成测试。这种情况下的集成测试是指在一个受到一定控制的模拟的真实部署环境中测试代码。这一步能将归档文件部署到一个服务器上执行。
集成测试 执行真正的集成测试。
后集成测试 解除集成测试准备。这一步涉及测试环境重置或重新初始化。
检验检验 可部署归档的有效性和完整性。过了这个阶段,将安装该归档。
安装 将该归档添加到本地 Maven 目录。这一步让其他可能依赖该归档的模块可以使用它。
部署 将该归档添加到远程 Maven 目录。这一步让这个工件能为更多的人所用。

Maven 从开源社区中汲取了十多年的项目构建管理经验。很难找到一个构建周期不符合上表中的生命周期阶段的软件项目。启动 Maven 2 引擎后,它会按顺序经历表中的各阶段,执行可能与该阶段绑定的 mojo。每个 mojo 则可以使用 Maven 2 丰富的 POM 支持、依赖项管理,也可以访问执行这一专门任务时的构建状态信息。调用 Maven 2 引擎时,可以将一个生命周期阶段指定为命令行参数。该引擎一直执行到指定的阶段(包括该指定的阶段)。包含的阶段中所有的 mojo 都会被触发。

但是在这里有另外一个版本,大家可以参考一下 http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html

  • validate: validate the project is correct and all necessary information is available
  • compile: compile the source code of the project
  • test: test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package: take the compiled code and package it in its distributable format, such as a JAR.
  • integration-test: process and deploy the package if necessary into an environment where integration tests can be run
  • verify: run any checks to verify the package is valid and meets quality criteria
  • install: install the package into the local repository, for use as a dependency in other projects locally
  • deploy: done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.

There are two other Maven lifecycles of note beyond the default list above. They are

  • clean: cleans up artifacts created by prior builds
  • site: generates site documentation for this project

2. Repository

Maven 2 仓库存储 Maven 在一个项目的依赖项解析中使用过的工件集。在本地磁盘上访问本地仓库,通过网络访问远程仓库。工件通常被打包成包含二进制库或可执行库的 JAR 文件。这被认为是工件的一个类型。但在实践中,工件也可以是 WAR、EAR 或其他代码捆绑类型。Maven 2 利用操作系统的目录结构对存储在仓库中的工件集进行快速索引。这个仓库索引系统依赖于这种能力来通过工件的坐标惟一标识工件。Maven 坐标是一组可以惟一标识工件的三元组值。坐标包含了下列三条信息:

  • (groupId)组 ID:代表制造该工件的实体或组织。例如,com.ibm.devworks 就是一个组 ID。
  • (artifactId)工件 ID:实际的工件的名称。例如,主体类名为 OpsImp 的项目也许会用 OpsImp 作为其工件 ID。
  • (version)版本:该工件的版本号。支持的格式为 mmm.nnn.bbb-qqqqqqq-dd ,其中, mmm 是主版本号, nnn 是次版本号, bbb 代表其 bug 修复水平。 qqqqq (限定词)或 dd (构建号)也能添加到版本号中,这两项是可选项。

下面是JUnit依赖项的Maven坐标:

<dependencies>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
   </dependency>
</dependencies>

另外两个并不常用的坐标指标是:

  • packaging. jar, pom etc. #note: 下载对象类型,是下载jar还是pom文件
  • classifier. javadoc, jdk15 etc. #note: 下载对象后缀名,比如如果jdk15的话,那么下载对象就是junit-jdk15.jar.这个可以用来区分环境

另外dependency还有scope字段,这个字段主要用来解释如何使用这个模块的:

  • compile. 缺省,适用于所有阶段,会随着项目一起发布。比如log4j.
  • provided. 类似compile,但是期望JDK或者是使用者会提供这个依赖。比如servlet-api.
  • runtime. 只是在运行时候使用。比如JDBC Driver.
  • test. 只是在测试时使用。比如junit.
  • system. 类似provided,但是需要显示提供所包含依赖的jar,maven不会在repo中查找它。

2.1. 本地仓库

maven默认设置~/.m2/repository为本地仓库。将项目所需要的依赖全部都缓存下来,这样不用每次都重新下载依赖。

dirlt@dirlt-virtual-machine:~/.m2/repository$ ls -l
total 68
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 antlr
drwxrwxr-x 8 dirlt dirlt 4096  6月 24 22:36 asm
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 classworlds
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 com
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 commons-cli
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 commons-collections
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 commons-io
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 commons-lang
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 dom4j
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 jdom
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 jtidy
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 junit
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 net
drwxrwxr-x 6 dirlt dirlt 4096  6月 24 23:42 org
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 oro
drwxrwxr-x 5 dirlt dirlt 4096  6月 24 23:40 plexus
drwxrwxr-x 3 dirlt dirlt 4096  6月 24 22:36 xml-apis

我们可以通过修改~/.m2/setting.xml来配置本地仓库位置。但是似乎没有太大必要=D

<settings>
<localRepository>D:\java\repository</localRepository>
</settings>

2.2. 远程仓库

参考资源 http://juvenshun.iteye.com/blog/359256

如果本地仓库没有某个工件的话,那么就会尝试从远程仓库下载。在{M2_HOME}/lib/maven-2.0.10-uber.jar能够找到\org\apache\maven\project\pom-4.0.0.xml,它是所有Maven POM的父POM,所有Maven项目继承该配置,你可以在这个POM中发现如下配置:

<repositories>
  <repository>
    <id>central</id>
    <name>Maven Repository Switchboard</name>
    <layout>default</layout>
    <url>http://repo1.maven.org/maven2</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
</repositories>

它的id是central表示是一个中央仓库,地址是http://repo1.maven.org/maven2, 它关闭了snapshot版本构件下载的支持。

为了能够在我们的项目中配置远程仓库,可以在项目pom.xml里面添加下面代码。大致结构是这样的:

  • repositories(工件仓库)
    • repository
      • id
      • name
      • url
      • releases
        • enabled(是否可以使用release版本)
      • snapshots
        • enabled(是否可以使用snapshot版本)
  • pluginRepositories(插件仓库)
    • pluginRepository
<project>
  ...
  <repositories>
    <repository>
      <id>maven-net-cn</id>
      <name>Maven China Mirror</name>
      <url>http://maven.net.cn/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>maven-net-cn</id>
      <name>Maven China Mirror</name>
      <url>http://maven.net.cn/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
  ...
</project>

当然使用上面方式在每个项目pom.xml添加就会产生重复,一种比较好的方式就是加在settings.xml下面

  • profiles
    • profile
      • id(profile id)
  • activeProfiles
    • activeProfile(激活的profile通过id识别)
<settings>
  ...
  <profiles>
    <profile>
      <id>dev</id>
      <!-- repositories and pluginRepositories here-->
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>dev</activeProfile>
  </activeProfiles>
  ...
</settings>

如果你的地理位置附近有一个速度更快的central镜像,或者你想覆盖central仓库配置,或者你想为所有POM使用唯一的一个远程仓库(这个远程仓库代理的所有必要的其它仓库),你可以使用settings.xml中的mirror配置。以下的mirror配置用maven.net.cn覆盖了Maven自带的central:

  • mirrors
    • mirror
      • id
      • name
      • url
      • mirrorOf(作为哪一个repo的镜像通过id识别)
<settings>
  ...
  <mirrors>
    <mirror>
      <id>maven-net-cn</id>
      <name>Maven China Mirror</name>
      <url>http://maven.net.cn/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>
  ...
</settings>

这里仅仅是配置了central的镜像,如果希望配置所有仓库镜像的话那么<mirrorOf>*</mirrorOf>. 关于更加高级的镜像配置,可以参考:http://maven.apache.org/guides/mini/guide-mirror-settings.html. #todo: maven是如何在中央仓库和本地仓库,以及镜像之间选择下载工件的。

2.3. 分发构件至远程仓库

mvn install 会将项目生成的构件安装到本地Maven仓库,mvn deploy 用来将项目生成的构件分发到远程Maven仓库。本地Maven仓库的构件只能供当前用户使用,在分发到远程Maven仓库之后,所有能访问该仓库的用户都能使用你的构件。 安装到maven本地仓库对于使用一些下载源代码并且编译的项目非常有用. 我们可以在pom.xml里面指定maven分发构件位置。注意这里我们通常将release和snapshot区分开来放在不同的repo下面。

  • distributionManagement
    • repository
      • id
      • name
      • url
    • snapshotRepository
      • id
      • name
      • url
  • servers 这个也可以在settings.xml里面设置
    • server(对于远程服务器的话可能需要提供用户名和密码)
      • id
      • username
      • password
<project>
  ...
  <distributionManagement>
    <repository>
      <id>nexus-releases</id>
      <name>Nexus Release Repository</name>
      <url>http://127.0.0.1:8080/nexus/content/repositories/releases/</url>
    </repository>
    <snapshotRepository>
      <id>nexus-snapshots</id>
      <name>Nexus Snapshot Repository</name>
      <url>http://127.0.0.1:8080/nexus/content/repositories/snapshots/</url>
    </snapshotRepository>
  </distributionManagement>
  <servers>
    <server>
      <id>nexus-releases</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>nexus-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
  </servers>
  ...
</project>

每一个项目都都需要填写上面的配置,一个比较省力的方法就是:

  • 新建项目叫做X.Y.Z:1.0
  • 在这个项目的pom.xml里面填写上面部署信息
  • 然后将这个项目推送到repo上面。(或者是远程或者是本地)

之后我们只需要在我们自己项目里面填写

<parent>
  <groupId>X.Y</groupId>
  <artifactId>Z</artifactId>
  <version>1.0</version>
</parent>

maven会自动将父项目的pom.xml包含进来。 #note: 可以在这里填写很多公共配置

完成之后执行mvn deploy即可进行部署。done!!! :)

2.4. 远程仓库搭建

参考资源 http://juvenshun.iteye.com/blog/34

可用的maven仓库软件有:

  • Nexus
  • Apache Archiva.

自己维护了一下公司内部的Apache Archiva看起来似乎使用并不麻烦, 但是还是不及Nexus好用. 所以推荐使用nexus. 至于nexus仓库搭建, 网上有相当多的这方面教程.

nexus有下面几种repo:

  • Host Repo(直接放在这台机器),
  • Proxy Repo(可以将其他Repo转发过来),可以将多个proxy repo集合起来,这样在settings.xml里面只需要写一个repo.
  • Group Repo(可以集合不同Repo成为一个独立Repo),
  • Virtual Repo(比如将maven2仓库对外暴露maven1仓库接口)

另外如果自己将一些pom直接copy到某个repo下面的话,nexus不能够索引到,这个时候需要右击这个repo选择Update Index.(nexus依赖索引知道文件分布)

搭建自己的仓库似乎非常有必要, 原因有下面几点:

  1. 一些难下载到的组件只需要下载一次或者是从其他机器上拷贝过来.
  2. 因为ivy2和mvn使用不同的本地目录和不同方式来存储组件, 所以即使用mvn下载了某个组件, 使用ivy2还需要下载一边. 但是如果有本地仓库的话, 那么下载就容易多了.
  3. 这些组件库可以很方便地集中管理. 如果在团队里面使用的话, 大家可以共享已经下载好的组件, 加快开发速度.
  4. 内部组件也可以放在仓库里面.

nexus 可以从 这里 下载到.

  • 使用bin/nexus start来启动.
  • 访问http://localhost:8081(see conf/nexus.properties).
  • 管理员账号是admin/admin123.
  • 数据目录是 ../sonatype-work(see conf/nexus.properties).

通常我们只需要多添加几个proxy repo, 然后做一个group repo将这些proxy repos包含进来. proxy repo必须设置"Download Remote Indexes"为True, 这样才可以从远程仓库上将索引拉下(如果之前为False, 然后之后要选择"repair index"来修复索引). "Administration -> Scheduled Tasks"观察后台是否在做索引, 完成索引之后才能够查询和使用. (初始建立索引可能需要花费10~30分钟)

3. Getting Started

安装maven

如果使用的是ubuntu/debian的话,可以直接使用apt-get install maven来完成. 可是有时候还是需要使用binary-package方式来安装. 比如系统上已经有maven3但是希望安装一个maven2.

maven3和maven2之间有不兼容的地方,不仅是语法还有插件. 之前安装cobertura时候发现运行不了,最后确定问题在于使用的是maven3。当时系统安装的是maven3所以自己需要手动安装一个maven2,这种情况应该还是比较多的,所以这也就是单独编写这节的原因。下面是通过binary-package手动安装的过程:

常用命令

  • mvn validate. 验证工程是否正确,所有需要的资源是否可用
  • mvn compile. 编译项目源代码
  • mvn test-compile. 编译测试项目源代码
  • mvn package. 将编译输出打包
  • mvn integration-test. 运行集成测试
  • mvn verify. 检查是否可以发布
  • mvn install. 将包安装到本地的repository
  • mvn deploy. 将包部署到远程的repository
  • mvn generate-sources 生成应用所需要的额外代码
  • mvn eclipse # 自从使用了intellij就不需要这个东西了. intellij可以自动导入pom.xml.
    • mvn eclipse:eclipse 生成eclipse项目文件
    • mvn eclipse:clean 清除eclipse项目文件
  • mvn clean. 清除编译输出
  • mvn test. 运行测试用例
    • mvn test -Dtest=com.dirlt.java.mr.TestRunMultipleOutputs
    • mvn test -Dtest=com.dirlt.java.mr.TestRunMultipleOutputs#testSample
    • -Dmaven.test.skip=true 跳过单元测试
  • mvn process-test-resources. 将src/test/resources拷贝到target/test-classes目录下面作为测试的资源文件
  • mvn dependency
    • mvn dependency:copy-dependencies 将依赖的jar copy到target/dependency目录下面
    • mvn dependency:unpack-dependencies 和上面一样但是会unpack这些jar
  • mvn <command> -P # 激活使用某个profile. profile能够区分不同的构建环境。
  • mvn -U <command> # 强制检查远端仓库
    • [ERROR] Failed to execute goal on project iceberg: Could not resolve dependencies for project com.umeng.dp:iceberg:jar:4.1.5: Failure to find kafka:kafka:jar:0.7.0 in http://nexus:8088/nexus/content/groups/public/ was cached in the local repository, resolution will no t be reattempted until the update interval of umeng.public has elapsed or updates are forced -> [Help 1]
    • 可以看出maven对于仓库检查是有一定时间间隔的,在一定时间间隔内不会重复检查仓库,这样可以减少网络延迟。但是如果我们更新了仓库希望maven强制检测的话,那么就要使用这个选项。

常用插件