引用

https://www.oracle.com/cn/java/technologies/apache-maven-getting-started-1.html
https://www.oracle.com/cn/java/technologies/apache-maven-getting-started-2.html
http://www.itsoku.com/article/240#menu_15

惯例优于配置

Maven 是一个项目管理和构建自动化工具,构建时使用惯例优于配置的原则 。它要求在没有定制之前,所有的项目都有如下的结构
image.png

  • 一个 maven 项目在默认情况下会产生 JAR 文件,另外 ,编译后 的 classes 会放在 $/target/classes 下面, JAR 文件会放在 $/target 下面

构建项目

mvn archetype:generate -DgroupId=com.mycompany.helloworld -DartifactId=helloworld -Dpackage=com.mycompany.helloworld -Dversion=1.0-SNAPSHOT

archetype:generate 目标会列出一系列的 archetype 让你选择。 Archetype 可以理解成项目的模型。Maven 为我们提供了很多种的项目模型,包括从简单的 Swing 到复杂的 Web 应用。我们选择默认的 maven-archetype-quickstart
image.png
也可以通过 -D archetypeArtifactId: 指定 ArchetypeId,这里-D表示指定配置文件,通常-D与配置连写(也可分开写)。这里解释一下上文用到的配置

  • groupId: 组织名,公司网址的反写 + 项目名称
  • artifactId: 项目名-模块名
  • package:包名(可不写)
  • version:版本

配置文件

一个Maven项目所有的配置都放置在 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>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 坐标,我们就可以用它来指定我们的项目所依赖的其他项目,插件,或者父项目。

  • 此时需要注意,由于Maven采用惯例大于配置的原则,所以pom.xml中的并不是全部配置,我们可以使用下面命令进行查看(建议在刚刚创建的项目下看一遍)
mvn help:effective-pom

Maven插件

在上文中,我们用了 mvn archetype:generate 命令来生成一个项目。那么这里的 archetype:generate 是什么意思呢?archetype 是一个插件的名字,generate是目标(goal)的名字。这个命令的意思是告诉 maven 执行 archetype 插件的 generate 目标。插件目标通常会写成 pluginId:goalId

一个目标是一个工作单元,而插件则是一个或者多个目标的集合。比如说Jar插件,Compiler插件,Surefire插件等。从看名字就能知道,Jar 插件包含建立Jar文件的目标, Compiler 插件包含编译源代码和单元测试代码的目标。Surefire 插件的话,则是运行单元测试

看到这里,估计你能明白了,mvn 本身不会做太多的事情,它不知道怎么样编译或者怎么样打包。它把构建的任务交给插件去做。插件定义了常用的构建逻辑,能够被重复利用。这样做的好处是,一旦插件有了更新,那么所有的 maven 用户都能得到更新。

Maven 生命周期

Maven中定义了三套生命周期

  1. clean生命周期
  2. default生命周期
  3. site生命周期

3套生命周期是相互独立的,没有依赖关系的,而每套生命周期中有多个阶段,每套中的多个阶段是有先后顺序的,并且后面的阶段依赖于前面的阶段,而用户可以直接使用mvn命令来调用这些阶段去完成项目生命周期中具体的操作

  • 注意是调用阶段,如mvn 生命周期阶段

在上文中,我们有一个命令是:mvn package。这里的 package 是一个maven的生命周期阶段 (lifecycle phase )。

  • 再次注意!package是该生命周期的一个阶段!

那么生命周期阶段和上面说的插件目标之间是什么关系呢?插件目标可以绑定到生命周期阶段上。一个生命周期阶段可以绑定多个插件目标。当 maven 在构建过程中通过每个阶段时,会执行该阶段所有的插件目标。

maven 能支持不同的生命周期,但是最常用的是默认的Maven生命周期 (default Maven lifecycle ),生命周期默认按顺序执行,如果你没有对它进行任何的插件配置或者定制的话,那么上面的命令 mvn package 会依次执行默认生命周期中直到包括 package 阶段前的所有阶段的插件目标,图片列出了default生命周期的主要阶段
image.png

这里推荐一个查看生命周期的工具buildplan-maven-plugin

mvn fr.jcgay.maven.plugins:buildplan-maven-plugin:list

image.png

Maven 依赖管理

管理依赖反而是我们最常用,最简单的一部分,在 POM 中,依赖关系是在 dependencies 部分中定义的

<dependencies> 
    <dependency> 
      <groupid>junit</groupid> 
      <artifactid>junit</artifactid> 
      <version>3.8.1</version> 
      <scope>test</scope> 
    </dependency> 
  </dependencies>

那这个例子很简单,但是实际开发中我们会有复杂得多的依赖关系,因为被依赖的 jar 文件会有自己的依赖关系。那么我们是不是需要把那些间接依赖的 jar 文件也都定义在POM中呢?答案是不需要,因为 maven 提供了传递依赖的特性。

所谓传递依赖是指 maven 会检查被依赖的 jar 文件,把它的依赖关系纳入最终解决的依赖关系链中。

  • 这里注意!在 POM 的 dependencies 部分中,scope 决定了依赖关系的适用范围。我们的例子中 junit 的 scope 是 test,那么它只会在执行 compiler:testCompile and surefire:test目标的时候才会被加到 classpath 中,在执行 compiler:compile 目标时是拿不到 junit 的。

我们还可以指定 scope 为 provided,意思是 JDK 或者容器会提供所需的jar文件。比如说在做web应用开发的时候,我们在编译的时候需要 servlet API jar 文件,但是在打包的时候不需要把这个 jar 文件打在 WAR 中,因为servlet容器或者应用服务器会提供的。

scope 的默认值是 compile,即任何时候都会被包含在 classpath 中,在打包的时候也会被包括进去。

实战

初识

我们通过mvn help:effective-pom语句发现compiler也是一个插件

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <executions>
        <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>default-testCompile</id>
            <phase>test-compile</phase>
            <goals>
                <goal>testCompile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

可以看到maven-compiler-plugin插件绑定了生命周期的compile与test-compile阶段(通过phase)。这里id相当于是这个过程的名字,要求唯一,goal则是插件需要执行的目标。

绑定

我们尝试将maven-clean-plugin插件的clean操作绑定在validate阶段上

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <executions>
        <execution>
            <id>FHC-clean</id>
            <goals>
                <goal>clean</goal>
            </goals>
            <phase>validate</phase>
        </execution>
    </executions>
</plugin>

运行install(该阶段包含validate)发现该clean被执行
image.png

参数配置

通常运行命令的时候,我们需要-D进行大量的配置,非常的困难且不美观。我们可以通过configuration配置参数,格式如下:

<configuration>
    <目标参数名>参数值</目标参数名>
</configuration>

我们尝试跳过测试,跳过前如下(无视这里的No tests to run.)
image.png

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

image.png
跳过成功,这里跳过是整个插件的所以目标都跳过,如果只针对某个目标,则可以写在execution中