我们最能感受到的Maven的好处应该是它的“自动化构建”与“管理依赖关系”两大功能,下面就看围绕这两大功能的Maven中的核心概念。
1. 项目目录
Maven 使用约定优于配置的原则 。它要求在没有定制之前,所有的项目都有如下的主要目录结构:
一个 maven 项目在默认情况下会产生 JAR 文件,另外 ,编译后 的 .classe文件 会放在 ${basedir}/target/classes 下面;JAR文件会放在${basedir}/target
下面。如下图所示:
2. POM (Project Object Model)
2.1 概念介绍
一个项目所有的配置都放置在 POM 文件中:定义项目的类型、名字,管理依赖关系,定制插件的行为等等。比如说,你可以配置 compiler 插件让它使用 java1.5 来编译。
<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.mycompany.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>
在 POM 中,groupId,artifactId, packaging, version 叫作 maven 坐标,它能唯一的确定一个构件。有了 maven 坐标,我们就可以用它来指定我们的项目所依赖的其他项目,插件,或者父项目。
通常,大项目一般会分成几个子项目。在这种情况下,每个子项目就会有自己的 POM 文件,然后它们会有一个共同的父项目。这样只要构建父项目就能够构建所有的子项目了。子项目的 POM 会继承父项目的 POM。另外,所有的 POM都继承了一个 Super-POM。Super-POM 设置了一些默认值,比如在前面提到的默认的目录结构,默认的插件等等,它遵循了惯例优于配置的原则。如下图:
2.2 Pom文件的继承与聚合
继承
开发中一般将公共的配置——依赖关系(包括公共的类库、插件、信息配置)放到一个父项目的pom文件,然后其它项目的pom文件,都继承该pom文件。继承的代码如下:
这里需要注意,子pom文件不是一旦继承了父pom就会无条件地继承它所有的依赖关系,即插件、类库等。如果子pom想继承父pom的某个插件,只需要引入父pom中该插件的groupId与artifactId信息(不用写该插件其它的配置信息)即可。这样子pom是可以有选择性的继承它自己所需要的东西。
聚合
即大项目中一般会有一个空的Maven项目(只有pom文件,没有java代码)作为父项目,该项目的Pom文件(Modules标签中)聚合了其它子项目的Pom文件,然后只要构建父项目就能够构建所有的子项目了。
3. Maven 插件
Maven的每个生命周期都有一个“插件”(目标)来保证。在前面,我们用了 mvn archetype:generate 命令来生成一个项目。那么这里的 archetype:generate 是什么意思呢?archetype是一个插件的名字,generate是目标(goal)的名字。这个命令的意思是告诉 maven 执行archetype 插件的 generate 目标。插件目标通常会写成
pluginId:goalId
一个目标是一个工作单元,而插件则是一个或者多个目标的集合。比如说Jar插件,Compiler插件,Surefire插件等。从看名字就能知道,Jar插件包含建立Jar文件的目标, Compiler 插件包含编译源代码和单元测试代码的目标。Surefire 插件的话,则是运行单元测试。
看到这里,估计你能明白了,mvn本身不会做太多的事情,它不知道怎么样编译或者怎么样打包。它把构建的任务交给插件去做。插件定义了常用的构建逻辑,能够被重复利用。这样做的好处是,一旦插件有了更新,那么所有的 maven 用户都能得到更新。
Maven的插件服务非常强大,比如你想打包源代码为rar压缩格式,比如你想执行sql命令,等等,只需要找到相应的Maven插件,配置好即可使用,如下图:
4. Maven 生命周期
在前面,我们用的第二个命令是:mvn package。这里的 package 是一个maven的生命周期阶段 (lifecyclephase )。生命周期指项目的构建过程,它包含了一系列的有序的阶段 (phase),而一个阶段就是构建过程中的一个步骤。
那么生命周期阶段和上面说的插件目标之间是什么关系呢?插件目标可以绑定到生命周期阶段上。一个生命周期阶段可以绑定多个插件目标。当 maven 在构建过程中逐步的通过每个阶段时,会执行该阶段所有的插件目标。
maven 能支持不同的生命周期,但是最常用的是默认的Maven生命周期 (default Mavenlifecycle )。
Maven有3套生命周期,clean、compile、site,在配置插件的时候一般都要配置插件的执行时机(即Maven的某一个生命周期),如下图:
这里不一 一介绍生命周期,只介绍几个常用的:
Clean
pre-clean:执行一些需要在clean之前完成的工作
clean:移除所有上一次构建生成的文件(清除Target文件夹)
post-clean:执行一些需要在clean之后立刻完成的工作
Compile
process-resources:复制并处理资源文件至目标目录,准备打包
compile:编译项目源代码
process-test-resources:复制并处理资源文件至目标测试目录
test-compile:编译测试源代码
test:使用合适的单元测试框架运行测试,这些测试代码不会被打包或部署。
package:接受编译好的代码,打包成可发布的个格式,如jar
install:将包安装至本地仓库,以让其它项目依赖
deploy:将最终的包复制到远程仓库,以让其它开发人员与项目共享。
Site
pre-site:执行一些需要在生成站点文当前完成的工作。
site:生成项目的站点文档。
post-site:执行一些需要在生成站点文档之后完成的工作,并未部署做准备。
site-deploy:将生成的站点文档部署到服务器上。
5. Maven 依赖管理
5.1 概念介绍
之前我们说过,maven 坐标能够确定一个项目。换句话说,我们可以用它来解决依赖关系。在 POM 中,依赖关系是在 dependencies部分中定义的。在上面的 POM 例子中,我们用 dependencies 定义了对于 junit 的依赖:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies>
我们依赖的类库,我们都可以到Maven的中央仓库去找,例如下图找Hibernate的核心包:mvnrepository.com/search.html?query=hibernate
上面的例子很简单,但是实际开发中我们会有复杂得多的依赖关系,因为被依赖的 jar 文件会有自己的依赖关系。那么我们是不是需要把那些间接依赖的 jar 文件也都定义在POM中呢?答案是不需要,因为 maven 提供了传递依赖的特性。
所谓传递依赖是指 maven 会检查被依赖的 jar 文件,把它的依赖关系纳入最终解决的依赖关系链中。针对上面的 junit 依赖关系,如果你看一下 maven 的本地库你会发现 maven 不但下载了 junit-3.8.1.jar,还下载了它的 POM 文件。这样 maven 就能检查 junit 的依赖关系,把它所需要的依赖也包括进来。
在 POM 的 dependencies 部分中,scope 决定了依赖关系的适用范围。我们还可以指定scope 为 provided,意思是 JDK 或者容器会提供所需的jar文件。比如说在做web应用开发的时候,我们在编译的时候需要servlet API jar 文件,但是在打包的时候不需要把这个 jar文件打在 WAR 中,因为servlet容器或者应用服务器会提供的。
scope 的默认值是 compile,即任何时候都会被包含在 classpath 中,在打包的时候也会被包括进去。
5.2 依赖的范围(scope)
有如下几种:
test:
指测试范围有效,编译和打包时都不使用该依赖。
compile:(为默认值)
编译范围有效,编译和运行(打包)时都会将依赖存进去
provided:
测试、编译范围都有效,最后生成war包时不会加入,例如:servlet-api,编译的时候需要该文件,但是在打包的时候不需要把这个 jar 文件打在 WAR 中,因为servlet容器或者应用服务器会提供的。打进去会出现冲突。
runtime:
编译时不依赖,运行(打包)时依赖
5.3 依赖的传递(间接依赖)
首先,依赖是可以传递的
当依赖层级相同的时候,相同的东西,会采用,近者优先。当依赖层级不同时,层级(最短)近的优先。
5.4 排除依赖
当依赖包发生冲突时,我们可以用exclusion标签排除依赖
6. Maven仓 库
6.1 概念介绍
主要介绍3种仓库,本地仓库、私服(Nexus)、中央仓库。3者的关系如下图所示:
首先,Maven本身自带一个本地仓库;然后它又为全世界的Java开发者提供了一个免费的“中央仓库”,在其中几乎可以找到任何流行的开源类库;由于中央仓库是在外网中的,如果没有私服(Nexus),本地仓库就会频繁地与中央仓库即互联网打交道,这样效率很低,所以在两者之间衍生出了一个“私服——Nexus”,私服存在于局域网中,这样本地仓库就不用频繁地与外网中的中央仓库交互,所以效率就会大大提高。
6.2 修改本地仓库
本地仓库的位置默认为:${user.home}/.m2/repository。例如下图:
修改本地仓库位置的步骤如下:
新建本地工厂的文件夹,如我建在,与Maven同目录下:
将F:\maven\apache-maven-3.2.3\conf文件夹下的Settings.xml文件夹拷贝到上面新建的本地工厂的同级目录位置
修改本地工厂同目录下的Settings.xml文件中的内容,设置本地仓库的位置为,上面新建的仓库的目录
到此完成。
6.3 中央仓库的位置
中央仓库的位置在如下目录中:
${MAVEAN_HOME}\lib\maven-model-builder.jar\org\apache\maven\mode\pom.xml中,如下图:
Nexus的安装与介绍在后面的文章中介绍。
7. 总结
Maven提倡“约定优于配置”,它的项目的目录结构,测试方法的命名等都有一定的要求。
Maven是基于Pom的,一个Maven项目所有的配置都放置在 POM 文件中:定义项目的类型、名字,管理依赖关系,定制插件的行为等等。Pom文件之间还可以继承、聚合等。
Maven很强大,很大一方面是它的插件服务非常强大,Maven本身基本不怎么做事,它基本是调用一些插件来做事。Maven有3套生命周期,clean、compile、site,而每个生命周期中的每个步骤都有一个目标插件来支持。配置每个插件时也都会指明插件的运行时机(即Maven生命周期中的某个步骤)。
Maven自带强大的依赖管理系统,配置了某个依赖,确定了某个构件的坐标,Maven就能帮我们自动下载构件。
Maven本身自带一个本地仓库;然后它又为全世界的Java开发者提供了一个免费的“中央仓库”,为了解决本地仓库频繁与中央仓库(存在于外网中)交互,导致效率低的问题,又衍生出了私有仓库,即私服(Nexus)。