Maven入门教程

什么是Maven


Maven是项目(特别是java项目)进行项目管理的工具。可以用来对项目进行依赖管理、构建、发布和分发等。

Maven包括以下几部分:

  • **一组用于处理依赖管理、目录结构以及构建工作流的约定。**基于约定的标准化可以极大地简化开发过程。
  • 一个用于项目配置的XML Schema:项目对象模型(Project Object Model),简称POM
  • **一个委托外部组件来执行项目任务的插件架构。**这简化了更新以及扩展Maven 能力的过程。

基本概念


标准的目录结构

Maven 约定了一个标准的目录结构,如下表。

Maven入门教程

POM大纲

Maven入门教程

构件

任何可以被 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(需要使用插件进行构建)。

    Maven入门教程

依赖

项目的依赖是指编译和执行它所需要的外部构件。大多数情况下,你项目的依赖又会依赖于其他构件,称为传递依赖。

<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拥有三套独立的生命周期:cleandefaultsite,分别用于清理项目构建项目建立项目站点。每个生命周期又包含了不同的阶段,这些阶段是有顺序的,后面的阶段依赖于前面的阶段。下面是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入门教程

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的生命周期、阶段和插件的目标也要有清晰的认识。

Reference