maven学习笔记

教程:Maven实战

卍,生命周期 与 插件目标

卍,超级POM内容:

<?xml version="1.0" encoding="UTF-8"?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

<!-- START SNIPPET: superpom -->
<project>
  <modelVersion>4.0.0</modelVersion>

  <repositories>
    <repository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
      <releases>
        <updatePolicy>never</updatePolicy>
      </releases>
    </pluginRepository>
  </pluginRepositories>

  <build>
    <directory>${project.basedir}/target</directory>
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>
    <pluginManagement>
      <!-- NOTE: These plugins will be removed from future versions of the super POM -->
      <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
      <plugins>
        <plugin>
          <artifactId>maven-antrun-plugin</artifactId>
          <version>1.3</version>
        </plugin>
        <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
          <version>2.2-beta-5</version>
        </plugin>
        <plugin>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>2.8</version>
        </plugin>
        <plugin>
          <artifactId>maven-release-plugin</artifactId>
          <version>2.5.3</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <reporting>
    <outputDirectory>${project.build.directory}/site</outputDirectory>
  </reporting>

  <profiles>
    <!-- NOTE: The release profile will be removed from future versions of the super POM -->
    <profile>
      <id>release-profile</id>

      <activation>
        <property>
          <name>performRelease</name>
          <value>true</value>
        </property>
      </activation>

      <build>
        <plugins>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-source-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-sources</id>
                <goals>
                  <goal>jar-no-fork</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-javadoc-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-javadocs</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-deploy-plugin</artifactId>
            <configuration>
              <updateReleaseInfo>true</updateReleaseInfo>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

</project>
<!-- END SNIPPET: superpom -->

卍 Maven概述

1. 构建:清理、编译、运行单元测试、生成文档、打包、部署等工作叫做构建。Maven不是Java领域唯一的构建管理的解决方案,其他方式还有如下:

  • IDE:IDE一般都集成了构建功能,但IDE的构建依赖大量的手工操作。编译、测试、代码生成等工作都是相互独立的,很难一键完成所有的工作。很难在项目中统一所有的IDE配置,每人都有自己的喜好,所以有时候在机器A上可以成功运行的任务到了机器B的IDE中可能会失败。合理的利用IDE而不是过度的依赖它,对于构建这样的任务,在IDE中一次次的点击鼠标是愚蠢和低效的,Maven是这方面的专家,而且主流IDE中都集成了Maven,可以在IDE中方便的使用Maven执行构建。
  • Make:Make也许是最早的构建工具,Make由一个Makefile的脚本文件驱动。Make缺点是不能实现(至少很难)跨平台的构建。另外Makefile的语法也是个问题,Make构建失败的原因往往是一个难以发现的空格或Tab使用错误。
  • Ant:Another Neat Tool。最早用来构建著名的Tomcat。Ant的创作者创作它的动机就是受不了Makefile的语法格式。可以将Ant看做是Java版本的Make,正是因为使用了Java,所以Ant是跨平台的。Ant使用XML定义构建脚本,相对Makefile来说更加友好。Ant很长时间都没有依赖管理,需要用户手改管理依赖。后来Ant借助Ivy管理依赖。

2. Maven的用途之一是用于构建。在Maven之前,10个项目可能有10种构建方式,Maven标准化了构建过程,有了Maven之后所有项目的构建命令都是简单一致的。

卍 Maven的安装与配置

※,WIndows下安装Maven

1. Maven安装依赖JDK,先安装JDK。

2.  下载Maven的zip包,解压至 D:softwareapache-maven-3.6.3;

3. 配置环境变量M2_HOME,值为D:softwareapache-maven-3.6.3; Path环境变量添加%M2_HOME%in;

  • 注:在windows中的CMD中输入命令时,windows会首先在当前目录中寻找可执行文件的脚本,如果没找到,windows就会接着遍历环境变量Path中定义的路径。

4. 安装完成,运行mvn -v查看是否成功。

※,升级Maven

1. 升级Maven很简单,只需下载新的安装包,解压至某个目录,然后将M2_HOME环境变量指向这个目录即完成了Maven的升级。

※,Linux下安装Maven

※,安装目录分析(各个文件(夹)的作用)

  • bin: 该目录包含了mvn运行的脚本,这些脚本的作用是:准备好classpath和相关Java系统属性,然后执行Java命令。所以运行mvn命令实际上就相当于运行Java命令!
  • boot: 该目录只包含一个jar文件,plexus-classworlds是一个类加载器框架,相较于默认的Java类加载器,它提供了更丰富的语法以方便配置。Maven使用该框架加载自己的类库。
  • conf: 包含了很重要的一个文件:settings.xml。如果使用此目录下的这个settings.xml文件,可以全局控制Maven的配置(全局即计算机上的所有用户),更好的实践是将其复制到用户名录~/.m2/目录下,这样只对当前用户有效,而且复制到~/.m2/目录下还有一个好处是方便升级,不需要每次升级都重新复制一份settings.xml文件了。
  • lib: 该目录包含了Maven运行时需要的全部类库。

※,·mvn help:system`

  • 该命令会打印出所有的Maven所依赖的 Java系统属性和环境变量·,如果电脑上装了多个JDK,运行mvn命令时所使用的 JDK 可以从此命令中查看到。
  • Intellij IDEA 可以为不同的项目配置不同的Maven运行时依赖的JDK,具体设置路径:File | Settings | Build, Execution, Deployment | Build Tools | Maven | Runner

※,设置HTTP代理: 在setting.xml文件中添加如下配置即可设置代理

<settings>
……
    <proxies>
        <proxy>
            <id>my-proxy</id>
            <active>true</active>
            <protocol>http</protocol>
            <host>218.14.227.197</host>
            <port>3128</port>
            <username>***</username>
            <password>***</password>
            <nonProxyHosts>repository.mycom.com|*.google.com</nonProxyHosts>
        </proxy>
    </proxies>
……
</settings>
  • proxies下可以设置多个proxy元素,如果声明了多个proxy元素,默认第一个被激活的proxy会生效。active的值为true表示激活该代理。
  • nonProxyHost 元素用来指定哪些域名不需要代理,可以使用 “|” 符合来分隔多个域名,同时也支持通配符。

※,Maven安装最佳实践(由于时代变化,一些不再是必须的了,但是仍可参考)

  • 设置MAVEN_OPTS环境变量:由于mvn命令实质就是Java命令,因此运行Java命令可用的参数当然也可以在运行mvn命令时使用。这个时候MAVEN_OPTS环境变量就能排上用场了。通常需要设置MAVEN_OPTS环境变量的值为 -Xms128m-Xms512m。因为Java默认的最大可用内存往往不能满足Maven运行的需要(大型项目使用Maven生成站点时需要占用大量内存)。
  • 配置用户范围 settings.xml(参见上文安装目录分析)
  • 不要使用IDE内置的Maven。IDE内置的Maven一般较新,但是不稳定,而且往往和在命令行使用的Maven不是同一个版本。这就会导致两个潜在的问题:
    • 较新的版本存在不稳定因素,容易造成一些难以理解的问题;
    • 除了IDE,也经常会使用命令行的Maven,如果版本不一致,容易造成构建行为的不一致。因此应该在IDE中配置与命令行版本一致的Maven。

卍 Maven使用入门

 ※,hello-world 示例:

1. 编写POM文件,如下

1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.com/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.com/POM/4.0.0 http://maven.apache.com/xsd/maven-4.0.0.xsd">
3     <modelVersion>4.0.0</modelVersion>
4 
5     <groupId>com.cosfuture.eiduo</groupId>
6     <artifactId>maven-study</artifactId>
7     <version>1.0-SNAPSHOT</version>
8     <name>Maven Hello World Project</name>
9   <packaging>jar</packaging> 10 </project>
  • 第一行为XML头,指定了XML文档的版本和编码方式。
  • project元素是所有pom.xml的根元素。它还声明了一些POM相关的命名空间及xsd元素,虽然这些属性不是必须的,但是这些属性能够让第三方工具(如IDE中的xml编辑器)帮助我们快速的编辑和检查POM。
  • 根元素下的第一个子元素 modelVersion 指定了当前POM模型的版本,对于Maven 2 和 Maven 3来讲,它只能是4.0.0。
  • <groupId>、<artifactID>、<version>三个元素定义了一个项目基本的坐标。
    • <groupId>定义了项目属于哪个组,一般是定位到某公司的某个项目,如<groupId>com.cosfuture.eiduo</groupId>,  公共通用项目则为<groupId>com.cosfuture</groupId>
    • <artifactId>定义了当前Maven项目在组中唯一的ID。
  • 最后一个name元素声明了一个对用户更加友好的项目名称,不是必须的,但为了方便信息交流最好加上。

2. 编写Java代码

  • Maven约定项目主代码位于 src/main/java 目录中,Maven会自动搜寻该目录找到项目主代码。在此目录下新建目录 com/cosfuture/eiduo/maven/helloworld, 然后新建Java类:HelloWorld,代码如下:
    package com.cosfuture.eiduo.maven.helloworld;
    
    public class HelloWorld {
        public String sayHello() {
            return "Hello Maven!";
        }
        public static void main(String[] args) {
            System.out.println("Hello World!");
        }
    }
  • 注意该Java类的包名为:com.cosfuture.eiduo.maven.helloworld,这与pom文件中定义的groupId 和 artifactId 是吻合的。一般来说Java类的包都应该基于项目的groupId和artifactId,这样更加清晰,更加符合逻辑,也方便搜索Java构件或者Java类。
  • 运行·mvn clean compile` 可以执行清理target/,编译代码的工作。
  • 从Maven的输出中,可以看到Maven进行了哪些工作。其中的  clean:clean,compiler:compile 等对应了Maven的一些插件和插件的目标,如clean:clean是maven-clean-plugin插件的clean目标。compiler:compile是maven-compiler-plugin的compile目标。注:·compiler:compile在最新版Maven格式为: maven-compiler-plugin:3.8.1:compile·
  • maven-compiler-plugin插件的一些配置信息:有时候maven执行compile工作的时候会报错,如:不再支持源选项 5。请使用 7 或更高版本。这时可以在POM文件中配置compiler插件,使其支持不同版本的JDK,同时还可以指定编译时使用的编码等
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>15</source> //这里使用了JDK 15
                        <target>15</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>

3. 编写单元测试代码:目标:让Maven执行自动化测试

  • Maven项目默认的测试代码目录是 src/test/java, 在编写测试用例前先创建此目录。
  • 在Java世界中,JUnit是事实上的单元测试标准,要使用JUnit,首先需要为Hello World 项目添加一个JUnit依赖:
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>

    添加了JUnit的基本坐标后,Maven就会自动从settings.xml中配置的仓库中下载JUnit的jar包。元素scope代表的是依赖范围,各个值得含义如下:

    • 默认值是 compile;表示该依赖对主代码和测试代码都有效。
    • 值为 test 表示该依赖只对测试有效,换句话说,测试代码中import JUnit代码没有问题,但是如果在主代码中import JUnit代码就会造成编译错误。
  • 创建对应的package文件夹:com.cosfuture.eiduo.maven.helloworld,然后创建HelloWorldTest类,代码如下:
    package com.cosfuture.eiduo.maven.helloworld;
    
    import org.junit.Assert;
    import org.junit.Test;
    
    public class HelloWorldTest {
        @Test
        public void testSayHello() {
            HelloWorld helloWorld = new HelloWorld();
            String result = helloWorld.sayHello();
            Assert.assertEquals("Hello Maven!", result);
        }
    }

    在JUnit 3中,约定所有需要执行测试的方法都已test开头,在Junit 4中,则约定需要执行的测试方法已@Test进行标注,方法名称则不必以test开头了。

  • 运行·mvn clean test· 则会自动执行 清理、编译并自动执行测试代码并输出测试报告,显示一共运行了多少测试,失败了多少,出错了多少,跳过了多少等等。自动测试实际运行的插件是maven-surefire-plugin (mvn surefire:test)。

4. 打包和运行

  • POM文件中可以指定打包类型,  <packaging>jar</packaging>; 不写则默认为jar。
  • 运行·mvn clean package`命令可以进行打包,jar:jar即jar插件的jar目标将项目主代码打包成一个名为 artifactId-version.jar 的jar包,该文件位于target/目录下。如果需要可以在POM中使用<finalName>元素指定打包的名称(位于<build>元素中)。将打包好的jar文件复制到其他项目的Classpath中就可以使用该jar包中的代码了。但是如何能让其他项目直接引用这个jar包呢?还需要一个步骤:install。
  • 运行·mvn clean install`可以先打包,然后将此jar包安装(复制)到本地的Maven仓库中。安装到仓库之后,本地其他的项目便可以直接依赖此jar包并使用此jar包中的代码了。
  • 默认生成的jar包是无法直接使用 java -jar xxx.jar 命令运行的,因为带有main方法的类信息没有被添加到manifest中(打开jar文件中的META-INF/MANIFEST.MF文件,将无法看到Main-Class一行)。为了生成可执行的jar文件,需要借助 ·maven-shade-plugin`插件,该插件配置如下: 
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.4</version>
        <!--设置为true(默认值即为true)会生成一个dependency-reduced-pom文件,当需要把这个用shade生成的jar作为其他模块的依赖时很有用,
    dependency-reduced-pom不会包含shade jar中已经存在的jar,从而避免了无用的重复。-->
    <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.cosfure.eiduo.maven.helloworld.HelloWorld</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>

    再次运行·mvn clean package`命令,会在target/目录下生成两个jar包:

    • hello-world-1.0-SNAPSHOT.jar。 这个jar包是带有Main-Class信息的可运行jar;这个jar包 包含了项目运行所需要的的一切代码,包含自身的代码和引用的第三方类库代码。所以这个jar包比原始的jar包要大。 ·java -jar hello-world-1.0-SNAPSHOT.jar` 可以看到输出main方法中的代码。

    • original-hello-world-1.0-SNAPSHOT.jar。这个是原始的jar。这个jar包只包含自身的代码,不包含引用的第三方类库的代码,这个jar包比较小。

5, 总结:

  • mvn clean compile
  • mvn clean test  //执行 test 命令前会先执行compile命令
  • mvn clean package // 执行package命令前会先执行test命令
  • mvn clean install // 执行install命令前会先执行package命令

※,使用Archetype生成项目骨架

1. Maven中有一些约定,如根目录下放置pom.xml文件,在 src/main/java 目录中放置主代码,在 src/test/java 目录中放置项目的测试代码等,每次都手动建立这些目录很枯燥,Maven提供了一个插件maven-archetype-plugin 可以根据模板自动生成项目骨架。使用方法如下:

首先Maven命令的全格式如下(注意冒号分割):·mvn <groupId>:<artifactId>:<version>:<goal>·; Maven3中用户即使不指定版本,Maven也只会解析最新的稳定版本,但是Maven2中如果不指定版本则Maven会自动下载最新版本,进而可能得到不稳定的SNAPSHOT版本,导致运行失败!

Maven 3直接运行命令: `mvn archetype:generate`

Maven2最好运行mvn命令的全模式:·mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate·

然后选择模板(默认是quickstart),填写groupId、artifactId、version、package,Maven会自动生成骨架,一个示例如下:

......
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7:
Define value for property 'groupId': com.java.tong
Define value for property 'artifactId': study
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' com.java.tong: : com.java.tong.maven
Confirm properties configuration:
groupId: com.java.tong
artifactId: study
version: 1.0-SNAPSHOT
package: com.java.tong.maven
 Y: : y
......

可以自己开发Maven Archetype,使用自定义的archetype来快速生成项目骨架。

卍 坐标与依赖

※,坐标

1. Maven坐标的元素包含:groupId(必须), artifactId(必须), version(必须), packaging(可选), classfier(不能直接定义)。

  • groupId:定义当前Maven项目所隶属的实际项目。通常,一个Maven项目是实际项目的一个模块,比如SpringFramework这个实际项目有很多模块,每个模块都是一个Maven项目,如spring-core,spring-context等等。groupId通常情况下精确到某个公司/组织的某个项目。
  • artifactId:定义实际项目中的一个模块。推荐的做法是使用实际项目名称(即groupId中的关键词名称)作为artifactId的前缀,比如使用spring-core使用spring作为前缀。
  • version:定义Maven项目的版本。
  • packaging:定义Maven项目的打包方式。默认值是jar。打包方式不同,构建时使用的命令也不同,如jar和war会使用不同的构建命令。
  • classifier:用来帮助定义构建输出的一些附属构件。附属构件如: Java文档 javadoc.jar, 其classifier为javadoc, 源代码sources.jar,其classifier为sources。注意不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。

2. 

※,依赖

1. POM文件根元素project下的dependencies元素可以包含一个或多个dependency元素,每个dependency可以包含的元素有:

  • 【必须】groupId、artifactId和version:依赖的基本坐标;
  • 【可选】type: 依赖的类型,对应于项目坐标定义的packaging。其默认值为jar;
  • 【可选】scope:依赖的范围;
  • 【可选】optional:标记依赖是否可选;
  • 【可选】exclusions:用来排除传递性依赖;

2. 依赖范围

classpath 的作用是让JVM知道要运行的代码去哪儿找,一个程序的代码包含两部分:自己写的那部分 和 引入的依赖。Maven就是专门用来管理依赖的,以下的classpath针对依赖而言.

  • Maven在编译项目主代码的时候使用一套classpath,第三方的类库(如spring-core)以依赖的方式被引入到classpath中;
  • Maven在编译和执行测试代码的时候会使用另外一套classpath,第三方类库如JUnit也是以依赖的方式被引入classpath,但不同的是JUnit的依赖范围是test;
  • Maven在实际运行Maven项目的时候又会使用一套classpath。如spring-core需要在此classpath中,但JUnit不需要在此classpath中。

Maven的依赖范围就是用来控制第三方类库依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:

  • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的第三方类库,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行时都需要使用该依赖。
  • test:测试依赖范围。使用此依赖范围的第三方类库,只对于测试classpath有效。在编译项目主代码或运行项目的时候无法使用此依赖。典型的例子是JUnit,它只有在编译测试代码和运行测试的时候才需要。
  • provided:  “已提供”依赖范围。对于编译和测试classpath有效,但在运行时无效。典型的例子是servelet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候由于容器已经提供,就不需要Maven重复引入一遍。
  • runtime:运行时依赖范围。对于测试和运行classpath有效,但在编译注代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口实现。只有在执行测试或运行项目时才需要实现上述接口的具体JDBC驱动。
  • system:系统依赖范围。和provided依赖范围一致,但是使用system范围的依赖时必须通过<systemPath>元素显式指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的(本地的,Maven仓库之外的类库文件),而且往往都是和本机系统绑定,可能造成构建的不可移植。因此应谨慎使用。systemPath 元素可以引用环境变量,如:
            <dependency>
                <groupId>javax.sql</groupId>
                <artifactId>jdbc-stdext</artifactId>
                <version>2.0</version>
                <scope>system</scope>
                <systemPath>${java.home}/lib/rt.jar</systemPath>
            </dependency>
  • import(Maven2.0.9及以上):导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。

依赖范围与classpath的关系表格:

依赖范围(scope) 对于编译classpath有效 对于测试classpath有效 对于运行classpath有效 例子
compile Y Y Y spring-core
test / Y / JUnit
provided Y Y / servelet-api
runtime / Y Y JDBC驱动实现
system Y Y / 本地的,Maven仓库之外的类库

3,传递性依赖

假设A依赖B,B依赖C,则称A第一直接依赖B,B第二直接依赖C,A传递依赖C。第一直接依赖的范围和第二直接依赖的范围决定了传递依赖的范围,如下表所示:

第一列是第一直接依赖

第一行是第二直接依赖

compile test provided runtime
compile compile ---- ---- runtime
test test ---- ---- test
provided provided ---- provided provided
runtime runtime ---- ---- runtime
  • 当第二直接依赖范围是compile时,传递依赖范围和第一直接依赖范围一致;
  • 当第二直接依赖范围是test时,依赖不会得以传递;
  • 当第二直接依赖范围是provided时,只传递第一直接依赖也是provided的依赖,且传递依赖的范围也是provided;
  • 当第二直接依赖范围是runtime时,传递依赖范围和第一直接依赖范围一致,但compile例外,此时传递性依赖的范围是runtime。

4. 依赖调节(Dependency Mediation)

Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。

  • 依赖调节第一原则:路径最近者优先。如:A > B > C > X(1.0)  和 A > D > X(2.0),两个版本的X最终取的是X(2.0),前者路径长度为3,后者路径长度为2,取后者。
  • 依赖调节第二原则(Maven 2.0.9及以上):第一声明者优先。A > Y(1.0), B > Y(2.0) ,两个版本的Y,哪个在POM文件中先声明就取哪个。

5. 可选依赖

<dependency>元素中的<optional>元素表明这个依赖项是可选依赖,可选依赖项是不会传递的。如 A依赖B, B依赖X(X是可选的), B依赖Y(Y是可选的); 那么X和Y不会被传递给A。A如要依赖X或Y需要显式的再次在A的POM文件中声明。

为什么要使用可选依赖这一特性呢?可能项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。

<project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.juvenxu.mvnbook</groupId>
        <artifactId>project-b</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.10</version>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <version>8.4-701.jdbc3</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </project>

上述XML代码片段中,使用<optional>元素表示mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于MySQL数据库,那么在项目A中就需要显式地声明mysql-connector-java这一依赖

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.juvenxu.mvnbook</groupId>
        <artifactId>project-a</artifactId>
        <version>1.0.0</version>
        <dependencies>
            <dependency>
                <groupId>com.juvenxu.mvnbook</groupId>
                <artifactId>project-b</artifactId>
                <version>1.0.0</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.10</version>
            </dependency>
        </dependencies>
    </project>

最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的。前面我们可以看到,使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划Maven项目的时候也同样适用。在上面的例子中,更好的做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如com.juvenxu.mvnbook:project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql,在各自的POM中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用project-b-mysql或者project-b-postgresql。由于传递性依赖的作用,就不用再声明JDBC驱动依赖。

6.最佳实践

  • 排除依赖项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显式地声明对于项目C 1.1.0版本的依赖。代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或者多个exclusion子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。换句话说,Maven解析后的依赖中,不可能出现groupId和artifactId相同,但是version不同的两个依赖
    <project>
            <modelVersion>4.0.0</modelVersion>
            <groupId>com.juvenxu.mvnbook</groupId>
            <artifactId>project-a</artifactId>
            <version>1.0.0</version>
            <dependencies>
                <dependency>
                    <groupId>com.juvenxu.mvnbook</groupId>
                    <artifactId>project-b</artifactId>
                    <version>1.0.0</version>
                    <exclusions>
                        <exclusion>
                            <groupId>com.juvenxu.mvnbook</groupId>
                            <artifactId>project-c</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
                <dependency>
                    <groupId>com.juvenxu.mvnbook</groupId>
                    <artifactId>project-c</artifactId>
                    <version>1.1.0</version>
                </dependency>
            </dependencies>
        </project>
  • 归类依赖。项目中经常有很多关于Spring Framework的依赖,它们分别是org.springframework:spring-core:2.5.6、org.springframework:spring-beans:2.5.6、org.springframework:spring-context:2.5.6和org.springframework:spring-context-support:2.5.6,它们是来自同一项目的不同模块。因此,所有这些依赖的版本都是相同的,而且可以预见,如果将来需要升级Spring Frame-work,这些依赖的版本会一起升级。此时应该在POM中一个唯一的地方定义Spring Framework的版本。在升级Spring Framework的时候只需要修改一处。代码如下
        <properties>
            <springframework.version>2.5.6</springframework.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${springframework.version}</version>
            </dependency>
        </dependencies>
  • 优化依赖。Maven经过一系列工作之后最终得到的所有依赖叫做 已解析依赖(Resolved Dependency),
    • 运行·mvn dependency:list`命令查看当前项目的已解析依赖(同时还包含每个依赖的范围)。
    • 运行·mvn dependency:tree·命令查看当前项目的依赖树。过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。
    • 运行·mvn dependency:analyze· 命令可以分析当前项目的依赖情况,输出示例如下。
      [WARNING] Used undeclared dependencies found:
      [WARNING]    org.springframework:spring-context:jar:5.0.7.RELEASE:compile
      [WARNING] Unused declared dependencies found:
      [WARNING]    org.springframework:spring-core:jar:5.0.7.RELEASE:compile
      [WARNING]    org.springframework:spring-beans:jar:5.0.7.RELEASE:compile
      • 使用了但未显式声明的依赖:意指项目中使用到的,但是没有显式声明的依赖,这里是spring-context。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多相关的Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,显式声明任何项目中直接用到的依赖。
      • 显式声明了但未使用的依赖:意指项目中未使用的,但显式声明的依赖,这里有spring-core和spring-beans。需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。很显然,该例中的spring-core和spring-beans是运行Spring Framework项目必要的类库,因此不应该删除依赖声明。当然,有时候确实能通过该信息找到一些没用的依赖,但一定要小心测试。

卍,仓库

※,在Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。在Maven出现之前,每个项目中都有一个命名为lib/的文件夹,里面存储了项目所有的依赖构件,各个项目lib/目录下的内容存在大量的重复(和现在angular的node_modules一样的问题)。有了Maven仓库,实际的Maven项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(比如,编译项目的时候需要将依赖加入到classpath中),Maven会自动根据坐标找到仓库中的构件,并使用它们。

※,仓库的分类

 Maven仓库分为两类:本地仓库 和 远程仓库。

本地仓库:

  • 安装好Maven后,如果不执行任何Maven命令,本地仓库目录是不存在,当用户输入第一条Maven命令之后,Maven才会创建本地仓库。默认本地仓库地址是:·${user.home}/.m2/repository·
  • settings.xml中可以修改本地仓库位置:·<localRepository>path omy epository</localRepository>·
  • 对于Maven来讲,每个用户只有一个本地仓库,但可以配置访问很多远程仓库。

远程仓库有很多,一些特殊的介绍如下:

  • 中央仓库:中央仓库是Maven核心自带的远程仓库。在默认配置下,如果本地仓库没有Maven需要的构件时,它就会尝试从中央仓库下载。
    •  https://repo.maven.apache.org/maven2       或者       https://repo1.maven.org/maven2/
    • Maven安装文件自带了中央仓库的配置。打开 ·$M2_HOME/lib/maven-model-builder-3.6.3.jar·,然后可以找到·orgapachemavenmodelpom-4.0.0.xml`。这个POM是所有Maven项目都会集成的超级POM
        <repositories>
          <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
           <!-- Maven2和Maven3布局皆是default,即基于groupId、artifact、version等构建目录存储文件,Maven1的布局方式为legacy-->
            <layout>default</layout>
            <snapshots>
              <enabled>false</enabled>
            </snapshots>
          </repository>
        </repositories>
  • 私服:为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用。Maven私服软件——Nexus 下文介绍。
  • 其他远程仓库:除了中央仓库和私服,还有很多其他公开的远程仓库,如aspose自己的仓库:https://repository.aspose.com/repo/ 等等。

※,远程仓库的配置

1. 配置除中央仓库之外的其他远程仓库:很多情况下,默认的中央仓库无法满足项目需求,可能需要的构件存在于另一个远程仓库中。这时可以在POM中配置该仓库,如配置POM使用JBoss Maven仓库:

<project>
        ……
        <repositories>
            <repository>
                <id>jboss</id>
                <name>JBoss Repository</name>
                <url>http://repository.jboss.com/maven2/</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <layout>default</layout>
            </repository>
        </repositories>
        ……
    </project>
  • 在repositories 元素下可以使用repository元素声明一个或多个远程仓库。Maven会按照定义的仓库顺序尝试下载构件(如果在settings.xml中配置了某个仓库的镜像,那么就会使用镜像地址而不会使用原始仓库地址。Maven在执行命令之前就会计算好远程仓库的下载顺序),都找不到时就会使用 超级POM里配置的默认中央仓库地址下载
  • 任何一个仓库声明的id必须是唯一的。Maven自带的中央仓库使用的id是 central, 如果其他的仓库也使用了该id,就会覆盖中央仓库的配置。
  • 该例配置中的releases和snapshots元素比较重要,它们用来控制Maven对于发布版构件和快照版构件的下载。根据该配置,Maven只会从JBoss仓库下载发布版的构件,而不会下载快照版的构件。

  • 对于releases和snapshots 元素来说,除了enabled,它们还包含另外两个子元素 updatePolicy 和 checksumPolicy:

        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>daily</updatePolicy>
            <checksumPolicy>ignore</checksumPolicy>
        </snapshots>
    • 元素updatePolicy用来配置Maven从远程仓库检查更新的频率,默认的值是daily,表示Maven每天检查一次。其他可用的值包括:never—从不检查更新;always—每次构建都检查更新;interval:X—每隔X分钟检查一次更新(X为任意整数)

    • 元素checksumPolicy用来配置Maven检查检验和文件的策略。当构件被部署到Maven仓库中时,会同时部署对应的校验和文件。在下载构件的时候,Maven会验证校验和文件,如果校验和验证失败,怎么办?当checksumPolicy的值为默认的warn时,Maven会在执行构建时输出警告信息,其他可用的值包括:fail—Maven遇到校验和错误就让构建失败;ignore—使Maven完全忽略校验和错误

2. 镜像:可以配置一个或多个远程仓库的镜像。在settings.xml文件中配置如下代码:

<settings>
……
    <mirrors>
        <mirror>
            <id>maven.net.cn</id>
            <name>one of the central mirrors in China</name>
            <url>http://maven.net.cn/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
……
</settings>

该例配置了中央仓库的一个中国区镜像,其中的<mirrorOf>元素 的值 必须和被镜像仓库的id 完全一致,正是这个id将 镜像仓库和被镜像仓库联系在了一起配置了 central 仓库的镜像之后,所有对central 仓库的请求都会被转至该镜像,即使镜像中没有找到某个构件,Maven也不会再去原始的central仓库尝试下载了。也就是说,镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件

  • mirrorOf 元素的值将镜像仓库和原始仓库联系起来
  • <mirrorOf>*</mirrorOf>:值为*, 匹配所有远程仓库;
  • <mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库;
  • <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库repo1和repo2,使用逗号分隔多个远程仓库;
  • <mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。

3. 远程仓库的认证:大部分远程仓库无须认证就可以访问,但有时候出于安全方面的考虑,我们需要提供认证信息才能访问一些远程仓库。

配置认证信息和配置仓库信息不同,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在settings.xml文件中。这是因为POM往往是被提交到代码仓库中供所有成员访问的,而settings.xml一般只放在本机。因此,在settings.xml中配置认证信息更为安全。

假设需要为一个id为my-proj的仓库配置认证信息,编辑settings.xml文件,代码如下:

<settings>
……
    <servers>
        <server>
            <id>my-proj</id>
            <username>repo-user</username>
            <password>repo-pwd</password>
        </server>
    </servers>
……
</settings>

关键点是id元素,id元素的值必须和POM中需要认证的仓库的id完全一致,正是这个id将认证信息与仓库配置联系在了一起

4. 部署至远程仓库:Maven除了能对项目进行编译、测试、打包、安装之外,还能将项目生成的构件部署到仓库中。部署前需要先配置settings.xml文件,添加如下代码:

    <distributionManagement>
        <repository>
            <id>proj-release</id>
            <name>Project release Repository</name>
            <url>http://mvn.mizxx.com/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>proj-snapshots</id>
            <name>Project snapshots Repository</name>
            <url>http://mvn.mizxx.com/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

往远程仓库部署构件的时候往往需要认证,配置认证的方式上文已讲,简而言之,就是需要在settings.xml中创建一个server元素,其id与仓库的id匹配,并配置正确的认证信息。不论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证的时候,配置的方式是一样的 。

配置正确后,在命令行运行·mvn clean deploy·,Maven就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。

※,快照版本:Maven为什么要区分发布版和快照版呢?(原书6.5节详解)

快照版的使用场景一般是:多个模块同时开发,A同学开发模块X, B同学开发模块Y,其中Y依赖于X的版本,使用快照版可以尽可能的让A和B独立开发而不需要太多的手工操作。B同学的Y模块的POM文件就一直使用1.0-SNAPSHOT版本, A同学的模块X的POM中也使用1.0-SNAPSHOT,这样A同学可以随意构建1.0-SNAPSHOT的版本,B同学根据其仓库的配置(即<updatePolicy>元素的值)来拉取1.0-SNAPSHOT的最新版本。这样A和B都不用太关心POM的版本问题。

A同学只需要将模块A的版本设定为1.0-SNAPSHOT,然后发布到私服中,在发布的过程中,Maven会自动为构件打上时间戳。比如1.0-20091214.221414-13就表示2009年12月14日22点14分14秒的第13次快照。有了该时间戳,Maven就能随时找到仓库中该构件1.0-SNAPSHOT版本最新的文件

B同学除了根据其仓库配置(<updatePolicy>)获取最新快照版,还可以在使用任何 Maven命令时加上-U参数强制让Maven检查更新!如 ·mvn clean compile -U·

※,仓库搜索服务

这些仓库搜索服务都代理了主流的Maven公共仓库(如 central、JBoss、Jave.net等)。

※,

卍,生命周期和插件

※,除了坐标、依赖以及仓库之外,Maven另外两个核心概念是生命周期和插件。Maven的声明周期是抽象的,其实际行为都由插件来完成,如package阶段的任务可能会由maven-jar-plugin完成。生命周期和插件两者协同工作,密不可分。

※,什么叫生命周期

在Maven出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理、编译、测试及部署。虽然大家都在不停地做构建工作,但公司和公司间、项目和项目间,往往使用不同的方式做类似的工作。有的项目以手工的方式在执行编译测试,有的项目写了自动化脚本执行编译测试。可以想象的是,虽然各种手工方式十分类似,但不可能完全一样;同样地,对于自动化脚本,大家也是各写各的,能满足自身需求即可,换个项目就需要重头再来。

Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。

Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译源代码)都交由插件来完成。每个构建步骤都可以绑定一个或者多个插件行为,而且Maven为大多数构建步骤编写并绑定了默认插件。例如,针对编译的插件有maven-compiler-plugin,针对测试的插件有maven-surefire-plugin等。当用户有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。

Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外,该机制还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来自定义构建行为。

※,生命周期详解

1. 三套生命周期

Maven的生命周期并不是一个整体,而是拥有三套相互独立的生命周期:clean、default、site。每个生命周期包含一些阶段(phase),阶段是有顺序的,并且后面的阶段依赖于前面的阶段。生命周期间是独立的,意味用户可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响。

三套生命周期及其阶段如下:

clean生命周期的目的是清理项目,它包含三个阶段:
1)pre-clean执行一些清理前需要完成的工作。
2)clean清理上一次构建生成的文件。<===绑定插件:目标===> maven-clean-plugin:clean
3)post-clean执行一些清理后需要完成的工作。

default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分【官方文档点此】,包含的阶段按顺序如下:

validate
initialize
generate-sources
process-sources 
generate-resources
process-resources <===绑定插件:目标===> maven-resources-plugin:resources。处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
compile <===绑定插件:目标===> maven-compiler-plugin:compile。编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
process-classes
generate-test-sources
process-test-sources 
generate-test-resources
process-test-resources <===绑定插件:目标===> maven-resources-plugin:testResources。处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
test-compile <===绑定插件:目标===> maven-compiler-plugin:testCompile。编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
process-test-classes
test <===绑定插件:目标===> maven-surefire-plugin:test。使用单元测试框架运行测试,测试代码不会被打包或部署。
prepare-package
package <===绑定插件:目标===> maven-jar-plugin:jar。接受编译好的代码,打包成可发布的格式,如JAR。
pre-integration-test
integration-test
post-integration-test
verify
install <===绑定插件:目标===> maven-install-plugin:install。将包安装到Maven本地仓库,供本地其他Maven项目使用。
deploy <===绑定插件:目标===> maven-deploy-plugin:deploy。将最终的包复制到远程仓库,供其他开发人员和Maven项目使用

site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包含如下阶段:
pre-site 执行一些在生成项目站点之前需要完成的工作。
site 生成项目站点文档。<===绑定插件:目标===> maven-site-plugin:site
post-site 执行一些在生成项目站点之后需要完成的工作。
site-deploy 将生成的项目站点发布到服务器上。<===绑定插件:目标===> maven-site-plugin:depoy

2. 通过生命周期的阶段调用Maven命令:注意各个生命周期间是相互独立的,而一个生命周期的阶段是有前后依赖关系的。举例如下:

  • 首先 mvn 后面可以加上任意多的以空格分割的阶段,Maven会按顺序执行,如: `mvn pre-clean post-clean compile clean`语法上也是可以的,最后什么都没得到...-_-||。甚至还可以将生命周期阶段和插件命令混用: ·mvn clean compiler:compile· 注意:生命周期阶段调用是有前后依赖关系的,而插件命令调用之上执行某个功能动作。
  • ·mvn clean· 调用clean生命周期的clean阶段,实际执行的阶段为:clean周期的pre-clean 和 clean阶段。
  • ·mvn compile· 调用default生命周期的compile阶段,实际执行的阶段有:从default生命周期的 validate 阶段 到 compile 阶段。
  • ·mvn clean compile· 调用clean生命周期的clean阶段 和 default生命周期的compile阶段,实际执行的阶段为两者按顺序加和。
  • ·mvn clean deploy site-deploy· 调用clean生命周期的clean阶段、default生命周期的deploy阶段和site生命周期的site-deploy阶段,实际执行阶段为三者按顺序加和。
  •  

※,插件 与 插件目标

 Maven核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的。每个插件有很多功能,每个功能称为插件的一个目标(goal)。比如 maven-dependency-plugin插件有十多个目标,每个目标对应了一个功能,如`mvn dependency:tree` (命令的全格式为:·mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:tree·),`mvn dependency:list`,`mvn dependency:analyze`等等。

上文同时也说明了Maven可以通过插件运行命令:基本格式为·mvn <groupId>:<artifactId>:<version>:<goal>·,也可用每个插件的简略形式。

※,生命周期 和 插件绑定关系:

Maven命令的运行有两种方式,通过生命周期的阶段调用 和 通过插件的目标调用。其实两者是有绑定关系的,调用生命周期阶段的Maven命令实际上运行的也是插件命令。一些生命周期的阶段默认绑定了相应插件的某个目标,于是调用生命周期的阶段命令时就会调用这个插件的这个目标。另一些生命周期的阶段默认没有绑定任何插件,于是调用这些生命周期阶段也就不会有任何实质性的动作(不考虑生命周期阶段的前后依赖关系)。

除了默认的打包类型jar之外,常见的打包类型还有war、pom、maven-plugin、ear等。它们的default生命周期与插件目标的绑定关系可参阅Maven官方文档

※,自定义绑定

除了内置绑定外,用户还能够自己选择将某个插件的某个目标绑定到生命周期的某个阶段上。这种自定义绑定方式能让Maven项目在构建过程中执行更多富有特色的任务。

例子:将插件 maven-source-plugin的jar-no-fork目标( 此目标用于生成 Maven项目主代码的jar包 )绑定到生命周期的verify阶段,在POM文件中配置如下代码

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1.2</version>
            <executions>
                <execution>
                    <id>gen-source-jar</id>
                    <phase>verify</phase> <!--可以不写此元素,maven-source-plugin:jar-no-fork目标默认绑定到此阶段-->
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

配置完成后,运行 mvn verify 就会在/target 目录生成主代码的jar包。(PS:单纯的项生成主代码的jar包,可以不用在POM中配置绑定,直接运行插件命令即可:·mvn source:jar-no-fork·)。

很多插件的目标在编写时已经定义了默认绑定阶段,此时也可以不写<phase>元素。如maven-soruce-plugin:jar-no-fork 目标默认绑定了verify 阶段,所以POM中可以不写<phase>元素。可以使用mvn help命令查询某个插件的详细信息(有哪些目标,每个目标的默认绑定的生命周期阶段等等):

·mvn help:describe -D plugin=org.apache.maven.plugins:maven-source-plugin:3.2.1·   或者写为:

·mvn help:describe -Dplugin="org.apache.maven.plugins:maven-source-plugin:3.2.1"·

`mvn help:describe -D plugin=org.apache.maven.plugins:maven-source-plugin:3.2.1 -Ddetail` // -Ddetail 或 -D detail都行。

如果多个目标绑定到同一个生命周期的阶段上,那么这些插件声明的先后顺序决定了目标的执行顺序。

※,插件配置

用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,以满足项目的需求。几乎所有Maven插件的目标都有一些可配置的参数,用户可以通过命令行和POM配置等方式来配置这些参数

1. 从命令行配置插件

使用Maven命令的时候可以使用-D参数,并伴随一个参数键=参数值的形式来配置插件目标的参数。-D参数是Java自带的,其功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性,便实现了插件参数的配置。

  • ·mvn install -D maven.test.skip=true· // maven-surefire-plugin插件提供了一个maven.test.skip参数,设置为true就会跳过执行测试!

2. POM中配置插件

2.1, 插件全局配置:声明插件时可以进行一个全局的配置,这样该插件的所有目标 任务都会使用这个配置。

示例1:配置maven-compiler-plulgin,告诉它编译时的源文件 的Java版本以及生成与JVM指定版本兼容的字节码文件,代码如下

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
    </plugins>
</build>

示例2:配置maven-surefire-plugin,跳过代码测试阶段:

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

跳过测试也可以通过POM中的<properties>属性实现,代码如下:

    <properties>
        <maven.test.skip>true</maven.test.skip>
    </properties>

2.2, 插件任务(目标)配置(即配置插件的某个目标的参数):

一个<execution>元素代表一个插件的目标配置,示例代码如下

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.3</version>
            <executions>
                <execution>
                    <id>ant-validate</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <tasks>
                            <echo>I'm bound to validate phase.</echo>
                        </tasks>
                    </configuration>
                </execution>
                <execution>
                    <id>ant-verify</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <tasks>
                            <echo>I'm bound to verify phase.</echo>
                        </tasks>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

※,获取插件信息

仅仅理解如何配置使用插件是不够的。当遇到一个构建任务的时候,用户还需要知道去哪里寻找合适的插件,以帮助完成任务。找到正确的插件之后,还要详细了解该插件的配置点。

1. 在线插件信息

2. 使用 maven-help-plugin 插件 查看插件详细信息

  • ·mvn help:describe -Dplugin="org.apache.maven.plugins:maven-compiler-plugin:3.8.1"·  //注意引号,可省略版本信息,若省略则默认解析最新版。
  • ·mvn help:describe -D plugin=org.apache.maven.plugins:maven-compiler-plugin:3.8.1· // -D plugin;空格
  • ·mvn help:describe -Dplugin="org.apache.maven.plugins:maven-compiler-plugin:3.8.1" -Dgoal=compile· //只查看某个 goal 的信息
  • ·mvn help:describe -D plugin=org.apache.maven.plugins:maven-compiler-plugin:3.8.1 -Ddetail· // -Ddetail 可以查看更加详细的信息。
  • ·mvn help:describe -D plugin=compiler` //使用省略的插件名称。此时无法指定版本,只能查看最新版本的信息。

可以使用  mvn help:describe 命令查看其本身的使用方法:·mvn help:describe -D plugin=help -D goal=describe -D detail·,输出如下:

[INFO] Mojo: 'help:describe'
help:describe
  Description: Displays a list of the attributes for a Maven Plugin and/or
    goals (aka Mojo - Maven plain Old Java Object).
  Implementation: org.apache.maven.plugins.help.DescribeMojo
  Language: java

  Available parameters:

    artifactId
      User property: artifactId
      The Maven Plugin artifactId to describe.
      Note: Should be used with groupId parameter.

    cmd
      User property: cmd
      A Maven command like a single goal or a single phase following the Maven
      command line:
      mvn [options] [<goal(s)>] [<phase(s)>]

    detail (Default: false)
      User property: detail
      This flag specifies that a detailed (verbose) list of goal (Mojo)
      information should be given.

    goal
      User property: goal
      The goal name of a Mojo to describe within the specified Maven Plugin. If
      this parameter is specified, only the corresponding goal (Mojo) will be
      described, rather than the whole Plugin.

    groupId
      User property: groupId
      The Maven Plugin groupId to describe.
      Note: Should be used with artifactId parameter.

    minimal (Default: false)
      User property: minimal
      This flag specifies that a minimal list of goal (Mojo) information should
      be given.

    output
      User property: output
      Optional parameter to write the output of this help in a given file,
      instead of writing to the console.
      Note: Could be a relative path.

    plugin
      Alias: prefix
      User property: plugin
      The Maven Plugin to describe. This must be specified in one of three
      ways:

      1.  plugin-prefix, i.e. 'help'
      2.  groupId:artifactId, i.e. 'org.apache.maven.plugins:maven-help-plugin'
      3.  groupId:artifactId:version, i.e.
        'org.apache.maven.plugins:maven-help-plugin:2.0'

    version
      User property: version
      The Maven Plugin version to describe.
      Note: Should be used with groupId/artifactId parameters.

※,插件解析机制

1. 插件仓库和依赖仓库是分开配置的。超级POM中内置了两种仓库。可以在POM中配置自己的 插件仓库(同时依然可以在settings.xml中配置这些仓库的镜像),代码如下:

    <!-- maven plugin repository setting -->
    <pluginRepositories>
        <pluginRepository>
            <id>my plugin repo</id>
            <name>我的插件远程仓库</name>
            <url>https://www.baidu.com</url>
        </pluginRepository>
    </pluginRepositories>

2. 插件解析机制

  • 在POM中<build><plugins><plugin>元素中配置插件的时候,如果该插件是Maven的官方插件(即如果其groupId为org.apache.maven.plugins),就可以省略groupId配置。
  • 配置插件时省略版本(对于依赖,省略版本也一样的原理),则Maven会根据本地仓库和所有远程仓库的元数据计算出 latest和release的具体版本值。latest表示所有仓库中该构件的最新版本,而release表示最新的非快照版本。在Maven 2中,插件的版本会被解析至latest。Maven 3调整了解析机制,当插件没有声明版本的时候,不再解析至latest,而是使用release。这样就可以避免由于快照频繁更新而导致的插件行为不稳定。仓库元数据位于 groupId/artifactId/maven-metadata.xml中。
  • 解析插件简称(又称插件前缀):
    • 插件前缀与 groupId:artifactId是一一对应的,这种匹配关系存储在仓库元数据中。这里的元数据位置为: groupId/maven-metadata.xml,org.apache.maven.plugins下的maven-metadata.xml中的代码截取如下:
      <plugins>
          ......
          <plugin>
              <name>Apache Maven Clean Plugin</name>
              <prefix>clean</prefix>
              <artifactId>maven-clean-plugin</artifactId>
          </plugin>
          ......
      </plugins>
    • Maven默认使用两个 groupId: org.apache.maven.plugins  和 org.codehaus.mojo;用户可以在settings.xml中配置自己的groupId,配置后Maven同时也会检查用户配置的groupId下的元数据,代码如下
      <settings>
          <pluginGroups>
              <pluginGroup>com.your.plugins</pluginGroup>
          </pluginGroups>
      </settings>
    • 当Maven解析到 mvn dependency:tree 这样的命令之后,
      • Maven首先会查找groupId为 org.apache.maven.plugins 的仓库元数据,定位到prefix为dependency的项,于是就确定了插件的artifactId,最后再根据 上文获取version的方法得到version值,就可以确定插件的完整坐标:groupId:artifactId:version。
      • 如果在groupId为  org.apache.maven.plugins 的仓库元数据下没有找到 prefix为 dependency的项,则继续在 groupId为 org.codehaus.mojo 下找,如果还找不到,就去用户自定义的 groupId下找,所有元数据中都找不到的话,Maven就会报错。
  •  

※,

卍,聚合和继承

Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。

※,

※,

※,

※,

※,

※,

卍,使用Nexus搭建Maven私服

原文地址:https://www.cnblogs.com/everest33Tong/p/14194990.html