BinaryTom

导航

Maven学习笔记

一 Maven

1、简介

Maven是一款优秀的构建工具,主要服务于基于Java平台的依赖管理、项目信息管理和项目构建。它可以帮助我们自动化构建过程,从清理、编译、测试到生成报告。使用它可能需要很多理由,插件、构建、依赖管理等等;也可能不需要任何理由。只因为它有一个拥有全世界最多Java开源软件包的中央仓库。

2、安装

关于Maven的安装也比较简单,注意以下几点即可:
1. 确认Java已安装并且环境变量已配置
2. 下载安装包并且解压
3. 设置Maven的环境变量

二 Maven的简单使用

Maven项目的核心是pom.xml,POM定义了项目的基本信息,描述项目如何构建,声明项目依赖。一个最简单的pom.xml格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>groupid</groupId>
    <artifactId>artifactid</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>PROJECT NAME</name>

</project>

modelVerson指定了pom模型的版本,在Maven2、3中只能是4.0。而在这个pom中最重要的是包含groupIdartifactIdversion,他们定义了一个项目的坐标。至于version中的SNAPSHOT则表明该项目处于开发中,是不稳定的版本。

1、一些约定

一些约定俗成的东西,在形成了通用规范、标准之后。会带来许多好处,减少学习成本、减少配置等,而Maven最核心的设计理念之一就是“约定由于配置”(Convention Over Configuration)。因此Maven在用户没有特殊配置时,会假定用户的项目是按照如下约定的:
1. 源码目录为src/main/java/
2. 编译输出目录为/target/classes/
3. 打包方式为jar
4. 包输出目录为target/

使用Archetype生成项目骨架

遵从约定好处自然不必多说,可同时也会带来一些约束。比如每次的项目都需要根据约定创建一个Maven的骨架结构,这种重复的工作会让人变得不爽,尤其是程序员这种讨厌重复的物种。不过Maven也提供了archetype来帮助我们生成骨架,只需要简单的运行mvn archetype:genreate。如果IDE中就更方便了,可以直接新建Maven项目的骨架。

2、坐标(Coordinate)

坐标,定位信息,就是可以通过一组信息可以定位到一个唯一位置。在Maven同样如此,Maven通过一组元素定义了坐标:groupId、artifactId、version、packing、classifier。一组坐标定义如下:

    <groupId>org.sonatype.nexus</groupId>
    <artifactId>nexus-indexer</artifactId>
    <version>2.0.0</version>
    <packaging>jar</packaging>
  • groupId:定义当前Maven项目隶属的实际项目组,通常为公司域名反向+项目组名。通常为项目中的包名。
  • artifactId:定义该项目中的一个项目模块,推荐使用实际项目名作为artifactId的前缀。nexus-indexer,nexus为项目名,indexer为模块名。
  • version:版本号。
  • packaging(可选):定义该模块的打包方式。
  • classifier(不能直接定义):帮助定义构建输出的一些附属构件。

在此基础上,项目构件的文件名也是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging。同时Maven仓库的布局也是基于Maven的坐标,构件的路径与坐标的大致对应关系为groupId/artifactId/version/artifactId-version.packaging

3、依赖(Dependency)

Maven的一个好处就是可以帮助管理项目中的依赖,在每一个构件都有了唯一坐标的基础上,就可以通过这些坐标来对依赖的构件进行定位。不过除了坐标之外Maven还提供了一些其他元素来帮助管理项目依赖。

依赖配置

一个依赖声明可以包含如下的元素。

    <dependencies>
        <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <type>...</type>
            <scope>...</scope>
            <sptional>...</sptional>
            <exclusions>
                <exclusion>
                ...
                </exclusion>
            ...
            </exclusions>
        </dependency>
    </dependencies>
  • groupId、artifactId、version:依赖的坐标,最重要的信息。
  • type:依赖的类型,对应坐标的packaging,默认为jar
  • scope:依赖的范围
  • optional:标记依赖是否可选
  • exclusions:排除传递依赖,解决依赖冲突。

依赖范围

依赖范围用元素表示,依赖范围是用来控制依赖和三种classpath(编译时classpath、测试classpath、运行时classpath)的关系。Maven有如下几种依赖关系:
- compile:编译依赖范围。对于编译、测试、运行三种都有效。
- test:测试以来范围。只对于测试classpath有效(JUnit)。
- provided:已提供依赖范围。对于编译和测试classpath有效,但在运行时无效(servlet-api)。
- runtime:运行时依赖范围。对于测试和运行classpath有效,但在编译时无效(jdbc驱动)。
- system:系统依赖范围。和provided范围一致,但是必须通过systemPath指定依赖路径。
- import:导入依赖范围。作用于dependencyManagement元素,将目标pom的作用于dependencyManagement配置导入并合并到当前作用于dependencyManagement元素种。

传递性依赖

依赖范围除了控制与三种classpath的关系外,还对传递性依赖产生影响。如表所示,最左边一列表示第一直接依赖范围,最上边一行表示第二直接依赖范围,中间的交叉单元格则为传递依赖范围。

compile test provided runtime
compile compile
test test
provided provided provided
runtime runtime

依赖调解原则

传递性依赖在简化和方便了依赖声明的同时,也会带来一些依赖冲突。这时Maven的依赖调解原则会根据以下两个原则来选择使用的依赖:
1. 第一原则:路径最近者优先
2. 第二原则:路径相同时,第一声明者优先

4、仓库

在Maven中,把项目的依赖、插件或者项目构建的输出称之为构件,同时坐标机制为每个构件定义了相同的使用方式。因此Maven可以在某个位置存储所有Maven项目共享的构件,也就是我们所说的仓库。对于Maven来说仓库分为本地和远程两种仓库。在根据坐标寻找构件时,会首先查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要其他版本,则会去远程仓库下载。如果本地和远程的仓库都没有需要的构件,就会报错。
Maven仓库

本地仓库

默认本次仓库地址为~/.m2/repository/,也就是用户路径下的.m2文件夹中的repository。可以编辑文件~/.m2/settings.xml,设置localRepository来设置想要的仓库地址。例如:

<settings>
    <localRepository>D:\m2\repository\</localRepository>
</settings>

同时一个构件只有在本地仓库之中,才能被其他Maven项目使用。在项目中执行mvn clean install命令,就可以将某个项目构建并且安装到本地仓库之中。

中央仓库

中央仓库是Maven默认的远程仓库,在Maven的安装文件中自带了中央仓库的配置,放在所有Maven项目都会继承的超级POM中。

私服

私服是搭建在局域网内的仓库服务,供局域网内的Maven用户使用。在用户的Maven需要下载构件的时候,会从私服请求,如果私服上不存在,则会从外部的远程仓库下载,缓存在私服上之后,再为局域网内的下载请求提供服务。此外,一些无法从外部仓库下载到的构件也能从本地上传到私服上供局域网内用户使用。使用私服有如下一些好处:
- 节省外网带宽
- 加速Maven构建
- 部署第三方构件
- 提高稳定性,增强控制
- 降低中央仓库负荷

三 Maven的生命周期和插件

1、生命周期

Maven共有三套相互独立的生命周期,分别为clean、default和site。clean的生命周期的目的是清理项目;default生命周期的目的是构建项目;而site生命周期的目的是简历项目站点。

clean生命周期

生命周期 说明
pre-clean 执行一些清理前需要完成的工作
clean 清理 上一次构建生成的文件
post-clean 执行一些清理后需要完成的工作

default生命周期

生命周期 说明
validate
generate-sources
process-sources
generate-resources
process-resources 复制并处理资源文件,至目标目录,准备打包。
compile 编译项目的源代码。
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources 复制并处理资源文件,至目标测试目录。
test-compile 编译测试源代码。
process-test-classes
test 使用合适的单元测试框架运行测试。这些测试代码不会被打包或部署。
prepare-package
package 接受编译好的代码,打包成可发布的格式,如 JAR 。
pre-integration-test
integration-test
post-integration-test
verify
install 将包安装至本地仓库,以让其它项目依赖。
deploy 将最终的包复制到远程的仓库,以让其它开发人员与项目共享。

site生命周期

生命周期 说明
pre-site 执行一些需要在生成站点文档之前完成的工作
site 生成项目的站点文档
post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
site-deploy 将生成的站点文档部署到特定的服务器上

使用命令行调用生命周期阶段

各个生命周期相互独立,而一个生命周期的阶段是有前后依赖关系的。
- $mvn clean:这个命令调用了clean生命周期的clean阶段,实际执行的阶段为pre-clean和clean两个阶段。
- $mvn test:调用default生命周期的test阶段,实际执行的阶段为default周期从开始(validate)到test阶段的所有阶段。
- $mvn clean deploy site-deploy:该命令结合了三个生命周期,且deploy和site-deploy分别为各自生命周期的最后一个阶段。因此该命令实际执行的阶段为clean的pre-clean、clean阶段,和default、site生命周期的全部阶段。

2、插件

插件目标(Plugin Goal)

为了能够复用代码,插件本身往往具有多个功能,而在插件里面,每个功能就是一个插件目标。例如maven-dependency-plugin有十多个目标,每个目标对应一个功能,例如:
- dependency:analyze
- dependency:tree
- dependency:list
这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。

插件绑定

Maven的核心仅定义了抽象的生命周期,具体的任务是交由插件完成。因此Maven的生命周期与插件相互绑定,才能用以完成某个具体的构建任务。不过为了能让用户可以几乎不用配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定和很多插件的目标,当我们使用命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。

自定义绑定

除了默认的内置绑定外,用户也能够自己选择将某个插件目标绑定到生命周期的某个阶段上。只需使用如下配置即可:

<build>
    <plugins>
        <!-- 自定义绑定,创建项目的源码jar -->
        <plugin>
            <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>verify</phase>
                    <goals>
                        <!-- 插件目标 -->
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

这样在使用mvn verify时就会使用配置绑定的插件。

四 聚合与继承

1、聚合

用户可以在一个打包方式为pom的Maven项目中生命任意数量的module元素来实现模块的聚合,为了方便构建项目,通常将聚合模块放在项目目录的最顶层。聚合模块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>groupId</groupId>
    <artifactId>artifactId</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!-- 打包方式必须为pom-->
    <packaging>pom</packaging>
    <name>PROJECT NAME</name>
    <modules>
        <module>module1</module>
        <module>module2</module>
    </modules>
</project>

聚合模块与其他模块的目录结构并非一定要为父子关系,也可以使用平行目录结构,聚合模块的POM也需要做出相应的修改,指向正确的目录:

    <modules>
        <module>../module1</module>
        <module>../module2</module>
    </modules>

2、继承

父模块定义

在Maven中也提供了继承这一面向对象的思想,可以在父POM中声明一些配置供子POM继承,以实现配置复用,和方便管理的目的。声明父POM同样十分简单:

<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>groupId</groupId>
    <artifactId>prefix-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!-- 打包方式必须为pom-->
    <packaging>pom</packaging>
    <name>Parent</name>
</project>

子模块继承

然后需要让其他模块继承它,就可以共用父POM中的配置了,需要使用parent元素声明父模块,并且指定坐标的三个元素是必须的:

<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>

    <parent>
        <groupId>groupId</groupId>
        <artifactId>../prefix-parent/pom.xml</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>prefix-module1</artifactId>
    <name>Module1</name>

    <dependencies>
    ...
    <\dependencies>

    <build>
        <plugins>
        ...
        </plugins>
    </build>
</project>

Maven可以从父POM中继承的元素如下:

  • groupId:项目组ID,项目坐标的核心元素
  • version:项目版本,项目坐标的核心因素
  • description:项目的描述信息
  • organization:项目的组织信息
  • inceptionYear:项目的创始年份
  • url:项目的URL地址
  • developers:项目的开发者信息
  • contributors:项目的贡献者信息
  • distributionManagement:项目的部署配置
  • issueManagement:项目的缺陷跟踪系统信息
  • ciManagement:项目的持续集成系统信息
  • scm:项目的版本控制系统西溪
  • malilingLists:项目的邮件列表信息
  • properties:自定义的Maven属性
  • dependencies:项目的依赖配置
  • dependencyManagement:项目的依赖管理配置
  • repositories:项目的仓库配置
  • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
  • reporting:包括项目的报告输出目录配置、报告插件配置等

依赖管理

虽然Maven可以继承dependencies元素,但是却存在问题,因为无法确定所有的模块都会需要父模块的依赖,让与依赖完全无关的模块继承这些依赖显然是不合理的。Maven提供的dependencyManagement元素,既能让子模块集成到父模块的依赖配置,又可以保证使用的灵活性。例如,我们可以在parent中加入这样的dependencyManagement配置:

<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>groupId</groupId>
    <artifactId>prefix-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Parent</name>
    <properties>
        <springframework.version>3.2.7</springframework.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>  
                <artifactId>spring-core</artifactId>  
                <version>${springframework.version}</version>  
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

它并不会引入或是下载实际的jar包,而只是对依赖的配置进行管理,如果我们想在其他模块中引入该jar包的话,还需要在该模块的pom中添加如下配置:

<dependencies>  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-core</artifactId>  
    </dependency>  
</dependencies> 

这样会使依赖的引入和使用更加灵活,同时在整体的依赖配置管理上也达到了一处配置、到处使用的目的。至于在前面说到的为import的以来范围,则只有在dependencyManagement元素下面才有效果,它可以将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。

以上是我在学习Maven过程中的一些总结,恩,就这些。

关于Maven中的SNAPSHOT版本和RELEASE版本的区别: SNAPSHOT在上传时会在后面自动生成一个快照号,如果依赖了SNAPSHTO版本的话,每次构建都会自行下载最新的包,可能会由于依赖的包改动而造成兼容性问题。RELEASE则表示稳定的版本,不会出现这种情况,因此正式上线的代码中不应依赖SNAPSHOT版本。

参考资料

posted on 2018-04-05 15:09  BinaryTom  阅读(125)  评论(0编辑  收藏  举报