Maven入门教程
什么是Maven
Maven是项目(特别是java项目)进行项目管理的工具。可以用来对项目进行依赖管理、构建、发布和分发等。
Maven包括以下几部分:
- **一组用于处理依赖管理、目录结构以及构建工作流的约定。**基于约定的标准化可以极大地简化开发过程。
- 一个用于项目配置的XML Schema:项目对象模型(Project Object Model),简称POM。
- **一个委托外部组件来执行项目任务的插件架构。**这简化了更新以及扩展Maven 能力的过程。
基本概念
标准的目录结构
Maven 约定了一个标准的目录结构,如下表。
POM大纲
构件
任何可以被 Maven 的坐标系统(参见接下来的关于 GAV 坐标的讨论)唯一标识的对象都是一个 Maven 构件。大多数情况下,构件是构建 Maven 项目所生成的文件,如 JAR。但是,只包含其他 POM(该文件本身并不产生构件)使用的定义的 POM 文件也是 Maven 构件。
Maven 构件的类型由其 POM 文件的元素指定。最常用的值是 pom、jar、ear、war 以及 maven-plugin。
POM 文件的用例
POM文件的用法有以下几种:
- 默认——用于构建一个构件
- 父POM——提供一个由子项目继承的单个配置信息源——声明这个 POM 文件作为它们的元素的值。
- 聚合器——用于构建一组声明为的项目,这些子项目位于其当前聚合器项目的文件夹中,每个都包含有它自己的 POM 文件。
作为父 POM 或者聚合器的 POM 文件的元素的值将是 pom。注意,一个 POM文件可能同时提供两项功能。
GAV 坐标
POM 定义了 5 种称为坐标的元素,用于标识 Maven 构件。其中GAV是声明一个构件必须指定的坐标,分别代表、以及。
-
:是项目或者项目组的全局的唯一标识符。
-
:用于标识和某个相关的不同的构件。
-
:是指和项目相关的主要构件的类型(对应于构件的 POM 文件中的值)。它的默认值是 jar。例如,pom、war、ear。
-
:标识了构件的版本。
-
:用于区分属于相同的 POM 但是却被以不同的方式构建的构件,当然构件的内容也不一样。如netty-all-4.1.23.Final-javadoc.jar,netty-all-4.1.23.Final-sources.jar(需要使用插件进行构建)。
依赖
项目的依赖是指编译和执行它所需要的外部构件。大多数情况下,你项目的依赖又会依赖于其他构件,称为传递依赖。
<dependencies>
<!-- 在这里添加你的依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.23.Final</version> <!--版本号-->
<scope>system</scope> <!--作用域-->
<systemPath>\...\netty-all-4.1.23.Final.jar</systemPath> <!--指定文件系统中的绝对位置-->
</dependency>
</dependencies>
元素可以具有以下值:
- compile — 编译和执行需要的(默认值)。表示被依赖项目需要参与当前项目的编译,当然后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去。
- runtime — 只有执行需要。表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。oracle jdbc驱动架包就是一个很好的例子,一般scope为runntime。另外runntime的依赖通常和optional搭配使用,optional为true。我可以用A实现,也可以用B实现。
- provided—意味着打包的时候可以不用包进去,别的设施(Web Container)会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。
- test—表示依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。比较典型的如junit。
- system—从参与度来说,也provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用。
- import—将在后面的“依赖管理”一节进行讨论。
依赖管理
1. 依赖继承
在maven多模块项目中,为了保持模块间依赖的统一,常规做法是在parent model中,使用dependencyManagement预定义所有模块需要用到的dependency(依赖)。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>${spring-cloud-starter-feign.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
然后,子model根据实际需要引入parent中预定义的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
这样做的好处:
-
依赖统一管理(parent中定义,需要变动dependency版本,只要修改一处即可);
-
代码简洁(子model只需要指定groupId、artifactId即可)
-
dependencyManagement只会影响现有依赖的配置,但不会引入依赖,即子model不会继承parent中dependencyManagement所有预定义的depandency,只引入需要的依赖即可,简单说就是“按需引入依赖”或者“按需继承”;因此,在parent中严禁直接使用depandencies预定义依赖,坏处是子model会自动继承depandencies中所有预定义依赖;
2. import scope
maven继承和java的继承类似,都是单继承,也就是说子model中只能出现一个parent标签。如果parent中预定义的依赖太多,会造成pom文件过长,不利于依赖的管理。
import scope依赖就能解决上述问题。你可以把dependencyManagement放到单独的专门用来管理依赖的pom中,然后在需要使用依赖的模块中通过import scope依赖,就可以引入dependencyManagement。例如可以写这样一个用于依赖管理的pom。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.test.sample</groupId>
<artifactId>base-parent1</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactid>junit</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactid>log4j</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
然后我就可以通过非继承的方式来引入这段依赖管理配置
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.test.sample</groupId>
<artifactid>base-parent1</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>junit</groupId>
<artifactid>junit</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactid>log4j</artifactId>
</dependency>
注意:scope=import只能用在dependencyManagement里面,且仅用于type=pom的dependency
构建的生命周期
maven的生命周期是对所有构建过程的抽象和统一。maven拥有三套独立的生命周期:clean,default和site,分别用于清理项目,构建项目和建立项目站点。每个生命周期又包含了不同的阶段,这些阶段是有顺序的,后面的阶段依赖于前面的阶段。下面是default生命周期的部分阶段清单:
- validate——检查项目是否正确,所有必需的信息是否已经就绪。
- process-sources——处理源代码,如过滤任何值。
- compile——编译项目的源代码。
- process-test-resources——复制并处理资源到测试目标目录中。
- test-compile——将测试源代码编译到测试目标目录中。
- test——使用合适的单元测试框架测试编译的源代码。
- package——将编译的代码打包为它的可分发格式,如 JAR。
- integration-test——处理并将软件包部署到一个可以运行集成测试的环境中。
- verify——运行任何的检查以验证软件包是否有效,并且符合质量标准。
- install——将软件包安装到本地存储库中,在那里其他本地构建项目可以将它引用为依赖。
- deploy——将最终的构件上传到远程存储库,以与其他开发人员和项目共享。
用户可以通过调用某个生命周期阶段和maven进行交互,用法说明如下:
//maven将会顺序执行default生命周期package阶段及其之前的所有阶段。 mvn package
//该命令调用clean生命周期的clean阶段和default生命周期的install阶段。
//该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践。
mvn clean install
插件
maven虽然定义了3套独立的生命周期,每个周期又分为不同的阶段,但是并没有直接实现它们。这些阶段(phase)就像是maven定义好的接口一样,然后由具体的maven插件来实现这些接口。
一个maven插件通常可以完成多个目标(任务),我们可以通过下面的下面的方式来单独调用插件的某个目标
mvn compile:compile //表示执行compile插件的 compile目标
maven默认绑定了一些阶段(phase)和插件目标的映射(如下图)。注意,并不是所有的阶段都会默认绑定某个插件目标,比如clean生命周期中除了clean阶段外的其他两个阶段都没有默认绑定的目标
maven默认将阶段绑定到一些目标上,我们也可以自定义阶段绑定的插件目标。
<project>
...
<build>
<plugins>
<plugin>
<!-- groupId artifactId version 是maven的坐标 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
以上示例中,声明了maven-source-plugin(使用groupId、artifactId、version指定)。然后在元素下指定多个任务,通过指定多个,就可以把相同插件的目标绑定到不同的生命周期阶段。指定任务名称(在一个插件中必须唯一), 指定到的生命周期阶段,指定插件目标。
如果没有指定,那么就会绑定到插件默认的生命周期阶段上。如果插件没有默认生命周期阶段,那么插件目标将不会被执行。
插件管理
插件管理声明了其他 POM 可以使用的插件信息,这点和依赖管理类似。
我们可以先在parent POM里面声明如下:
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
然后在子POM继承父POM的配置:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
关于 Maven 插件
在声明由 Maven 项目生成的插件时,可以省略 groupId(org.apache.maven.plugins)。
Maven并没有提供与import scope依赖类似的方式管理插件,那我们只能借助继承关系,不过好在一般来说插件配置的数量远没有依赖配置那么多,因此这也不是一个问题。
配置文件profile
profile可以让我们定义一系列的配置信息,然后指定其**条件。这样我们就可以定义多个profile,然后每个profile对应不同的**条件和配置信息,从而达到不同环境使用不同配置信息的效果。
存储库
maven存储库分为远程和本地:
- maven远程存储库是一个可以提供maven依赖上传下载的服务。可以使用nexus搭建maven远程存储库。
- 本地存储库是本地计算机上的一组目录文件,它包含从远程存储库下载下来的构件,以及本地项目构建并安装的构件。
快照和发布
远程存储库通常会为正在开发中的构件,以及那些已经发布的构件,定义不同的区域。这些区域分别称为快照存储库和发布存储库。当值是以**-SNAPSHOT**结尾时,将被认为是快照版本。
快照版本和发布版本的主要区别在于:
- 同样值的快照版本可以重复上传到存储库,每次它都会被分配一个唯一的时间戳。而 RELEASE 版本同一个版本号的包只能上传一次。
- 当项目依赖于SNAPSHOT版本时,maven会自动从存储库中下载最新的构件,默认情况下,Maven 每天检查一次更新(由仓库配置的 updatePolicy 控制)。但是对于RELEASE 版本,如果本地仓库已有相应的版本,则不会再去仓库中拉取。
所以一般在开发模式下,我们可以频繁的发布SNAPSHOT版本,以便让其它项目能实时的使用到最新的功能做联调;当版本趋于稳定时,再发布一个正式版本,供正式使用。当然在做正式发布时,也要确保当前项目的依赖项中不包含对任何SNAPSHOT版本的依赖,保证正式版本的稳定性。
总结
本文将maven的相关概念作了系统而全面的介绍,适合作为maven的入门学习。maven基于约定大于配置的原则制定了一套管理项目的规范。我们需要知道maven定义的标准目录结构,POM结构,什么是构件,如何标识构件等基本概念。同时,对于依赖管理以及插件管理,以及maven的生命周期、阶段和插件的目标也要有清晰的认识。