一、概述
1. 简介
Introduction
Maven,一个意为accumulator of knowledge的犹太词,起初只是为了简化Jakarta Turbine项目中的构建流程。若干个项目拥有不同的Ant构建文件,我们需要一个标准化方式来构建这些项目,一个清晰地定义项目的组成的方式、一个简单的发布项目信息的方式和跨项目共享JAR文件的方式。这就是现在可用于构建和管理任意基于JAVA的项目的工具。我们希望创造能简化Java开发者日复一日的工作的东西,并且帮助人们理解任意基于Java的项目。
Maven的基本目标是让一个开发者在最短时间内理解一个开发任务的完整状态。为了达到这个目标,Maven尝试解决了几个值得关注的领域:
- Making the build process easy,让构建流程更简单(Maven提供了很多细节屏蔽,使用Maven不需要知道底层机制)
- Providing a uniform build system,提供一个通用的构建系统(使用Maven的POM和一系列插件来构建项目)
- Providing quality project information,提供高质量的项目信息(比如,直接从源码控制修改日志文档,交叉引用的来源,邮件表,依赖清单,单元测试报告等)
- Providing guidelines for best practices development,提供最佳实践开发的指南(目前单元测试最佳实践有:将测试源码放在一个单独的、但并行的源码树中,使用测试用例命名约定来定位和执行测试,测试用例配置自己的环境而不要依赖定制的测试条件)
- Allowing transparent migration to new features,允许透明迁移到新特性
注意,以下是关于Maven的错误理解:
- Maven是一个网站和文档工具。
- Maven扩展了ant来让你下载依赖。
- Maven是一套可复用的ant脚本。
约定优于配置
Maven是一个项目管理和构建自动化工具。对于程序员来说,最关心的是它的项目构建功能。
Maven采用纯Java编写。采用了POM(project object model)概念来管理项目,所有项目配置信息都被定义在pom.xml的文件中。由于Maven的面向项目的方法,目前Apache下绝大多数项目都已采用Maven管理。Maven还支持多种插件。
Maven使用惯例优于配置的原则。它要求在没有定制之前,所有项目都有如下结构:
- ${baseDir} —— 存放pom.xml和所有子目录
- ${baseDir}/src/main/java —— 项目的java源代码
- ${baseDir}/src/main/resources —— 项目的资源,如property文件
- ${baseDir}/src/test/java —— 项目的测试类,如Junit代码
- ${baseDir}/src/test/resources —— 测试使用的资源
一个maven项目在默认情况下会产生JAR文件。
- ${baseDir}/target/classes —— 编译后的classes
- ${baseDir}/target —— JAR文件
2. Maven和Ant
Ant的项目管理工具(作为make的替代工具)不能满足绝大多数开发人员的需要。通过检查ant构建文件,很难发现项目的相关性信息和其它元信息(如开发人员,版本或站点主页等)。
Maven除了以程序构建能力为特色之外,还提供了Ant所缺少的高级项目管理工具。由于Maven的缺省构建规则有较高的可重用性,所以常常用两三行脚本就可以构建简单项目,比Ant简洁。
Maven和Ant针对构建问题的两个不同方面。Ant为Java技术开发项目提供跨平台构建任务。Maven本身描述项目的高级方面,它从Ant借用了绝大多数构建任务。它们代表两个差异很大的工具,以下是等同组件之间的区别:
二、环境配置与简单使用
首先保证已安装JDK: http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html
Maven官网: http://maven.apache.org/download.cgi
最新的Maven版本是3.3.9,系统要求:JDK1.7。
下载apache-maven-3.3.9-bin.zip,并解压。然后将apache-maven-3.3.9目录下的bin文件夹路径,添加到环境变量path中。
在cmd中验证配置成功:
Hello World
注意,如果第一次运行maven,需要Internet连接,因为maven需要从网络下载插件。插件默认的存放路径是: C:UsersYourUserName.m2 epository。
1. 创建项目
mvn archetype:generate
maven提供了archetype来帮助我们快速勾勒出项目骨架。
注意,如果是maven2,最好指定archetype的版本,使用命令:mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate。因为maven2不会自动解析最新的稳定版本,会自动下载最新版本,可能得到不稳定的SNAPSHOT版本,导致运行失败。实际运行的是插件:maven-archetype-plugin,注意冒号的分隔,其格式为groupId:artifactId:version:goal,org.apache.maven.plugins是maven官方插件的groupId,maven-archetype-plugin是archetype插件的artifactId,2.0-alpha-5是目前该插件最新的稳定版本,generate是要使用的插件目标。
输入上述命令后,会看到一段长长的输出,有很多可用的archetype供选择,每一个archetype前面都会对应有一个编号,同时命令行会提示一个默认的编号,其对应的archetype为maven-archetype-quickstart,直接回车以选择该archetype,紧接着maven会提示输入要创建项目的groupId,artifactId,version以及包名package。
也可以直接使用如下命令,一次把groupId, artifactId, version, package参数都设定:
mvn archetype:generate -DgroupId=com.mycompany.helloworld -DartifactId=helloworld -Dpackage=com.mycompany.helloworld -Dversion=1.0-SNAPSHOT
2. 项目结构
最终生成的项目结构如下图所示。
【POM文件】pom.xml,存放在项目目录下。
archetype插件建立了一个helloworld项目,这个名字来自artifactId。在这个目录下有,有一个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.bupt.helloworld</groupId>
<artifactId>helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>helloworld</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
【源代码和资源】默认生成App.java文件, 放在src/main下。
package com.bupt.helloworld;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
【测试代码和资源】放在src/test下。此处略。
3. 构建程序
进入项目目录,构建:
cd helloworld
mvn package
注意第一次运行maven,会从网上的maven库下载需要的程序,放在本地库中。默认的本地库是%USER_HOME%.m2 epository。
构建成功后,项目目录下出现了一个新的目录 target,构建打包后的jar文件就放在该目录下。编译后的class文件放在target/classes/目录下。测试class文件放在target/test-classes目录下。
4. 运行程序
注:-cp和-classpath一样,是指定类运行所依赖其他类的路径,通常是类库、jar包等。这里用到的是helloworld-1.0-SNAPSHOT.jar包。
三、重要概念
1. 构建生命周期(Build Lifecycle)
Maven是基于构建生命周期的核心概念的。即,构建和部署一个特定项目的流程是清晰定义的。对于项目构建者来说,这意味着只需要学习一小部分命令就可以构建任意maven项目,POM会保证他们获得想要的结果。
项目构建中进行的清理、编译、测试及部署可以视作一个个生命周期,但每个项目每个人都有不同的实现方式。Maven的生命周期就是对所有构建过程进行抽象和统一。
生命周期和插件是协同工作的。Maven命令的输入往往对应了生命周期,如mvn compile表示执行默认生命周期的阶段compile。Maven的生命周期是抽象的,实际行为由插件完成,如mvn compile可能由maven-compile-plugin完成。
有三套相互独立的内置的生命周期:default,clean,site。
- Clean Lifecycle:在真正的构建之前,进行一些清理工作。
- Default Lifecycle:构建的核心部分,包括编译、测试、打包、部署等。
- Site Lifecycle:生成项目报告。
每个构建生命周期都是由一个不同的构建phases序列组成的。一个build phase代表该生命周期中的一个阶段。平时在命令行输入的命令总会对应于一个特定的阶段,比如,mvn clean,这里的clean是指clean生命周期中的clean阶段。在一个生命周期中,运行某个阶段时,它之前的所有阶段都会被运行,也就是说,mvn clean等同于mvn pre-clean clean。如果我们运行mvn post-clean,那么pre-clean,clean都会先执行。——这是Maven很重要的一个规则。
1.1 Clean Lifecycle
- pre-clean: 执行清理前的工作。
- clean:清理上一次构建生成的所有文件。
- Post-clean:执行清理后的工作。
1.2 Site Lifecycle
- Pre-site:执行生成站点文档之前的工作。
- Site:生成项目的站点文档。
- Post-site:执行生成站点文档之后的工作。
- Site-deploy:将生成的站点文档部署到特定的服务器上。
1.3 Default Lifecycle
- Validate:验证项目是正确的,并且所需的信息都可用。
- Initalize:初始化构建状态,比如,设置属性或创建目录。
- Gengerate-sources:生成编译中包含的任意源码。
- Process-sources:处理源码,比如过滤一些值。
- Generate-resources:生成package中包含的资源。
- Process-resources:复制和处理资源到目的文件夹,为打包阶段做准备。
- Compile:编译项目源码。
- Process-classe:编译后的生成文件的后置处理,比如,java类的字节码增强。
- Generate-test-sources:生成编译中包含的任意测试源码。
- Process-test-sources:处理测试源码。
- Generate-test-resources:为测试生成资源。
- Process-test-resources:复制和处理资源到测试目的文件夹。
- Test-compile:编译测试源码到测试目的文件夹。
- Process-test-classes:测试编译中生成文件的后置处理,Maven 2.0.5及以上版本。
- Test:使用一个合适的单元测试框架运行测试。这些测试代码不会被打包或部署。
- Prepare-package:执行实际打包之前必要的操作。
- Package:获取已编译的代码,打包成可发布的格式,如JAR。
- Pre-integration-test:执行集成测试之前的操作。这可能包括设置必需的环境。
- Integration-test:如果需要的话,在一个集成测试可以运行的环境中部署这个包。
- Post-integration-test:执行集成测试后的操作。这可能包括清理环境。
- Verify:运行检查来验证package是有效的、达到质量标准的。
- Install:安装package到本地repository,供其他项目依赖。
- Deploy:将最终的package复制到远程仓库,供其它开发人员与项目共享。
2. 插件机制
Maven从本质上来说是一个执行插件的框架。Maven的核心文件很小,主要任务都是由插件来完成的。例如:%local repository%orgapachemavenplugins可以看到一些下载好的插件:
Maven的插件一般分为构建插件Build Plugins,和报告插件Reporting Plugins。构建插件在构建过程中执行,它们在POM的<build/>标签下配置。报告插件在站点生成过程执行,它们在<reporting/>标签下配置。因为报告插件的结果是站点生成的一部分,报告插件应该同时是国际化和本地化的。
更多关于插件的详细信息,见http://maven.apache.org/plugins/index.html
2.1 插件目标plugin goals
一个插件通常可以完成多个任务,每一个任务就叫做插件的一个目标。
例如,编译源代码是由maven-compiler-plugin完成的,compile目标用来编译位于src/main/java目录下的主源码,testCompile目标用来编译位于src/test/java目录下的测试源码。
执行mvn install时,调用的插件和插件目标如下:
用户可以通过两种方式调用插件目标。第一种是将插件目标与生命周期阶段lifecycle phase绑定,这样用户在命令行只是输入生命周期阶段而已。例如,Maven默认将maven-compiler-plugin的compile目标与compile生命周期阶段绑定,因此,命令mvn compile实际上是先定位到compile这一生命周期阶段,然后再根据绑定关系调用maven-compiler-plugin的compile目标。
第二种方式是直接在命令行指定要执行的插件目标,例如mvn archetype:generate就表示调用maven-archetype-plugin的generate目标,这种带冒号的调用方式与生命周期无关。
2.1.1 默认的phase-plugin goals绑定关系
Generate-resource |
Plugin:descriptor |
Process-resource |
Resources:resources |
Compile |
Compiler:compile |
Process-test-resources |
Resources:testResources |
Test-Compile |
Compiler:testCompile |
Test |
Surefire:test |
Package |
Jar:jar && plugin:addPluginArtifactMetadata |
Install |
Install:install |
Deploy |
Deploy:deploy |
2.1.2 自定义的绑定
例如,将maven-source-plugin的jar-no-fork目标绑定到生命周期的package阶段,这样,以后执行mvn package命令打包项目时,在package之后会执行源代码打包,生成.jar源码包。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-source</id>
<phase>package</phase><!-- 要绑定到的生命周期的阶段 -->
<goals>
<goal>jar-no-fork</goal><!-- 要绑定的插件的目标 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
……
</build>
2.1.3 maven-antrun-plugin
让用户在Maven项目中运行ant任务。用户可以直接以ant的方式编写target,然后交给该插件的run目标去执行。在一些由ant向maven迁移的项目中,该插件尤其有用。此外,当需要编写一些自定义程度很高的任务,同时又觉得maven不够灵活时,也可以以ant的方式实现之。
2.1.4 maven-archetype-plugin
Archetype指项目的骨架。前面的命令mvn archetype:generate,实际上就是让maven-archetype-plugin生成一个简单的项目骨架,帮助开发者快速上手。Generate目标使用交互的方式提示用户输入必要的信息,以创建项目,体验更好。此外,还有其他目标帮助用户自定义项目原型,例如,你有一个产品要交付给很多客户进行二次开发,就可以为他们提供一个archetype。
2.1.5 maven-assembly-plugin
用于制作项目分发包。该分发包可能包含了项目的可执行文件、源代码、readme、平台脚本等等。maven-assembly-plugin支持各种主流的格式,如zip, tar.gz, jar和war等,具体打包哪些文件是高度可控的,例如用户可按文件级别的粒度、文件集级别的粒度、模块级别的粒度、以及依赖级别的粒度控制打包。此外,包含和排除配置也是支持的。maven-assembly-plugin要求用户使用一个名为assembly.xml的元数据文件来表述打包,它的single目标可以直接在命令行调用,也可以被绑定至生命周期。
2.1.6 maven-dependency-plugin
最大的用途是分析项目依赖。
- dependency:list能够列出项目最终解析到的依赖列表。
- dependency:tree进一步描绘项目依赖。
- dependency:analyze项目依赖潜在的问题,如果有直接使用到的却未声明的依赖,该目标就会发出警告。
- dependency:copy-dependencies将项目依赖从本地maven仓库复制到某个特定的文件夹下面。
2.1.7 maven-enforcer-plugin
创建一系列规则,强制大家遵守,包括设定java版本,设定maven版本,禁止某些依赖,禁止SNAPSHOT依赖。只要在一个父POM配置规则,然后让大家继承,当规则被破坏时,maven就会报错。除了标准的规则之外,你还可以扩展该插件,编写自己的规则。
maven-enforcer-plugin的enforce目标负责检查规则,它默认绑定到生命周期的validate阶段。
2.1.8 maven-help-plugin
一个小巧的辅助工具,最简单的help:system可以打印所有可用的环境变量和java系统属性。help:effective-pom和help:effective-settings最为有用,它们分别打印项目的有效POM和有效settings,有效POM是指合并了所有父POM(包括Super POM)后的XML,当你不确定POM的某些信息从何而来时,就可以查看有效POM。有效settings同理,特别是当你发现自己配置的settings.xml没有生效时,就可以用help:effective-settings来验证。此外,maven-help-plugin的describe目标可以帮助你描述任何一个Maven插件的信息,还有all-profiles目标和active-profiles目标帮助查看项目的Profile。
2.1.9 maven-release-plugin
帮助自动化项目版本发布,它依赖于POM中的SCM信息。release:prepare用来准备版本发布,具体的工作包括检查是否有未提交代码、检查是否有SNAPSHOT依赖、升级项目的SNAPSHOT版本至RELEASE版本、为项目打标签等等。release:perform则是签出标签中的RELEASE源码,构建并发布。版本发布是非常琐碎的工作,它涉及了各种检查,而且由于该工作仅仅是偶尔需要,因此手动操作很容易遗漏一些细节,maven-release-plugin让该工作变得非常快速简便,不易出错。maven-release-plugin的各种目标通常直接在命令行调用,因为版本发布显然不是日常构建生命周期的一部分。
2.1.10 maven-resources-plugin
为了使项目结构更为清晰,Maven区别对待Java代码文件和资源文件,maven-compiler-plugin用来编译Java代码,maven-resources-plugin则用来处理资源文件。默认的主资源文件目录是src/main/resources
,很多用户会需要添加额外的资源文件目录,这个时候就可以通过配置maven-resources-plugin来实现。此外,资源文件过滤也是Maven的一大特性,你可以在资源文件中使用${propertyName}形式的Maven属性,然后配置maven-resources-plugin开启对资源文件的过滤,之后就可以针对不同环境通过命令行或者Profile传入属性的值,以实现更为灵活的构建。
2.1.11 maven-surefire-plugin
可能是由于历史的原因,Maven 2/3中用于执行测试的插件不是maven-test-plugin,而是maven-surefire-plugin。其实大部分时间内,只要你的测试类遵循通用的命令约定(以Test结尾、以TestCase结尾、或者以Test开头),就几乎不用知晓该插件的存在。然而在当你想要跳过测试、排除某些测试类、或者使用一些TestNG特性的时候,了解maven-surefire-plugin的一些配置选项就很有用了。例如 mvn test -Dtest=FooTest 这样一条命令的效果是仅运行FooTest测试类,这是通过控制maven-surefire-plugin的test参数实现的。
2.1.12 build-helper-maven-plugin
Maven默认只允许指定一个主Java代码目录和一个测试Java代码目录,虽然这其实是个应当尽量遵守的约定,但偶尔你还是会希望能够指定多个源码目录(例如为了应对遗留项目),build-helper-maven-plugin的add-source目标就是服务于这个目的,通常它被绑定到默认生命周期的generate-sources阶段以添加额外的源码目录。需要强调的是,这种做法还是不推荐的,因为它破坏了 Maven的约定,而且可能会遇到其他严格遵守约定的插件工具无法正确识别额外的源码目录。
build-helper-maven-plugin的另一个非常有用的目标是attach-artifact,使用该目标你可以以classifier的形式选取部分项目文件生成附属构件,并同时install到本地仓库,也可以deploy到远程仓库。
2.1.13 exec-maven-plugin
exec-maven-plugin很好理解,顾名思义,它能让你运行任何本地的系统程序,在某些特定情况下,运行一个Maven外部的程序可能就是最简单的问题解决方案,这就是exec:exec的用途,当然,该插件还允许你配置相关的程序运行参数。除了exec目标之外,exec-maven-plugin还提供了一个java目标,该目标要求你提供一个mainClass参数,然后它能够利用当前项目的依赖作为classpath,在同一个JVM中运行该mainClass。有时候,为了简单的演示一个命令行Java程序,你可以在POM中配置好exec-maven-plugin的相关运行参数,然后直接在命令运行 mvn exec:java 以查看运行效果。
2.1.14 jetty-maven-plugin
在进行Web开发的时候,打开浏览器对应用进行手动的测试几乎是无法避免的,这种测试方法通常就是将项目打包成war文件,然后部署到Web容器中,再启动容器进行验证,这显然十分耗时。为了帮助开发者节省时间,jetty-maven-plugin应运而生,它完全兼容 Maven项目的目录结构,能够周期性地检查源文件,一旦发现变更后自动更新到内置的Jetty Web容器中。做一些基本配置后(例如Web应用的contextPath和自动扫描变更的时间间隔),你只要执行 mvn jetty:run ,然后在IDE中修改代码,代码经IDE自动编译后产生变更,再由jetty-maven-plugin侦测到后更新至Jetty容器,这时你就可以直接测试Web页面了。需要注意的是,jetty-maven-plugin并不是宿主于Apache或Codehaus的官方插件,因此使用的时候需要额外的配置settings.xml
的pluginGroups元素,将org.mortbay.jetty这个pluginGroup加入。
2.1.15 versions-maven-plugin
很多Maven用户遇到过这样一个问题,当项目包含大量模块的时候,为他们集体更新版本就变成一件烦人的事情,到底有没有自动化工具能帮助完成这件事情呢?(当然你可以使用sed之类的文本操作工具,不过不在本文讨论范围)答案是肯定的,versions-maven- plugin提供了很多目标帮助你管理Maven项目的各种版本信息。例如最常用的,命令 mvn versions:set -DnewVersion=1.1-SNAPSHOT 就能帮助你把所有模块的版本更新到1.1-SNAPSHOT。该插件还提供了其他一些很有用的目标,display-dependency- updates能告诉你项目依赖有哪些可用的更新;类似的display-plugin-updates能告诉你可用的插件更新;然后use- latest-versions能自动帮你将所有依赖升级到最新版本。最后,如果你对所做的更改满意,则可以使用 mvn versions:commit 提交,不满意的话也可以使用 mvn versions:revert 进行撤销。
3. POM
POM 代表工程对象模型。它是使用 Maven 工作时的基本组件,是一个 xml 文件。它被放在工程根目录下,文件命名为 pom.xml。POM 包含了关于工程和各种配置细节的信息,Maven 使用这些信息构建工程。
4. 构建配置文件
构建配置文件是一组配置的集合,用来设置或者覆盖 Maven 构建的默认配置。使用构建配置文件,可以为不同的环境定制构建过程,例如 Producation 和 Development 环境。
Profile 在 pom.xml 中使用 activeProfiles / profiles 元素指定,并且可以用很多方式触发。Profile 在构建时修改 POM,并且为变量设置不同的目标环境(例如,在开发、测试和产品环境中的数据库服务器路径)。
5. 仓库
在 Maven 的术语中,仓库是一个位置(place),例如目录,可以存储所有的工程 jar 文件、library jar 文件、插件或任何其他的工程指定的文件。
Maven 仓库有三种类型:
- 本地(local)
- 中央(central)
- 远程(remote)
6. 外部依赖
Maven的依赖管理使用的是 Maven - 仓库 的概念。但是如果在远程仓库和中央仓库中,依赖不能被满足,如何解决呢? Maven 使用外部依赖的概念来解决这个问题。
例如,让我们对在 Maven - 创建工程 部分创建的项目做以下修改:
- 在 src 文件夹下添加 lib 文件夹
- 复制任何 jar 文件到 lib 文件夹下。我们使用的是 ldapjdk.jar ,它是为 LDAP 操作的一个帮助库
现在你有了自己的工程库(library),通常情况下它会包含一些任何仓库无法使用,并且 maven 也无法下载的 jar 文件。如果你的代码正在使用这个库,那么 Maven 的构建过程将会失败,因为在编译阶段它不能下载或者引用这个库。
为了处理这种情况,需要将这个外部依赖添加到 maven pom.xml 中。
<dependencies>
的第二个 <dependency>
元素 , 阐明了外部依赖的关键概念。
- 外部依赖(library jar location)能够像其他依赖一样在 pom.xml 中配置。
- 指定 groupId 为 library 的名称。
- 指定 artifactId 为 library 的名称。
- 指定作用域(scope)为系统。
- 指定相对于工程位置的系统路径。
7. 工程模板
8. 快照
9. 构建自动化
10. 管理依赖
11. 自动化部署
12. Web应用
参考资料
http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-1-406235-zhs.html
https://maven.apache.org/guides/introduction/introduction-to-the-pom.html
http://www.yiibai.com/maven/enable-proxy-setting-in-maven.html