Maven
一、什么是Maven
Maven 是一个软件项目管理和理解工具。基于项目对象模型 (POM) 的概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。
二、Maven能做什么?
Maven 能够帮助开发者完成以下工作:
• 构建
• 文档生成
• 报告
• 依赖
• SCMs
• 发布
• 分发
• 邮件列表
三、Maven约定配置
• Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。如下所示
目录 | 目的 |
---|---|
src/main/java | 项目存放JAVA文件 |
/src/main/resources | 项目的资源,比如说property文件 |
src/test/java | 项目测试类,比如Junit |
src/test/resources | 测试用的资源 |
src/main/webapp/WEB-INF | web应用文件目录,web项目的信息,比如web.xml,本地的图片 |
target | 打包输出目录 |
/target/classes | 编译输出目录 |
/target/test-classes | 测试编译输出目录 |
Test.java | Maven只会自动运行符合该命令名规则的测试类 |
~/.m2/repository | Maven默认的本地仓库目录位置 |
Maven特点
- 项目设置遵循统一规则。
- 任意工程中共享。
- 依赖管理包括自动更新。
- 一个庞大且不断增长的库。
- 可扩展,能够轻松编写Java或脚本语言的插件。
- 只需要很少或不需要额外配置即可即时访问新功能。
- 基于模型的构建,Maven能够将任意数量的项目构建到预定义的输出类型中。如JAR、WAR或基于项目元数据的分发,而不需要在大多情况下执行任何脚本。
- 项目信息的一致性站点,使用与结构过程相同的元数据,Maven能够生成一个网站或PDF,包括你要添加的任何文档,并添加到关于项目开发状态的标准报告中。
- 发布管理和发布单独的输出,Maven将不需要额外的配置,就可以与源代码管理系统(如Subversion或Git)集成,并可以基于某个标签管理项目的发布。它可以将其发布到分发位置供其他项目使用。
- 向后兼容性,可以从旧版本Maven的多个模块移植到Maven3中。
- 子项目使用父项目依赖时,正常情况子项目应该继承父项目的依赖,无需版本号。
- 并行构建
- 更好的报错,Maven改进了错误报告,提供了Maven wiki页面链接。
Maven构建生命周期
Maven构建生命周期定义了一个项目构建到发布的过程。
一个典型的Maven构建(build)生命周期是由以下几个阶段的序列组成的:
Maven生命周期详情
Maven 有以下三个标准的生命周期:
-
clean:项目清理的处理
当我们执行 mvn post-clean 命令时,Maven 调用 clean 生命周期,它包含以下阶段:
• pre-clean:执行一些需要在clean之前完成的工作
• clean:移除所有上一次构建生成的文件
• post-clean:执行一些需要在clean之后立刻完成的工作 -
default(或 build):项目部署的处理
这是 Maven 的主要生命周期,被用于构建应用,包括下面的 23 个阶段:
生命周期 | 描述 |
---|---|
validate(校验) | 校验项目是否正确并且所有必要的信息可以完成项目的构建过程。 |
initialize(初始化) | 初始化构建状态,比如设置属性值。 |
generate-sources(生成源代码) | 生成包含在编译阶段中的任何源代码。 |
process-sources(处理源代码) | 处理源代码,比如说,过滤任意值。 |
generate-resources(生成资源文件) | 生成将会包含在项目包中的资源文件。 |
process-resources (处理资源文件) | 复制和处理资源到目标目录,为打包阶段最好准备。 |
compile(编译) | 编译项目的源代码。 |
process-classes(处理类文件) | 处理编译生成的文件,比如说对Java class文件做字节码改善优化。 |
generate-test-sources(生成测试源代码) | 生成包含在编译阶段中的任何测试源代码。 |
process-test-sources(处理测试源代码) | 处理测试源代码,比如说,过滤任意值。 |
generate-test-resources(生成测试资源文件) | 为测试创建资源文件。 |
process-test-resources(处理测试资源文件) | 复制和处理测试资源到目标目录。 |
test-compile(编译测试源码) | 编译测试源代码到测试目标目录. |
process-test-classes(处理测试类文件) | 处理测试源码编译生成的文件。 |
test(测试) | 使用合适的单元测试框架运行测试(Juint是其中之一)。 |
prepare-package(准备打包) | 在实际打包之前,执行任何的必要的操作为打包做准备。 |
package(打包) | 将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。 |
pre-integration-test(集成测试前) | 在执行集成测试前进行必要的动作。比如说,搭建需要的环境。 |
integration-test(集成测试) | 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 |
post-integration-test(集成测试后) | 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 |
verify (验证) | 运行任意的检查来验证项目包有效且达到质量标准。 |
install(安装) | 安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。 |
deploy(部署) | 将最终的项目包复制到远程仓库中与其他开发者和项目共享。 |
- site:项目站点文档创建的处理
Maven Site 插件一般用来创建新的报告文档、部署站点等。
• pre-site:执行一些需要在生成站点文档之前完成的工作。
• site:生成项目的站点文档。
• post-site: 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备。
• site-deploy:将生成的站点文档部署到特定的服务器上。
Maven 仓库
在 Maven 的术语中,仓库是一个位置(place)。
Maven 仓库是项目中依赖的第三方库,这个库所在的位置叫做仓库。
在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。
Maven 仓库能帮助我们管理构件(主要是JAR),它就是放置所有JAR文件(WAR,ZIP,POM等等)的地方。
Maven 仓库有三种类型:
• 本地(local)
• 中央(central)
• 远程(remote)
本地仓库
Maven 的本地仓库,在安装 Maven 后并不会创建,它是在第一次执行 maven 命令的时候才被创建。
运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。
默认情况下,不管Linux还是 Windows,每个用户在自己的用户目录下都有一个路径名为 .m2/respository/ 的仓库目录。
Maven 本地仓库默认被创建在 %USER_HOME% 目录下。要修改默认位置,在 %M2_HOME%\conf 目录中的 Maven 的 settings.xml 文件中定义另一个路径。
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>C:/MyLocalRepository</localRepository>
</settings>
当你运行 Maven 命令,Maven 将下载依赖的文件到你指定的路径中。
中央仓库
Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了大量常用的库。
中央仓库包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的Java项目依赖的构件都可以在这里下载到。
中央仓库的关键概念:
• 这个仓库由 Maven 社区管理。
• 不需要配置。
• 需要通过网络才能访问。
要浏览中央仓库的内容,maven 社区提供了一个 URL:http://search.maven.org/#browse。使用这个仓库,开发人员可以搜索所有可以获取的代码库。
远程仓库
如果 Maven 在中央仓库中也找不到依赖的文件,它会停止构建过程并输出错误信息到控制台。为避免这种情况,Maven 提供了远程仓库的概念,它是开发人员自己定制仓库,包含了所需要的代码库或者其他工程中用到的 jar 文件。
举例说明,使用下面的 pom.xml,Maven 将从远程仓库中下载该 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.companyname.projectgroup</groupId>
<artifactId>project</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.companyname.common-lib</groupId>
<artifactId>common-lib</artifactId>
<version>1.0.0</version>
</dependency>
<dependencies>
<repositories>
<repository>
<id>companyname.lib1</id>
<url>http://download.companyname.org/maven2/lib1</url>
</repository>
<repository>
<id>companyname.lib2</id>
<url>http://download.companyname.org/maven2/lib2</url>
</repository>
</repositories>
</project>
Maven 依赖管理
Maven 一个核心的特性就是依赖管理。当我们处理多模块的项目(包含成百上千个模块或者子项目),模块间的依赖关系就变得非常复杂,管理也变得很困难。针对此种情形,Maven 提供了一种高度控制的方法。
可传递性依赖发现
A->B->C
A->B->C(0.2)
A->C(0.1)
最短路径原则: C(0.1)
A->B->C(0.2)
A->D->C(0.1)
优先声明原则:C(0.2)
传递性依赖的scope传递规则,与第一依赖和第二依赖有关,下表第一列表示第一依赖,第一行表示第二依赖
从上表我们可以轻松得到几点信息
- 第二依赖为complie不改变第一依赖
- 第二依赖test不传递依赖
- 第二依赖provided只传递provided
- 第二依赖runtime对compile第一依赖的传递依赖是runtime
依赖范围
传递依赖发现可以通过使用如下的依赖范围来得到限制:
范围 | 描述 |
---|---|
编译阶段 | 该范围表明相关依赖是只在项目的类路径下有效。默认取值。 |
供应阶段 | 该范围表明相关依赖是由运行时的 JDK 或者 网络服务器提供的。 |
运行阶段 | 该范围表明相关依赖在编译阶段不是必须的,但是在执行阶段是必须的。 |
测试阶段 | 该范围表明相关依赖只在测试编译阶段和执行阶段。 |
系统阶段 | 该范围表明你需要提供一个系统路径。 |
导入阶段 | 该范围只在依赖是一个 pom 里定义的依赖时使用。同时,当前项目的POM 文件的 部分定义的依赖关系可以取代某特定的 POM。 |
依赖管理
通常情况下,在一个共通的项目下,有一系列的项目。在这种情况下,我们可以创建一个公共依赖的 pom 文件,该 pom 包含所有的公共的依赖关系,我们称其为其他子项目 pom 的 pom 父。 接下来的一个例子可以帮助你更好的理解这个概念。
Maven坐标
一个完整的坐标信息,由 groupId、artifactId、version、packaging、classifier 组成,如下是一个简单的坐标定义。
<groupId>org.SpringFramework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.7.RELEASE</version>
<packaging>jar</packaging>
这是 JUnit 的坐标,下面详细介绍一下各个元素。
1. groupId
定义当前 Maven 项目从属的实际项目。关于 groupId 的理解如下所示。
1)Maven 项目和实际项目不一定是一一对应的。比如 SpringFramework,它对应的 Maven 项目就有很多,如 spring-core、spring-context、spring-security 等。造成这样的原因是模块的概念,所以一个实际项目经常会被划分成很多模块。
2)groupId 不应该同开发项目的公司或组织对应。原因比较好理解,一个公司和一个组织会开发很多实际项目,如果用 groupId 对应公司和组织,那 artifactId 就只能是对应于每个实际项目了,而再往下的模块就没法描述了,而往往项目中的每个模块是以单独的形式形成构件,以便其他项目重复聚合使用。
3)groupId 的表述形式同 Java 包名的表述方式类似,通常与域名反向一一对应。
2. artifactId
定义实际项目中的一个 Maven 项目(实际项目中的一个模块)。
推荐命名的方式为:实际项目名称-模块名称。
比如,org.springframework 是实际项目名称,而现在用的是其中的核心模块,它的 artifactId 为 spring-core。
3. version
定义 Maven 当前所处的版本。如上的描述,用的是 4.2.7.RELEASE 版本。需要注意的是,Maven 中对版本号的定义是有一套规范的。具体规范请参考《版本管理》的介绍。
4. packaging
定义 Maven 项目的打包方式。
打包方式通常与所生成的构件文件的扩展名对应,比如,.jar、.ear、.war、.pom 等。另外,打包方式是与工程构建的生命周期对应的。比如,jar 打包与 war 打包使用的命令是不相同的。最后需要注意的是,可以不指定 packaging,这时候 Maven 会自动默认成 jar。
5. classifier
定义构件输出的附属构件。
附属构件同主构件是一一对应的,比如上面的 spring-core-4.2.7.RELEASE.jar 是 spring-core Maven spring-core 项目的主构。
Maven spring-core 项目除了可以生成上面的主构件外,也可以生成 spring-core-4.2.7.RELEASE-javadoc.java 和 spring-core-4.2.7.RELEASE-sources.jar 这样的附属构件。这时候,javadoc 和 sources 就是这两个附属构件的 classifier。这样就为主构件的每个附属构件也定义了一个唯一的坐标。
最后需要特别注意的是,不能直接定义一个 Maven 项目的 classifier,因为附属构件不是由 Maven 项目构建的时候直接默认生成的,而是由附加的其他插件生成的。
前面介绍的组成坐标的 5 个要素中,groupId、artifactId 和 version 是必需的,packaging 是可选的,默认是 jar,而 classifier 是不能直接定义的。同时,Maven 项目的构件文件名与坐标也是有对应关系的,一般规则是 artifactId-version[-classifier].packaging。
Maven常用命令及其作用
命令名称 | 作用 |
---|---|
maven clean | 对项目进行清理,删除target目录下编译的内容 |
maven compile | 编译项目源代码 |
maven test | 对项目进行运行测试 |
maven packet | 打包文件并存放到项目的target目录下,打包好的文件通常都是编译后的class文件 |
maven install | 在本地仓库生成仓库的安装包,可供其他项目引用,同时打包后的文件放到项目的target目录下 |
一、常用命令使用场景举例
1、mvn clean package依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)等7个阶段
package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
2、mvn clean install依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install等8个阶段
install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库
3、mvn clean deploy依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install、deploy等9个阶段
deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
二、常见问题
(一)mvn clean install 和 mvn install 的区别
1、根据maven在执行一个生命周期命令时,理论上讲,不做mvn install 得到的jar包应该是最新的,除非使用其他方式修改jar包的内容,但没有修改源代码
2、平时可以使用mvn install ,不使用clean会节省时间,但是最保险的方式还是mvn clean install,这样可以生成最新的jar包或者其他包
(二)maven两种跳过单元测试方法的区别
1、 mvn package -Dmaven.test.skip=true
不但跳过了单元测试的运行,同时也跳过了测试代码的编译
2、 mvn package -DskipTests
跳过单元测试,但是会继续编译。如果没时间修改单元测试的bug,或者单元测试编译错误,则使用第一种,不要使用第二种