标签列表

everest33

自制力

导航

maven学习笔记

平时学习记录

※,Maven的坐标和Maven项目中的包名之间没有任何关系!但是实际应用中当然是保持一致比较好!

※,自己写的不同模块放在一个工程中,使用聚合Maven管理,此时可以不依赖本地Maven仓库就可以编译成功。Maven会根据parent找到所有的(自己写的)模块。需要注意的需要正确填写parent的relativePath路径,否则Maven会找不到相关的模块的pom 文件。relativePath不写时默认是上一级路径(即`../pom.xml`)。如果不是这个路径需要显式的填写<relativePath>xxxx</relativePath>。 当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到 或者 找到的POM文件和声明的父POM坐标对不上,Maven会再从本地仓库找。

※,maven compile报如下错:

原因: -source 8 中不支持 '<>' 具有匿名的内部类
    (请使用 -source 9 或更高版本以启用 '<>' 具有匿名的内部类)

原因是maven的settings.xml文件中配置的profile是jdk1.8而不是jdk11.修改为JDK11即可

	<profile>  
     <id>jdk-11</id>  
     <activation>
         <activeByDefault>true</activeByDefault>  
         <jdk>11</jdk>  
     </activation>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <maven.compiler.source>11</maven.compiler.source>  
         <maven.compiler.target>11</maven.compiler.target>   
     </properties>   
	</profile>

※,报如下错误时:expected START_TAG or END_TAG not TEXT (position: TEXT seen ...</mirror>\n\t\n\t<mirror>\n\t\ua0\ua0\ua0\ua0<i... @180:8),是因为maven的setting.xml文件中有空格,删除空格(可用制表符)保存下即可。

※,mvn打成jar包时,一个java类至少要存在于某个package内(即:package.Class中的package至少要有一层),否则别的工程引入此jar包后无法引入这个java类。

※,IDEA中某个模块依赖了某jar包(xxJar-1.0.0-SNAPSHOT),idea工具查找此jar包的顺序依次是:1. 查找同个工程下的其他模块是否有xxJar-1.0.0-SNAPSHOT,如果有则用此代码(此时本地仓库不需要有此 xxJar-1.0.0-SNAPSHOT jar包);2,没有则继续查找本地仓库是否有xxJar-1.0.0-SNAPSHOT,如果有则使用此jar包;3. 没有则继续查找setting.xml中配置的第一个服务器(第三方maven服务器或maven私服),如果有则下载到本地仓库并使用此下载的jar包;4,没有则继续用setting.xml中配置的第二个服务器依次类推,5,都没有则去中央仓库查找此jar包,有则下载到私服并下载到本地并使用本地的xxJar-1.0.0-SNAPSHOT  jar包。

※,依赖本地jar包:

★,Maven方式:


   com.seewo.resource
   seewo-resource-data-open-sdk
   1.0-SNAPSHOT
   system
   C:\mySoftware\apache-maven-3.6.3\repository\com\seewo\seewo-resource-data-open-sdk-1.0-SNAPSHOT.jar

这种方式通过maven package竟然没有把这个jar包打进去!!可以通过IDEA方式web项目方法添加这个jar包。另外也可以把这个jar包传到Maven仓库中,可以通过mvn deploy传(研究下一系列参数),也可以通过Maven仓库的管理界面(nexus)传到仓库中去。

★,IDEA方式,经试验结果如下:注意maven clean后可能会清除这个jar包配置....

  • service项目:在project structure ----Modules ----- Dependencies中添加jar包即可。
  • web项目:上述位置添加jar包还是报类找不到的错。需要在这个位置添加jar包:project structure --- Artifacts --- 对应的war包----WEB-INF/lib下添加这个jar包。这样打包的时候添加的这个本地jar包也会被打进war包里。

※,IDEA复制项目导致一系列命名问题:参考博客

★,.iml 文件:

  • idea 对module 配置信息之意, infomation of module。每个模块都有一个iml文件。IDEA中的.iml文件是项目标识文件,缺少了这个文件,IDEA就无法识别项目。
  • iml文件缺失:参考博客
    • 因为iml文件的信息实际是通过Maven的相关信息生成的,所以一般重新运行maven reimport就能重新自动生成,不行就参考博客里的方法
  • 模块名称带有方括号 或者 Java文件夹带有方括号,方括号里带有老模块的名称问题,参考上面的博客


教程:Maven实战

卍,生命周期 与 插件目标

卍,仓库搜索服务

卍,超级POM内容:对于Maven 3,超级POM在文件$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下。对于Maven 2,超级POM在文件$MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/project/pom-4.0.0.xml目录下。这里的x.x.x表示Maven的具体版本。

  • 超级POM定义了仓库及插件仓库,两者的地址都为中央仓库并且都关闭了SNAPSHOT的支持。这也就解释了为什么Maven默认就可以按需要从中央仓库下载构件。
<?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约定优于配置(Convention Over Configuration):这是Maven最核心的设计理念之一。Maven提出这一概念为项目提供合理的默认行为,无需不必要的配置,但是也是有代价的,那就是遵循Maven的约定,Maven会假设用户的项目是这样的:

  • 源码目录为src/main/java/
  • 资源文件目录为src/main/resources/
  • 编译输出目录为target/classes/
  • 测试目录为src/test/java/
  • 测试资源目录为src/test/resources/
  • 测试代码编译输出目录为target/test-classes/
  • 打包方式为jar
  • 包输出目录为target/

如果没有约定,10个项目可能使用10种不同的项目目录结构,这意味着交流学习成本的增加。如果不想遵守约定,Maven也允许通过配置覆盖约定,比如自定义源码目录,代码如下

<build>
    <!-- 源码目录就成了src/java而不是默认的src/main/java -->
    <sourceDirectory>src/java</sourceDirectory>
</build>
  • 默认的源码目录(src/main/java)是在超级POM中被定义的。超级POM的配置会被所有Maven项目继承,这些超级pom中的配置也就成为了Maven所提倡的约定

卍 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:\software\apache-maven-3.6.3;

3. 配置环境变量M2_HOME,值为D:\software\apache-maven-3.6.3; Path环境变量添加%M2_HOME%\bin;

  • 注:在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运行时需要的全部类库。

※,settings.xml和pom.xml的作用

  从settings.xml的文件名就可以看出,它是用来设置maven参数的配置文件。并且,settings.xml是maven的全局配置文件。而pom.xml文件是所在项目的局部配置。Settings.xml中包含类似本地仓储位置、修改远程仓储服务器、认证信息等配置。pom.xml包含项目所需的依赖关系及部分配置信息,如果同时在settings.xml中和pom.xml中都存在的配置项,则以pom.xml为准。具体配置优先级从高到低:pom.xml > user settings > global settings

※,·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配置文件settings.xml详解:将配置文件放在固定特定的目录下Maven即可读取这些配置,无需其他额外的配置。

settings.xml 是 maven 的配置文件,用户配置文件存放于 ${user.home}/.m2/ 目录下,系统全局配置文件放置于 ${maven.home}/conf/ 目录下,pom.xml 是 maven 的项目的配置文件。

配置文件的优先级从高到低为:pom.xml、用户配置 settings.xml、全局系统 settings.xml。如果这些文件同时存在,在应用配置时,会合并它们的内容,如果有重复的配置,优先级高的配置会覆盖优先级低的。

settings.xml配置文件详解:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <!-- 构建系统本地仓库的路径。其默认值:~/.m2/repository -->
    <!-- <localRepository>${user.home}/.m2/repository</localRepository> -->

    <!-- 是否需要和用户交互以获得输入。默认为 true -->
    <!-- <interactiveMode>true</interactiveMode> -->

    <!-- 是否需要使用 ~/.m2/plugin-registry.xml 文件来管理插件版本。默认为 false -->
    <!-- <usePluginRegistry>false</usePluginRegistry> -->

    <!-- 是否需要在离线模式下运行,默认为 false。当由于网络设置原因或者安全因素,构建服务器不能连接远程仓库的时候,该配置就十分有用 -->
    <!-- <offline>false</offline> -->

    <!-- 当插件的 groupId 没有显式提供时,供搜寻插件 groupId 的列表。使用某个插件,如果没有指定 groupId 的时候,maven 就会使用该列表。
        默认情况下该列表包含了 org.apache.maven.plugins 和 org.codehaus.mojo -->
    <!-- <pluginGroups> -->
        <!-- plugin 的 groupId -->
        <!-- <pluginGroup>org.codehaus.mojo</pluginGroup> -->
    <!-- </pluginGroups> -->

    <!-- 配置服务端的一些设置。如安全证书之类的信息应该配置在 settings.xml 文件中,避免配置在 pom.xml 中 -->
    <!-- <servers> -->
        <!-- <server> -->
            <!-- 这是 server 的 id(注意不是用户登陆的 id),该 id 与 distributionManagement 中 repository 元素的 id 相匹配 -->
            <!-- <id>server001</id> -->
            <!-- 鉴权用户名 -->
            <!-- <username>my_login</username> -->
            <!-- 鉴权密码 -->
            <!-- <password>my_password</password> -->
            <!-- 鉴权时使用的私钥位置。默认是 ${user.home}/.ssh/id_dsa -->
            <!-- <privateKey>${usr.home}/.ssh/id_dsa</privateKey> -->
            <!-- 鉴权时使用的私钥密码 -->
            <!-- <passphrase>some_passphrase</passphrase> -->
            <!-- 文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录,这时候就可以使用该权限。其对应了 unix 文件系统的权限,如:664、775 -->
            <!-- <filePermissions>664</filePermissions> -->
            <!-- 目录被创建时的权限 -->
            <!-- <directoryPermissions>775</directoryPermissions> -->
        <!-- </server> -->
    <!-- </servers> -->

    <!-- 下载镜像列表 -->
    <mirrors>
        <!-- 设置多个镜像只会识别第一个镜像下载 jar 包-->
        <mirror>
            <!-- 该镜像的唯一标识符。id 用来区分不同的 mirror 元素 -->
            <id>aliyunmaven</id>
            <!-- 被镜像的服务器的 id。如果我们要设置了一个 maven 中央仓库(http://repo.maven.apache.org/maven2/)的镜像,就需要将该元素设置成 central。
                可以使用 * 表示任意远程库。例如:external:* 表示任何不在 localhost 和文件系统中的远程库,r1,r2 表示 r1 库或者 r2 库,*,!r1 表示除了 r1 库之外的任何远程库 -->
            <mirrorOf>*</mirrorOf>
            <!-- 镜像名称 -->
            <name>阿里云公共仓库</name>
            <!-- 镜像的 URL -->
            <url>https://maven.aliyun.com/repository/public</url>
        </mirror>
    </mirrors>

    <!-- 用来配置不同的代理 -->
    <proxies>
        <proxy>
            <!-- 代理的唯一定义符,用来区分不同的代理元素 -->
            <id>myproxy</id>
            <!-- 是否激活。当我们声明了一组代理,而某个时候只需要激活一个代理的时候 -->
            <active>false</active>
            <!-- 代理的协议 -->
            <protocol>http</protocol>
            <!-- 代理的主机名 -->
            <host>proxy.somewhere.com</host>
            <!-- 代理的端口 -->
            <port>8080</port>
            <!-- 代理的用户名,用户名和密码表示代理服务器认证的登录名和密码 -->
            <username>proxyuser</username>
            <!-- 代理的密码 -->
            <password>somepassword</password>
            <!-- 不该被代理的主机名列表。该列表的分隔符由代理服务器指定;例子中使用了竖线分隔符,逗号分隔也很常见 -->
            <nonProxyHosts>*.google.com|ibiblio.org</nonProxyHosts>
        </proxy>
    </proxies>

    <!-- 根据环境参数来调整构建配置的列表。对应 pom.xml 中 profile 元素(只包含 id、activation、repositories、pluginRepositories 和 properties 元素)
        如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何定义在 pom.xml 中带有相同 id 的 profile -->
    <profiles>
        <profile>
            <!-- profile 的唯一标识 -->
            <id>test</id>
            <!-- 自动触发 profile 的条件逻辑。也可通过 activeProfile 元素以及使用 -P 标记激活(如:mvn clean install -P test)
                在 maven 工程的 pom.xml 所在目录下执行 mvn help:active-profiles 命令可以查看生效的 profile -->
            <activation>
                <!-- 默认是否激活 -->
                <activeByDefault>false</activeByDefault>
                <!-- 当匹配的 jdk 被检测到,profile 被激活。例如:1.4 激活 JDK1.4、1.4.0_2,而 !1.4 激活所有版本不是以 1.4 开头的 JDK -->
                <jdk>1.8</jdk>
                <!-- 当匹配的操作系统属性被检测到,profile 被激活。os 元素可以定义一些操作系统相关的属性 -->
                <os>
                    <!-- 激活 profile的 操作系统的名字 -->
                    <name>Windows XP</name>
                    <!--激活 profile 的操作系统所属家族。如:windows -->
                    <family>Windows</family>
                    <!--激活 profile 的操作系统体系结构 -->
                    <arch>x86</arch>
                    <!--激活p rofile 的操作系统版本 -->
                    <version>5.1.2600</version>
                </os>
                <!-- 如果 maven 检测到某一个属性(其值可以在 pom.xml 中通过 ${name} 引用),其拥有对应的 name=值,Profile 就会被激活。如果值字段是空的,
                    那么存在属性名称字段就会激活 profile,否则按区分大小写方式匹配属性值字段 -->
                <property>
                    <!-- 激活 profile 的属性的名称 -->
                    <name>mavenVersion</name>
                    <!-- 激活 profile 的属性的值 -->
                    <value>2.0.3</value>
                </property>
                <!-- 通过检测该文件的存在或不存在来激活 profile-->
                <file>
                    <!-- 如果指定的文件存在,则激活 profile -->
                    <exists>${basedir}/file2.properties</exists>
                    <!-- 如果指定的文件不存在,则激活 profile -->
                    <missing>${basedir}/file1.properties</missing>
                </file>
            </activation>
            <!-- 对应 profile 的扩展属性列表。maven 属性和 ant 中的属性一样,可以用来存放一些值。这些值可以在 pom.xml 中的任何地方使用标记 ${X} 来使用,这里 X 是指属性的名称。
                属性有五种不同的形式,并且都能在 settings.xml 文件中访问:
                1. env.X:在一个变量前加上 "env." 的前缀,会返回一个 shell 环境变量。例如:"env.PATH" 指代了 $path 环境变量(在 Windows 上是 %PATH%)
                2. project.x:指代了 pom.xml 中对应的元素值。例如:<project><version>1.0</version></project> 通过 ${project.version} 获得 version 的值
                3. settings.x:指代了 settings.xml 中对应元素的值。例如:<settings><offline>false</offline></settings> 通过 ${settings.offline} 获得 offline 的值
                4. Java System Properties:所有可通过 java.lang.System.getProperties() 访问的属性都能在 pom.xml 中使用该形式访问,例如:${java.home}
                5. x:在 <properties/> 元素中,或者外部文件中设置,以 ${someVar} 的形式使用
            -->
            <properties>
                <!-- 如果该 profile 被激活,则可以在 pom.xml 中使用 ${user.install} -->
                <user.install>${user.home}/our-project</user.install>
            </properties>
            <!-- 远程仓库列表。它是 maven 用来填充构建系统本地仓库所使用的一组远程仓库 -->
            <repositories>
                <!--包含需要连接到远程仓库的信息 -->
                <repository>
                    <!-- 远程仓库唯一标识 -->
                    <id>codehausSnapshots</id>
                    <!-- 远程仓库名称 -->
                    <name>Codehaus Snapshots</name>
                    <!-- 如何处理远程仓库里 releases 的下载 -->
                    <releases>
                        <!-- 是否开启 -->
                        <enabled>false</enabled>
                        <!-- 该元素指定更新发生的频率。maven 会比较本地 pom.xml 和远程 pom.xml 的时间戳。
                            这里的选项是:always(一直),daily(默认,每日),interval:X(这里 X 是以分钟为单位的时间间隔),或者 never(从不)。 -->
                        <updatePolicy>always</updatePolicy>
                        <!-- 当 maven 验证构件校验文件失败时该怎么做:ignore(忽略),fail(失败),或者 warn(警告)-->
                        <checksumPolicy>warn</checksumPolicy>
                    </releases>
                    <!-- 如何处理远程仓库里快照版本的下载。有了 releases 和 snapshots 这两组配置,pom.xml 就可以在每个单独的仓库中,为每种类型的构件采取不同的策略。
                        例如:可能有人会决定只为开发目的开启对快照版本下载的支持 -->
                    <snapshots>
                        <enabled/>
                        <updatePolicy/>
                        <checksumPolicy/>
                    </snapshots>
                    <!-- 远程仓库 URL -->
                    <url>http://snapshots.maven.codehaus.org/maven2</url>
                    <!-- 用于定位和排序构件的仓库布局类型。可以是 default(默认)或者 legacy(遗留)-->
                    <layout>default</layout>
                </repository>
            </repositories>
            <!-- 插件的远程仓库列表。和 repositories 类似,repositories 管理 jar 包依赖的仓库,pluginRepositories 则是管理插件的仓库 -->
            <pluginRepositories>
                <!-- 每个 pluginRepository 元素指定一个 maven 可以用来寻找新插件的远程地址 -->
                <pluginRepository>
                    <id/>
                    <name/>
                    <releases>
                        <enabled/>
                        <updatePolicy/>
                        <checksumPolicy/>
                    </releases>
                    <snapshots>
                        <enabled/>
                        <updatePolicy/>
                        <checksumPolicy/>
                    </snapshots>
                    <url/>
                    <layout/>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>

    <!-- 手动激活 profiles 的列表 -->
    <!-- <activeProfiles> -->
        <!-- 要激活的 profile id。例如:env-test,则在 pom.xml 或 settings.xml 中对应 id 的 profile 会被激活。如果运行过程中找不到对应的 profile 则忽略配置 -->
        <!-- <activeProfile>env-test</activeProfile> -->
  <!-- </activeProfiles> -->
</settings>

卍 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目标
  • 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>
  • 不再支持源选项5。请使用7或更高版本的解决方法:除了上述在<plugin>元素中配置外,还有另外两种方法:
    • 一是在项目的pom文件中使用<properties>元素,配置如下:
      <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
      <java.version>11</java.version>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      </properties>
    • 第二种是在settings.xml 文件中配置,对所有项目有效
          <profiles>
              <!-- 解决如下问题:不再支持源选项5。请使用7或更高版本。-->
              <profile>
                  <id>jdk-1.8</id>
                  <activation>
                      <activeByDefault>true</activeByDefault>
                      <jdk>1.8</jdk>
                  </activation>
                  <properties>
                      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                      <maven.compiler.source>1.8</maven.compiler.source>
                      <maven.compiler.target>1.8</maven.compiler.target>
                  </properties>
              </profile>
          </profiles>

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包比较小。

  • `maven-assembly-plugin`: maven-shade-plugin 可以将项目所有的依赖打包进一个jar包里,maven-assembly-plugin则是可以将项目的所有依赖打包进一个zip或其他格式的压缩包里,这个压缩包里含有项目本身的jar包以及项目依赖的jar包。maven-assembly-plugin用法如下
    • pom.xml中配置
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-assembly-plugin -->
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-assembly-plugin</artifactId>
                          <version>3.3.0</version>
                          <configuration>
                              <descriptors>
                                  <!--描述文件路径-->
                                  <!--http://maven.apache.org/plugins/maven-assembly-plugin/-->
                                  <!--http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html-->
                                  <descriptor>${project.basedir}/src/main/assemble/assembly.xml</descriptor>
                              </descriptors>
                          </configuration>
                          <executions>
                              <execution>
                                  <id>assemble-package-zip</id><!--名字任意-->
                                  <phase>package</phase>
                                  <goals>
                                      <goal>single</goal><!--只运行一次-->
                                  </goals>
                              </execution>
                          </executions>
                      </plugin>
    • 描述文件${project.basedir}/src/main/assemble/assembly.xml:官方文档
      <?xml version="1.0" encoding="UTF-8"?>
      <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
          <!--最终打成的zip包命名方式: <artifactId>-<id>.<format>;如 study-assemble.zip -->
          <id>assemble</id>
          <formats>
              <format>zip</format>
          </formats>
          <includeBaseDirectory>true</includeBaseDirectory>
      
          <!--配置要归档的文件集合-->
          <fileSets>
              <fileSet>
                  <filtered>false</filtered>
                  <directory>src/main/mysql</directory>
                  <outputDirectory>./mysql</outputDirectory>
              </fileSet>
              <fileSet>
                <!-- 这个元素配置是否对这个目录下的文件开启资源过滤,即是否可以使用pom中配置的变量。-->
                  <filtered>true</filtered>
                <!--指定文件位置: 将<directory>配置的文件夹中的内容输出到<outputDirectory>指定的文件夹中-->
                  <directory>src/main/bin</directory>
                  <outputDirectory>./bin</outputDirectory>
              </fileSet>
              <fileSet>
                  <filtered>false</filtered>
                  <directory>src/main/resources</directory>
                  <outputDirectory>./</outputDirectory>
                  <includes>
                      <include>**</include>
                  </includes>
              </fileSet>
              <fileSet>
                  <filtered>true</filtered>
                  <directory>src/main</directory>
                  <outputDirectory>./</outputDirectory>
                  <includes>
                      <include>readme.txt</include>
                  </includes>
              </fileSet>
          </fileSets>
          <dependencySets>
              <dependencySet>
                  <outputDirectory>lib</outputDirectory>
                  <scope>runtime</scope>
              </dependencySet>
          </dependencySets>
      </assembly>

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针对依赖而言.

  • 编译classpath:Maven在编译项目主代码的时候使用一套classpath,第三方的类库(如spring-core)以依赖的方式被引入到classpath中 (经测试,mvn compile不会检测src/test/java中的编译错误代码,所以是只针对主代码);
  • 测试classpath:Maven在编译和执行测试代码的时候会使用另外一套classpath,第三方类库如JUnit也是以依赖的方式被引入classpath,但不同的是JUnit的依赖范围是test;
  • 运行classpath: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及以上,见下文<dependencyManagement>的介绍):导入依赖范围。该依赖范围不会对三种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:resolve· maven将会下载所需依赖并解决版本冲突。完成后会列出最终所用的依赖的版本,内容跟dependency:list相似。
    • 运行·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\to\my\repository</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·,然后可以找到·org\apache\maven\model\pom-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/ 等等。

※,远程仓库的配置(注意:<repositories>元素配置的远程仓库只是用来下载构件的,部署构件需要用<distributionManagement>元素另外配置远程仓库,即使两个仓库可能是同一个都是自己的Maven私服。)

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会按照定义的仓库顺序尝试下载构件:先使用pom.xml中配置的第一个仓库下载jar包,如果下载不到就会尝试第二个,如果pom.xml都下载不到就会使用settings.xml中配置的仓库(注:如果在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 元素的值将镜像仓库和原始仓库ID联系起来
  • <mirrorOf>*</mirrorOf>:值为*, 匹配所有远程仓库;
  • <mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库;
  • <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库repo1和repo2,使用逗号分隔多个远程仓库;
  • <mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。

注意:同一个远程仓库只能配置一个mirror,多个相同的mirror(即mirrorOf的值相同)只有第一个会生效。但是多个远程仓库可以配置多个mirror(mirrorOf的值不同)!通常的使用方法是:配置一个mirror,mirrorOf的值是central,即代理中央仓库,url地址是自己的Maven私服的group类型的仓库,然后在nexus3私服上可以配置proxy类型的central仓库的地址(比如配置成阿里云的Maven中央仓库地址(不配置的话就是默认的中央仓库地址),这样当私服中没有某个jar包时,就会去阿里云的Maven中央仓库地址(而不是默认的中央仓库)去拉此jar包)

注意2:某个远程仓库配置了镜像后,原始配置中的url已经无关紧要,但是原始配置中配置的对快照版是否支持等配置项依然有效。

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

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

需要认证的仓库一般是我们部署自己的组件的仓库,亦即 pom.xml中的 <distributionManagement>元素中配置的仓库。

假设需要为一个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. 部署至远程仓库(注意:这里配置的URL地址不会走镜像,所以只能在这里配置正确的仓库URL地址):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就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本(判断依据:是否含有-SNAPSHOT字样,而且必须是大写),则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。

※,快照版本: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确定插件的版本号的方法暂未研究出来,比如mvn sonar:sonar 即使在<build><plugsin><plguin>中指定了sonar的版本是3.0.2,但是使用mvn sonar:sonar时,Maven确定的 sonar-maven-plugin 版本号还是最新的3.9.1.2184。暂不清楚是怎么确定的!

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

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目标默认绑定了阶段(package阶段)-->
                    <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 目标默认绑定了 package  阶段,也就是说当用户配置使用maven-source-plugin的jar-no-fork目标的时候,如果不指定phase参数,该目标就会被绑定到package阶段。可以使用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都行。

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

※,插件配置 (顺带讲解关于 跳过测试 的配置)

现在的IDE工具如vscode、Intellij Idea 都可以直接运行Junit的单个测试方法,所以已经无需通过Maven的方式运行单元测试了。

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

1. 从命令行配置插件

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

  • ·mvn install -D maven.test.skip=true· // maven-surefire-plugin插件提供了一个maven.test.skip参数,设置为true就会跳过测试(不仅会跳过测试运行,还会跳过测试代码的编译)!
    • 参数maven.test.skip同时控制了maven-compiler-plugin和maven-surefire-plugin两个插件的行为,测试代码编译跳过了,测试运行也跳过了

  • ·mvn install -D skipTests· // 跳过测试(只会跳过测试运行,测试代码还是会被编译)。

关于Maven的测试说明:

在默认情况下,maven-surefire-plugin的test目标会自动执行测试源码路径(默认为src/test/java/)下所有符合一组命名模式的测试类。这组模式为:

  • **/Test*.java:任何子目录下所有命名以Test开头的Java类。
  • **/*Test.java:任何子目录下所有命名以Test结尾的Java类。
  • **/*TestCase.java:任何子目录下所有命名以TestCase结尾的Java类。

 只要将测试类按上述模式命名,Maven就能自动运行它们,注意:以Tests结尾的测试类是不会得以自动执行的。

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中通过ID字段来配置这些仓库的镜像),代码如下:

    <!-- 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中。
  • 解析插件简称(简写)(又称插件前缀):
    • Maven命令以插件形式运行时,要么是全格式(可以不带版本号),要么是前缀格式,没有其他中间形式。以maven-compiler-plugin插件为例:
      • 全格式:`mvn org.apache.maven.plugins:maven-compiler-plugin[:3.8.1]:compile`,其中表示版本号的:3.8.1可以省略。
      • 简称格式:·mvn compile:compile· // maven-compiler-plugin的前缀名称为 compile。
      • 错误格式: 除上述两种格式外都是错误格式,比如: mvn compile:3.8.1:compile。
    • 插件前缀与 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的同时,还能促进各个模块配置的一致性。

※,聚合

1. 使用方法

新建一个只含有POM文件的聚合器Maven项目(即一个文件夹里面包含一个POM文件,不需要其他任何文件夹和文件,当然为了其他目的存在文件或文件夹也没关系),这个POM文件是聚合器POM文件,此聚合器POM文件通过<modules>元素将所有需要聚合的Maven项目归并在一起即完成了聚合。直接在此聚合器Maven项目的POM文件所在目录运行 Maven命令即可对所有其他Maven项目执行此Maven命令。聚合器POM文件的内容如下,示例中的其他两个Maven项目位于聚合器项目内部,和聚合器POM文件同目录。 当然也可以将聚合器项目和其他项目平级,只需改动<module>元素的值即可。

<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.tong.java</groupId>
    <artifactId>aggregator</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 聚合器POM文件的打包方式必须是 pom -->
    <packaging>pom</packaging>
    <name>多个Maven项目的聚合器</name>

    <modules>
        <!-- module的值是Maven项目相对于当前POM文件的相对路径,即是文件夹的名称,和artifactId名称无关,
        但一般情况下,Maven项目文件夹的名称和artifactId是一致的 -->
        <module>second-maven</module>
        <module>study</module>
    </modules>
</project>

※,继承

1. 使用方法

新建一个只包含POM文件的Maven项目作为父工程(由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java/之类的文件夹了),配置父工程的坐标

<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.tong.java</groupId>
    <artifactId>parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 作为父模块的POM,其打包类型必须为 pom -->
    <packaging>pom</packaging>
    <name>Maven工程的父工程</name>
</project>

有了父模块,就可以让其他模块来继承它:注意relativePath元素,这个relativePath很关键,在不使用Maven仓库(或者无法使用Maven仓库)的场景下,能否找到所有的项目就靠这个relativePath的正确配置了!!!

  • 元素relativePath表示父模块POM的相对路径。当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到 或者 找到的POM文件和声明的父POM坐标对不上,Maven会再从本地仓库找。
  • relativePath 默认值是 ../pom.xml,即Maven默认父POM在上一层目录下。
    <parent>
        <groupId>com.tong.java</groupId>
        <artifactId>parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!-- relativePath 默认值为"../pom.xml",即默认父POM文件位于上一级目录下 -->
        <relativePath>../maven-parent/pom.xml</relativePath>
    </parent>

配置好父模块之后,还可以将父模块添加到聚合器Maven项目中。

2, 可继承的POM元素

 父POM文件中的一些元素可以被继承,如果子项目中没有相关的元素则默认继承父POM中定义的对应元素。可继承的POM元素有:

  • groupId:项目组ID,项目坐标的核心元素;
  • version:项目版本,项目坐标的核心元素;
  • description:项目的描述信息;
  • organization:项目的组织信息;(organization元素下有 name 和 url 两个元素)
  • inceptionYear:项目的创始年份;(inception:开始,开端;interception:拦截)
  • url:项目的URL地址;
  • developers:项目的开发者信息;
  • contributors:项目的贡献者信息;
  • distributionManagement:项目的部署配置;项目deploy时的仓库配置,部署构件到仓库时一般需要认证。
        <distributionManagement>
            <repository>
                <id>proj-release</id>
                <name>Project release Repository</name>
                <url>http://mvn.mizss.com/repository/maven-releases/</url>
            </repository>
            <snapshotRepository>
                <id>proj-snapshots</id>
                <name>Project snapshots Repository</name>
                <url>http://mvn.mizss.com/repository/maven-snapshots/</url>
            </snapshotRepository>
        </distributionManagement>

    认证信息在 settings.xml 中配置,

    <!-- 配置部署仓库的认证信息 -->
        <servers>
            <server>
                <!-- 此处id的值需和pom文件中配置的仓库id保持一致 -->
                <id>proj-release</id>
                <username>developer</username>
                <password>cjwl8888</password>
            </server>
            <server>
                <id>proj-snapshots</id>
                <username>developer</username>
                <password>cjwl8888</password>
            </server>
        </servers>
  • issueManagement:项目的缺陷跟踪系统信息;
  • ciManagement:项目的持续集成系统信息;
  • scm:项目的版本控制系统信息,scm即 Source Code Management,源码管理
        <scm>
            <connection>scm:git:http://url.repository.com</connection>
            <developerConnection>scm:git:http://url.repository.com</developerConnection>
            <url>http://url.repository.com</url>
            <tag>HEAD</tag>
        </scm>
  • mailingLists:项目的邮件列表信息;

  • properties:自定义的Maven属性;
  • dependencies:项目的依赖配置;
  • dependencyManagement:项目的依赖管理配置; 父POM中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本(子模块声明使用的依赖使用的是顶级的<dependencies>元素),也就不会发生多个子模块使用依赖版本不一致的情况。这可以帮助降低依赖冲突的几率
        <!-- 
            <dependencies> 与 <dependencyManagement>的区别:
            1. <dependencyManagement>元素只在父POM中出现;
            2. 父POM中 <dependencies>下的依赖即使在子项目中不写该依赖,那么子项目仍然会从父项目中继承该依赖(全部继承)。
            3. 父POM中 <dependencyManageMent>中的依赖只是声明,既不会给父项目引入依赖,也不会给子项目引入依赖,不过这段配置是会被继承的
                当子项目中不显式声明此依赖,即使该依赖已经在父POM的<dependencyManagement>中声明了,也不会产生实际的效果;
                当子项目中显式的声明了此依赖,并且没有指定<version> 才会从父项目中继承该依赖:<version>,<scope>都会被继承;
                当子项目中显式的声明了此依赖,而且指定了<version>,那么Maven会使用子项目中指定的版本。
         -->
    import 依赖范围:
    该范围的依赖只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。
    假如某个模块想使用 某个pomA 中的dependencyManagement中的配置,除了复制配置或继承之外,还可以使用 import 依赖范围将这一配置导入。
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>groupId of pomA</groupId>
                    <artifactId>artifactId of pomA</artifactId>
                    <version>version of pomA</version>
                    <!-- 
                        import范围依赖由于其特殊性,一般都是指向打包类型为pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,
                       则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置
                    -->
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
  • repositories:项目的仓库配置;
  • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等;

    • <build>元素下有一个<pluginManagement>用于管理插件,类似于依赖管理的<dependencyManagement>,在该元素中配置的插件不会造成实际的插件调用行为。只有当子项目中配置了此插件时(子项目配置实际使用的插件使用的是`<build><plugins>`元素),<pluginManagement>中的插件配置才会影响实际的插件行为(子项目中没有配置的项皆会继承父项目的配置)。例:
          <!-- 父POM中的插件管理配置 -->
          <build>
              <pluginManagement>
                  <plugins>
                      <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>
                                  <goals>
                                      <goal>jar-no-fork</goal>
                                  </goals>
                              </execution>
                          </executions>
                      </plugin>
                  </plugins>
              </pluginManagement>
          </build>
      子项目中需要使用此插件生成源码包的时候,只需配置groupId和artifactId即可继承父项目中的此插件的配置。如果子项目无需生成源码包,则不配置此插件即可。
      <build>
          <plugins>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-source-plugin</artifactId>
              </plugin>
          </plugins>
      </build>

      对于插件和依赖,推荐使用父模块POM的<dependencyManagement>和<pluginManagement>元素管理,这样可以统一 依赖 和 插件的版本,避免潜在的因版本不一致导致的问题,也更容易维护

  • reporting:包括项目的报告输出目录配置、报告插件配置等;

3,聚合和继承的关系

  • 多模块Maven项目中的聚合和继承是两个完全不同的概念。聚合是为了方便快速构建项目,继承主要是为了消除重复配置。
  • 聚合模块和父模块的POM文件的<packaging>都必须是 pom, 而且两者都是除了POM文件之外并没有任何实质的内容。

实际的项目中,往往一个 POM 即是聚合POM,又是父POM。可以将两者合并,主要为了方便。这个POM中同时含有 <modules> 和 其他元素。

4,反应堆(Reactor)

4.1,在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构件结构,对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。

4.2, 反应堆的构建顺序:在聚合器项目下执行命令时,Maven输出会打印出反应堆的构建顺序。反应堆实际的构建顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。

4.3,模块间的依赖关系会将反应堆构成一个有向非循环图(DirectedAcyclic Graph,DAG),各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错

4.4, 裁剪反应堆: 仅仅构建完整反应堆中的某些个模块。Maven提供很多的命令行选项支持裁剪反应堆,输入mvn  -h可以看到这些选项:

  • -am,--also-make,同时构建所列模块的依赖模块;
    • `clean install -pl module -am`
  • -amd,--also-make-dependents,同时构建依赖于所列模块的模块;
    • `mvn clean install -pl module -amd`
  • -pl,--projects <arg> 构建指定的模块,模块间用逗号分隔;
    • ·mvn clean install -pl module1, module2·
  • -rf,--resume-from <arg> 在完整的反应堆构建顺序基础上指定从哪个模块开始构建
    • `mvn clean install -rf module`

在开发过程中,灵活应用上述4个参数,可以帮助我们跳过无须构建的模块,从而加速构建。在项目庞大、模块特别多的时候,这种效果就会异常明显。

卍,使用Nexus搭建Maven私服

※,下载地址:

※,安装

1. 直接安装在物理机上: 安装包自带了Jetty容器,无需额外的Web容器就能直接启动Nexus。

  解压后有两个文件夹

  • nexus-3.28.1-01 // 该目录包含了Nexus运行所需要的文件,如启动脚本、依赖jar包等
    • 启动脚本·bin/nexus {start|stop|run|run-redirect|status|restart|force-reload}· // start直接后台运行看不到日志,run可以看到日志。
      • 若启动正常时,访问可能无果,尝试把防火墙关闭一下,systemctl stop firewalld ;
    • 配置文件:·etc/nexus-default.properties` //默认端口8081
  • sonatype-work  //该目录包含Nexus生成的配置文件、日志文件、仓库文件等。删除该目录可以初始化配置,再次启动时会重新生成此目录。

2. docker安装

  • docker search nexus
  • docker pull sonatype/nexus3
  • docker run --name mynexus3 -d -p 8081:8081 -v /opt/software/nexus3/data/:/nexus-data sonatype/nexus:latest
  • docker方式部署完成。docker logs mynexus3可以查看日志,如果启动失败可以查看到报错信息!

※,配置、使用

大致思路:

  1. 创建一个proxy仓库,两个hosted仓库,然后将这三个仓库放在新创建的group仓库中。
  2. 配置pom.xml以及settings.xml,见下面叙述

访问 http://ip:port  即可访问nuxus3图形化界面。

第一步:配置nexus的仓库。参考下面的文章

https://www.jianshu.com/p/fa33a9cdab99

https://www.cnblogs.com/qdhxhz/p/9801325.html

  • Blob Stores // 配置文件的存储目录
  • Repositories //配置仓库,nexus3中的仓库有以下几种类型:
    • proxy:代理仓库,即当本地私服中没有组件时就向此代理仓库请求下载并缓存到本地。代理仓库url默认是中央仓库,也可以在页面上将其设置为阿里云的中央仓库:https://maven.aliyun.com/repository/central
    • hosted: 自己持有的仓库,开发时部署的组件即是部署到此类型的仓库中(而且只能部署到hosted类型的仓库中)
    • group:整合组。group是由proxy仓库和hosted仓库组合而成,可以配置某个组由哪些proxy和hosted仓库组成(配置时最好将hosted仓库放置在proxy仓库的上面)。proxy和hosted是nexus3内部的概念,而对外则是用group的url,比如在settings.xml 中配置镜像时,填写的私服地址即是group的url。group一般配置一个即可,在页面上将hosted(分为snapshots和releases两个具体仓库) 和 proxy 仓库添加至此group仓库

关于nexus的一些实际使用中的配置:

  • pom.xml文件中配置 <distributionManagement>, 使用mvn deploy时便会将组件部署至此处配置的仓库中。nexus3仓库对于匿名用户默认是只读的,还需要在settings.xml中配置认证信息
    <distributionManagement>
        <repository>
            <id>tonus-releases</id>
            <name>Tonus_Releases</name>
            <!--注意这里的url只能对应nexus3中hosted类型的仓库-->
            <url>http://10.8.0.238:8081/repository/host-releases/</url>
        </repository>
        <snapshotRepository>
            <id>tonus-snapshots</id>
            <name>Tonus_Snapshots</name>
          <!--当pom.xml中的version含有 -SNAPSHOT字样时(精确匹配,大小写也需匹配),Maven会将其认定为快照版本,其他全被认定是正式版本-->
            <url>http://10.8.0.238:8081/repository/host-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>
  • settings.xml中配置认证信息(对应pom.xml中<distributionManagement>元素中配置的仓库)
    <servers>
        <server>
            <!--此处ID对应pom.xml中<distributionManagement>元素中配置的仓库的ID-->
            <id>tonus-releases</id>
            <username>admin</username>
            <password>admin123</password>
        </server>
        <server>
            <id>tonus-snapshots</id>
            <username>admin</username>
            <password>admin123</password>
        </server>
    </servers>
  • settings.xml中将私服配置成中央仓库的镜像
    <mirrors>
    <!--可以配置多个仓库的镜像,不同的mirrorOf值对应不同的被镜像仓库-->
        <mirror>
            <!--此处ID随便取,不同的镜像间不重复即可-->
            <id>maven-public</id>
            <!--mirrorOf的值对应 pom.xml中<repositories> 或 <pluginRepositories>元素中配置的仓库ID 或者 是central-->
            <mirrorOf>central</mirrorOf>
            <name>Nexus Tonus</name>
            <!--此处url对应nexus3中的group类型的仓库url-->
            <url>http://192.168.1.156:8081/repository/tonus-public/</url>
        </mirror>
    </mirrors>
  • 另外settings.xml中有个<profile>属性,可以配置所有Maven项目都会使用的仓库,相当于将pom.xml中的  <repositories> 和 <pluginRepositories>  抽取到公共的settings.xml文件中。例如setting.xml中一个示例
        <profile>
          <id>jdk-1.4</id>
    
          <activation>
            <jdk>1.4</jdk>
          </activation>
    
          <repositories>
            <repository>
              <id>jdk14</id>
              <name>Repository for JDK 1.4 builds</name>
              <url>http://www.myhost.com/maven/jdk14</url>
              <layout>default</layout>
              <snapshotPolicy>always</snapshotPolicy>
            </repository>
          </repositories>
        </profile>

※,使用mvn deploy 将第三方jar包部署至远程私服仓库中。

  • 部署SNAPSHOT版本:
  • mvn deploy:deploy-file -DgroupId=com.seewo.resource -DartifactId=resource-data-open-sdk -Dversion=1.0-SNAPSHOT -Dpackaging=jar -Dfile=C:\Users\Everest\Desktop\resource-data-open-sdk-1.0-SNAPSHOT.jar -Durl=http://mvn.mizss.com/repository/maven-snapshots/ -DrepositoryId=maven-snapshots
    • -Durl的值 pom.xml(distributionManagement)中配置的仓库的地址。
    • -DrepositoryId的值为 pom.xml(distributionManagement) 或setting.xml中配置的仓库的ID。
    • 注意:mvn deploy部署SNAPSHOT版本时,jar包不能在本地仓库路径下,否则会报错:Cannot deploy artifact from the local repository:xxxx。只要将这个jar包移到其他目录下即可。部署正式版没有这个问题。
  •  
  • 部署正式版本
  • mvn deploy:deploy-file -DgroupId=com.seewo.resource -DartifactId=resource-data-open-sdk -Dversion=1.0 -Dpackaging=jar -Dfile=C:\mySoftware\apache-maven-3.6.3\repository\com\seewo\resource\resource-data
    -open-sdk\1.0-SNAPSHOT\resource-data-open-sdk-1.0-SNAPSHOT.jar -Durl=http://mvn.mizss.com/repository/maven-releases/ -DrepositoryId=maven-releases

※,Nexus不是唯一的私服软件,另外还有Apache的Archiva与JFrog的Artifactory

卍,使用Maven进行测试

卍,持续集成--Jenkins(前身Hudson): https://www.jenkins.io/zh/doc/tutorials/

1. 下载 运行

  • war包下载:http://mirrors.jenkins-ci.org/war-stable/latest/
  • 运行: `java -jar jenkins.war --httpPort=8081`或者后台运行:·nohup java -jar jenkins.war --httpPort=8082 &· 实时查看日志:·tail -f nohup.out`
  • 默认安装位置为 $HOME/.jenkins/
  • 配置好Jenkins的job后,构建过程是:Jenkins所在服务器拉取仓库代码到 $HOME/.jenkins/workspace/<jobName>,然后执行配置的任务。

2. Cannot run program "mvn" (in directory "..."): error=2, No such file or directory 

解决方法:(某个项目的) 配置-> 构建-> 调用顶层Maven目标-> Maven版本->  (选择在"系统管理" -> "全局工具配置" 里配置的Maven Name)

3. 大致使用流程:

  • 全局配置: Dashboard --> Manage Jenkins --> Global Tool Configuration --> 配置 Maven、Git、JDK路径。
  • 新建job: 建立某个项目的构建job,配置此项目相关的配置项。一般由6个步骤
    • General
    • 源码管理:设置项目的源码仓库地址,并配置用户名和密码,分支等等。
    • 构建触发器:告诉Jenkins什么时候进行构建。一些选项解释如下:
      • Build after other projects are built : 只有另一个构建完毕后,才会开始一个构建。这是设置一个构建管道的简单方法。
      • Build periodically : 定期构建。使用cron配置定期时间。
      • Poll SCM :通过cron配置,定期轮询源码仓库,当发现源码有变化时就会执行构建,无变化则不执行任何操作。这种策略适合较小的项目而不适合规模化。
      • 远程触发构建:一般是通过webhooks,当提交更改时,让scm系统(如github, gogs等source code management系统)触发Jenkins构建。
      • gogs+jenkins 实现自动化部署
    • 构建环境:
    • 构建:此处配置执行Maven的什么操作,如clean package。注意要选择Maven版本(全局配置的那个Maven名称)。
    • 构建后操作:

4. 

卍,使用Maven构建Web应用

※,web项目结构

  • Maven约定的Java Web项目结构和JAR项目一样,Web项目比较特殊的地方在于它还有一个Web资源目录,其默认位置是 src/main/webapp/,此目录下必须包含一个子目录 WEB-INF, 该子目录还必须 包含一个 Web资源表述文件web.xml。
  • 几乎所有的Web项目都需要依赖下面两个jar包: 它们为servlet 和 jsp的编写提供支持。

    需要注意的是,这两个依赖的范围是provided(对于编译和测试classpath有效,对于运行classpath无效),表示它们最终不会被打包至war文件中,这是因为几乎所有Web容器都会提供这两个类库,如果war包中重复出现,就会导致潜在的依赖冲突问题。

            <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>javax.servlet.jsp-api</artifactId>
                <version>2.3.3</version>
                <scope>provided</scope>
            </dependency>
  •  

※,使用 jetty-maven-plugin 插件 运行web项目 【使用jetty 快速开发和测试】

传统的Web测试方法要求我们编译、测试、打包及部署,这往往会消耗数10秒至数分钟的时间,jetty-maven-plugin能够帮助我们节省时间,它能够周期性地检查项目内容,发现变更后自动更新到内置的Jetty Web容器中。亦即 jetty-maven-plugin 插件能够省去 打包和部署 的步骤。使用jetty-maven-plugin插件的方法是在pom中配置坐标和相关配置:

            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.36.v20210114</version>
                <configuration>
                    <httpConnector>
                        <port>8080</port> //此处如果不配置默认是8080
                    </httpConnector>
                    <stopKey>shutdown</stopKey>
                    <stopPort>9080</stopPort>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <reload>automatic</reload>
                    <dumpOnStart>false</dumpOnStart>
                    <webApp>
                        <contextPath>/my-web</contextPath>
                    </webApp>
                    <systemProperties>
                        <systemProperty>
                            <name>org.eclipse.jetty.server.Request.maxFormContentSize</name>
                            <value>500000000</value>
                        </systemProperty>
                    </systemProperties>
                </configuration>
            </plugin>

 由于Maven默认只有org.apache.maven.plugins和org.codehaus.mojo两个groupId下的插件才支持简化的命令行调用(以简称运行Maven命令) ,所以如果想运行 ·mvn jetty:run·还需要在settins.xml中配置插件的groupId。

<pluginGroups>
    <!--新版Maven3.6.3貌似不配置这个也能以mvn jetty:run 简称运行jetty插件-->
    <pluginGroup>org.eclipse.jetty</pluginGroup>
  </pluginGroups>
以debug模式运行 mvn jetty:help,会发现即使在settings.xml中没有配置pluginGroup,Maven通过POM配置也能找到jetty对应的具体插件位置。
[DEBUG] Resolving plugin prefix jetty from [com.tong.java, org.apache.maven.plugins, org.codehaus.mojo]
[DEBUG] Resolved plugin prefix jetty to org.eclipse.jetty:jetty-maven-plugin from POM com.tong.java:my-web:war:1.0.1-SNAPSHOT【已从POM中解析出前缀jetty对应org.eclipse.jetty:jetty-maven-plugin】
但是自己写的插件必须要将自己的组名添加至settings.xml中,否则会报错!可能原因是jetty和cargo这种插件很普遍很流行,Maven 以关键字 jetty和cargo 从POM配置中解析出其对应的具体的插件全称,但是自己写的插件Maven不会做处理。
  • `mvn jetty:run -D jetty.port=8082` // -D jetty.port 参数可以动态指定jetty端口,但是如果已经在pom文件中配置了jetty的端口号,这里的参数指定的端口将无效,以pom文件配置的为准。

※,使用 cargo-maven2-plugin 实现自动化部署

0.  Cargo是一组帮助用户操作Web容器的工具,它能够帮助用户实现自动化部署,而且它几乎支持所有的Web容器,如Tomcat、JBoss、Jetty和Glassfish等。Cargo通过cargo-maven2-plugin提供了Maven集成,Maven用户可以使用该插件将Web项目部署到Web容器中

1. 虽然cargo-maven2-plugin和jetty-maven-plugin的功能看起来很相似,但它们的目的是不同的,jetty-maven-plugin主要用来帮助日常的快速开发和测试,而cargo-maven2-plugin主要服务于自动化部署。

2. 在 settings.xml中配置: <pluginGroup>org.codehaus.cargo</pluginGroup> (同jetty插件差不多,cargo插件在Maven3.6.3版本貌似已经无需配置了,debug模式可以看到cargo解析过程)

3. cargo-maven2-plugin 可以将web应用部署至本地 或远程Web容器中。

3.1 部署至本地Web容器:

  • Cargo支持两种本地部署的方式,分别为standalone模式和existing模式。在standalone模式中,Cargo会从Web容器的安装目录复制一份配置到用户指定的目录,然后在此基础上部署应用,每次重新构建的时候,这个目录都会被清空,所有配置被重新生成。而在existing模式中,用户需要指定现有的Web容器配置目录,然后Cargo会直接使用这些配置并将应用部署到其对应的位置

  • standalone模式
                <plugin>
                    <groupId>org.codehaus.cargo</groupId>
                    <artifactId>cargo-maven2-plugin</artifactId>
                    <version>1.8.4</version>
                    <configuration>
                        <container>
                            <!--这里必须填写tomcat?x, ?代表当前的Tomcat大版本数字-->
                            <containerId>tomcat9x</containerId>
                            <!--容器的安装目录-->
                            <home>C:\myD\software\apache-tomcat-9.0.35</home>
                        </container>
                        <configuration>
                            <!--standalone模式:将Tomcat容器配置复制至下面<home>目录。web应用也会部署至此文件下-->
                            <type>standalone</type>
                            <!-- ${project.build.directory}即 /target目录,这个目录是在超级POM中配置的 -->
                            <home>${project.build.directory}/tomcatDir</home>
                            <properties>
                                <!--如果不配置此项, cargo默认会让Web容器监听8080端口-->
                                <cargo.servlet.port>8085</cargo.servlet.port>
                            </properties>
                        </configuration>
                    </configuration>
                </plugin>
  • existing模式
                <plugin>
                    <groupId>org.codehaus.cargo</groupId>
                    <artifactId>cargo-maven2-plugin</artifactId>
                    <version>1.8.4</version>
                    <configuration>
                        <container>
                            <!--这里必须填写tomcat?x, ?代表当前的Tomcat大版本数字-->
                            <containerId>tomcat9x</containerId>
                            <!--容器的安装目录-->
                            <home>C:\myD\software\apache-tomcat-9.0.35</home>
                        </container>
                        <configuration>
                            <!--existing模式:直接将web应用部署至Tomcat安装目录-->
                            <!--端口是Tomcat配置文件server.xml中配置的,默认是8080-->
                            <type>existing</type>
                            <home>C:\myD\software\apache-tomcat-9.0.35</home>
                        </configuration>
                    </configuration>
                </plugin>
  •  ·mvn cargo:start·
  • `mvn cargo:run`
  • 注意运行cargo命令前需要先 mvn package。`mvn clean package cargo:run`

3.2 部署至远程Web容器:

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>
                <version>1.8.4</version>
                <configuration>
                    <container>
                        <!--这里必须填写tomcat?x, ?代表当前的Tomcat大版本数字-->
                        <containerId>tomcat9x</containerId>
                        <!--默认值是installed:即寻找对应的容器安装目录或者安装包,部署至本地Web容器时,使用的就是默认值。
                        当部署至远程Web容器时,安装目录或者安装包是不需要的,这里需显示指定为remote-->
                        <type>remote</type>
                    </container>
                    <configuration>
                        <!--表示依赖一个已经在运行的容器-->
                        <type>runtime</type>
                        <properties>
                            <cargo.remote.username>admin</cargo.remote.username>
                            <cargo.remote.password>admin123</cargo.remote.password>
                            <cargo.tomcat.manager.url>http://some-remote-ip:8080/manager</cargo.tomcat.manager.url>
                        </properties>
                    </configuration>
                </configuration>
            </plugin>

`mvn cargo:redeploy`   // 如果容器中已经部署了当前应用,Cargo会先将其卸载,然后再重新部署

※,

卍,版本管理

※,版本管理(Version Management)和 版本控制(Version Control)

  • 版本管理是指项目整体版本的演变过程管理,如从1.0-SNAPSHOT到1.0,再到1.1-SNAPSHOT。

  • 版本控制是指借助版本控制工具(如Git)追踪代码的每一个变更。

※,Maven的版本号定义约定:<主版本>.<次版本>.<增量版本>-<里程碑版本>。主版本和次版本之间,以及次版本和增量版本之间用点号分隔,里程碑版本之前用连字号分隔。

  • 主版本:表示了项目的重大架构变更。例如,Maven 2和Maven 1相去甚远;Struts 1和Struts 2采用了不同的架构;JUnit 4较JUnit 3增加了标注支持。

  • 次版本:表示较大范围的功能增加和变化,及Bug修复。例如Nexus 1.5较1.4添加了LDAP的支持,并修复了很多Bug,但从总体架构来说,没有什么变化。

  • 增量版本:一般表示重大Bug的修复,例如项目发布了1.4.0版本之后,发现了一个影响功能的重大Bug,则应该快速发布一个修复了Bug的1.4.1版本。

  • 里程碑版本:顾名思义,这往往指某一个版本的里程碑。

不是每个版本号都必须拥有这四个部分。一般来说,主版本和次版本都会声明,但增量版本和里程碑就不一定了。

当用户在声明依赖或插件未声明版本时,Maven就会根据上述的版本号约定自动解析最新版本。这个时候就需要对版本号进行排序。对于主版本、次版本和增量版本来说,比较是基于数字的,因此1.5>1.4>1.3.11>1.3.9。而对于里程碑版本,Maven则只进行简单的字符串比较,因此会得到1.2-beta-3>1.2-beta-11的结果。

※,自动化版本发布(maven-release-plugin)

1. 版本发布可以完全手工进行。版本发布意思就是将代码打包部署到Maven仓库中,手工操作的大致步骤有:检查是否有快照版依赖(不能有快照版依赖),将项目本身的版本由快照改为正式版(去除SNAPSHOT字样,当然如果只是想发布SNAPSHOT版本就无需进行这步操作了),提交所有代码,执行·mvn deploy·。

2,maven-release-plugin 插件的使用方法

要使用release插件需要两个配置信息,一个是插件本身的配置信息,一个是版本控制系统信息。配置信息如下

              <plugin>
                <artifactId>maven-release-plugin</artifactId>
                <configuration>
                    <!--tagBase和branchBase并非是一定要配置的,如果版本控制系统使用了标准的布局(即下面所配置的),Maven便能够自动识别-->
                    <!--<tagBase>https://github.com/everest33/java/tags</tagBase>
                    <branchBase>https://github.com/everest33/java/branches</branchBase>-->
                    
                    <!--以下三个是release:branch目标所需要的的配置-->
                    <branchName>test-branch</branchName>
                     <!--切换分支时会产生一个新的快照版,这里true表示分支使用新的版本-->
                    <updateBranchVersions>true</updateBranchVersions>
                     <!--这里false表示原来的主分支不使用新产生的版本-->
                    <updateWorkingCopyVersions>false</updateWorkingCopyVersions>
                </configuration>
            </plugin>

    <!-- source code management -->
    <scm>
        <!--<connection>表示一个只读的scm地址,scm:git:前缀是Maven规定-->
        <connection>scm:git:git@github.com:everest33/java.git</connection>
        <!--<developerConnection>表示可写的scm地址,scm:git:前缀是Maven规定-->
        <developerConnection>scm:git:git@github.com:everest33/java.git</developerConnection>
        <!--<url>表示可以在浏览器中访问的地址:也就是说要以http(s)开头-->
        <url>https://github.com/everest33/java</url>
        <tag>HEAD</tag>
    </scm>

3. maven-release-plugin 插件可以将版本发布的流程自动化。这个插件有若干个目标(goal),其中有两个常用的:

  • release:prepare,准备版本发布,依次执行以下操作(注:三次用户输入是连续进行的,但实际执行顺序是按如下步骤的
    • 检查项目是否有未提交的代码
    • 检查项目是否有快照版依赖( 不能有快照版依赖,但是release:prepare 执行时需要项目本身是快照版本,否则会报错)
    • 根据用户的输入将快照版本升级为发布版
    • 将POM中的SCM信息更新为标签地址(即将<scm><tag>的值由HEAD改为具体的tag值)
    • 基于修改后的POM执行构建Maven构建
    • 提交POM更改(commit message一般为:[maven-release-plugin] prepare release parent-1.1.2 )
    • 基于用户输入为代码打标签
    • 基于用户输入将代码从发布版升级为新的快照版
    • 提交POM更改(commit message一般为:[maven-release-plugin] prepare for next development iteration )
  • release:perform,执行版本发布。签出release:prepare生成的标签中的源代码,并在此基础上执行·mvn deploy·命令打包并部署构件至仓库中。

3.1, 执行release:prepare命令时会自动产生两个文件:

  • pom.xml.releaseBackup:此文件用于release:rollback目标,即回退release:prepare目标所执行的操作,将pom回退至之前的状态并提交。但是需要注意的是rollback并不会删除release:prepare生成的标签,因此用户需要手动删除。
  • release.properties:此文件用于release:perform目标,perform目标所执行的操作依赖于此文件中的内容,所以如果执行perform前删除了此文件就会报错,比如经常会报这个错误: No SCM URL was provided to perform the release from。

3.2,如果项目的打包类型是jar,在执行release:perform后不仅项目的主构件jar包会被生成并发布至仓库中,基于该主构件的-sources.jar和-javadoc.jar也会生成并发布。release:perform是怎样生成-sources.jar和-javadoc.jar的呢?超级POM中定义了一个名为 release-profile 的<profile>元素(<profile>是指一段在特定情况下被激活并更改Maven行为的配置),其中的activation元素下有一个名为performRelease、值为true的属性配置,这表示当Maven运行时,如果运行环境中有performRelease属性且值为true的时候,该Profile就被激活。也就是说,该Profile下的配置会得到应用

这个Profile配置了3个Maven插件,maven-sources-plugin的jar目标会为项目生成-source.jar文件,maven-javadoc-plugin的jar目标会为项目生成-javadoc.jar文件,而maven-deploy-plugin的update-release-info配置则会在部署的时候更新仓库中的元数据,告诉仓库该版本是最新的发布版。每个插件配置中值为true的inherited元素则表示该插件配置可以被子POM继承。

在执行release:perform的时候,Maven Release Plugin会自动生成值为true的performRelease属性。这时,超级POM中的release-profile就会被激活。另外还可以通过命令行参数激活profile,比如:·mvn clean install -DperformRelease=true·

 <profile>
      <id>release-profile</id>

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

3.3 ,自动化创建分支:release:branch

使用 maven-release-plugin插件自动创建分支有两种方法,

  • 一是通过命令行传参数,·mvn release:branch-DbranchName=<myBranchName>  -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false·
  • 而是在pom中配置参数,然后直接运行 mvn release:branch。配置如上文。

※,GPG签名

当从中央仓库下载第三方构件的时候,你可能会想要验证这些文件的合法性,例如它们是由开源项目官方发布的,并且没有被篡改过。PGP(Pretty Good Privacy)就是这样一个用来帮助提高安全性的技术。PGP最常用来给电子邮件进行加密、解密以及提供签名,以提高电子邮件交流的安全性。

GnuPG(简称GPG,来自http://www.gnupg.org/)是PGP标准的一个免费实现,无论是类UNIX平台还是Windows平台,都可以使用它。GPG能够帮助我们为文件生成签名、管理密钥以及验证签名等。

GPG用法:

  • `gpg --version` //查看版本
  • ·gpg --gen-keys` // 生成密钥对,私钥自己保存,用于对文件进行签名(即加密文件)。公钥分发给用户,用于对签名进行验证,即检测文件内容是否被篡改。生成时可以给私钥设置一个密码,以防万一私钥被别人得到。
  • ·gpg --list-keys` // 列出本机的密钥对,可以看到公钥和私钥的UUID
  • ·gpg --list-secret-keys· //列出本机的私钥
  • ·gpg -ab <someFile>· // 对某个文件进行签名,-a选项表示gpg将创建ASCII格式的输出,-b告诉gpg创建一个独立的签名文件。
  • ·gpg --verify <签名文件>` // 根据签名文件对文件内容进行验证。
  • ·gpg --keyserver hkp://pgp.mit.edu --send-keys <公钥ID>· // 为了能让你的用户获取公钥并验证你分发的文件,需要将公钥分发到公钥服务器中。例如,hkp://pgp.mit.edu是美国麻省理工学院提供的公钥服务器,此命令将公钥分发到该服务器中。公钥会在各个公钥服务器中被同步,因此你不需要重复地往各个服务器分发同一公钥
  • ·gpg --keyserver hkp://pgp.mit.edu --recv-keys  <公钥ID>· // 你的用户可以将服务器上的公钥导入到本地机器。

Maven GPG Plugin 可以自动完成签名。一般只会在版本发布时才对项目进行签名,平时开发时对构件签名没有什么意义,反而耗时。在超级POM中有一个release-profile,该Profile只有在Maven属性performRelease为true的时候才被激活,而release:perform执行的时候,就会将该属性置为true,这正是项目进行版本发布的时刻。因此,类似地,可以在settings.xml或者POM中创建自动激活的Profile对项目进行签名,代码如下

<profiles>
    <profile>
        <id>release-sign-artifacts</id>
        <activation>
            <property>
                <name>performRelease</name>
                <value>true</value>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <version>xxxxx</version>
                    <executions>
                        <execution>
                            <id>sign-artifacts</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>sign</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

最后需要一提的是,由于一个已知的Maven Release Plugin的Bug,release:perform执行过程中签名可能会导致进程永久挂起。为了避免该情况需要在 maven-release-plugin的configuration中配置 <mavenExecutorId>forked-path</mavenExecutorId>

※,

卍,灵活的构建:Maven为了支持构建的灵活性,内置了三大特性,即属性Profile资源过滤

※,Maven属性:Maven属性一共有6类,分别是

  • 内置属性:主要有两个内置属性
    • ${basedir}; 表示项目根目录,即包含pom.xml文件的目录。
    • ${version}; 表示项目版本。
  • POM属性:用户可以使用该类属性引用POM文件中对应元素的值。如${project.artifactId} 对应了<project><artifactId>元素的值,常用的POM属性有(一些是在超级POM中定义的):
    • ${project.build.sourceDirectory}:项目的主源码目录,默认为src/main/java/

    • ${project.build.testSourceDirectory}:项目的测试源码目录,默认为src/test/java/

    • ${project.build.directory}:项目构建输出目录,默认为target/
    • ${project.outputDirectory}:项目主代码编译输出目录,默认为target/classes/

    • ${project.testOutputDirectory}:项目测试代码编译输出目录,默认为target/test-classes/

    • ${project.basedir}: 项目的根目录,与${basedir}等价。
    • ${project.groupId}:项目的groupId
    • ${project.artifactId}:项目的artifactId
    • ${project.version}:项目的version,与${version}等价
    • ${project.build.finalName}:项目打包输出文件的名称,默认为${project.artifactId}-${project.version}。

  • 自定义属性:POM文件的<properties>属性中定义的Maven属性。
  • settings属性:与POM属性同理,用户使用以settings.开头的属性引用settings.xml文件中XML元素的值,如常用的${settings.localRepository}指向用户本地仓库的地址
  • Java系统属性:Maven可以引用所有的Java系统属性,如${user.home}指向了用户目录。可以使用mvn help:system查看所有的Java系统属性。
  • 环境变量属性:所有环境变量都可以使用以env.开头的Maven属性引用。例如${env.JAVA_HOME}指代了JAVA_HOME环境变量的值。用户可以使用mvn help:system查看所有的环境变量。

※,使用profile 开启资源过滤(所谓资源过滤就是在非pom文件中使用pom中定义的变量,当然需要配置一些东西才能这么使用):两者经常配合使用。

profile 是在POM文件或settings.xml文件中配置的一段POM配置,每个profile都有一个ID。当在命令行是使用-P参数指定这个ID时,这个profile中的配置便会生效。

资源过滤是指 在pom文件中配置 主/测试 资源目录(通常主/测试 资源目录只有一个,但Maven运行配置多个,虽然这会破坏POM的约定),同时通过<filtering>true></filtering>开启资源过滤。开启资源过滤后,主/测试 资源目录中的配置文件便可以使用POM文件中配置的变量了。不开启的话,属性文件和POM文件没有任何关系。例如:在pom中配置第二个资源目录/src/main/mysql,并开启资源过滤,其中含有一个MySQL属性配置文件,属性值随着不同的环境不同,如开发、测试、生产环境等

database.jdbc.driverClass=${db.driver}
database.jdbc.connectionUrl=${db.url}
database.jdbc.username=${db.username}
database.jdbc.password=${db.password}

然后在pom中配置一个ID为mysqlDev的profile,如下。

<profiles>
        <profile>
            <id>devMysql</id>
            <properties>
                <db.driver>com.mysql.jdbc.driver</db.driver>
                <db.url>jdbc:mysql://10.8.0.246:3306</db.url>
                <db.username>root</db.username>
                <db.password>eiduo521!</db.password>
            </properties>
        </profile>
    </profiles>

<build>
        <!--主资源目录可以配置多个并为每个资源目录提供不同的过滤配置,虽然这会破坏Maven约定。-->
        <!--资源目录下的资源文件打包后会被放在BOOT-INF\classes文件夹下, resources标签下可以配置includes和excludes标签(可以一起用)用于
        说明此资源目录下哪些资源文件需要被打包(或被排除)到BOOT-INT\classes目录下-->
        <resources>
            <!--配置主资源目录 并开启资源过滤(开启资源过滤后主资源目录中便可以使用POM文件中配置的变量了)-->
            <resource>
                <directory>${basedir}/src/main/resources</directory>
                <!--<directory>${project.basedir}/src/main/resources</directory>-->
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>${project.basedir}/src/main/mysql</directory>
                <filtering>true</filtering>
                <includes>
                    <!--只有*.xml文件会被视为资源文件,打包到BOOT-INT\classes文件中-->
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>${project.basedir}/src/test/resources</directory>
                <filtering>false</filtering>
            </testResource>
        </testResources>
</build>

 

然后运行·mvn clean compile -P mysqlDev`便会将资源目录src/main/msyql中的属性文件中的变量替换掉。总结下,能够替换掉的原因主要由3个:

  • 通过<resource>标签配置了一个新的主资源目录
  • 通过<filtering>开启了过滤,开启了过滤的意思是指:资源文件中的占位符变量可以使用pom中的变量值替换掉,即资源文件中的代码就像直接位于pom中一样可以直接使用pom中定义的变量。
  • 命令行通过 -P mysqlDev 激活了<profile>标签中的配置。也就是说,在前面两个标签已经配置好的情况下,如果这些变量值不配置在<profile>标签中,而是直接配置在pom的<properties>标签中,那么便不需要用-P激活profile了,直接 mvn clean compile就可以将资源文件中的变量替换掉。

还有一个插件 maven-assembly-plugin【参见本文其他处对此插件的详细描述】,也可以实现资源过滤,并且不限于资源文件。在maven-assembly-plugin的描述符文件中可以指定打包的文件并对这些文件是否开启过滤进行配置。这个插件作用是将工程打成zip包,service工程和web工程都可以使用此插件。另外pom.xml配置项 <packaging>war</packaging> 决定了打成jar包还是war包。

工作项目中service工程中,src/main/bin目录下的startup.sh中就用到了pom.xml中定义的变量{service.mem}。具体的配置是通过插件 maven-assembly-plugin 的配置文件 src/main/assemble/package.xml 定义的。这个文件把src/main/bin目录开启了资源过滤

 聊聊 SpringBoot 中的两种占位符:@*@ 和 ${*}:https://www.cnblogs.com/xiaoxi666/p/15676529.html

※,Maven Profile专题profile能够在构建的时候修改POM的一个子集,或者添加额外的配置元素。用户可以使用很多方式激活profile,以实现构建在不同环境下的移植。

1. 激活方式如下:

  • 命令行激活:用户可以使用mvn命令行参数-P加上profile的id来激活profile,多个id之间以逗号分隔。
  • settings文件显式激活:如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的active-Profiles元素,表示其配置的profile对于所有项目都处于激活状态。
  • 系统属性激活:当某系统属性存在的时候(mvn help:system查看系统属性),自动激活profile,还可以进一步配置当某系统属性<propName>存在且值等于<propValue>时激活此profile。pom代码如下
            <profile>
                <id>myProfile</id>
                <activation>
                    <property>
                        <name>propName</name>
                        <!--value可以不配置,不配置表示只要属性存在就激活,如 `mvn clean install -D propName` -->
                        <value>propValue</value>
                    </property>
                </activation>
            </profile>

    在命令行通过-D参数可以声明系统属性,如·mvn clean install -D propName=propValue·  多个profile完全可以使用同一个系统属性来激活。

  • 操作系统环境激活:如果构建在不同的操作系统有差异,用户完全可以将这些差异写进profile,然后配置它们自动基于操作系统环境激活。
            <profile>
                <id>myProfile</id>
                <activation>
                    <os>
                        <name>windows 10</name>
                        <!--这里family的值包括Windows、UNIX和Mac等-->
                        <family>Windows</family>
                        <arch>x86_64</arch>
                        <version>x.x.x.xx</version>
                    </os>
                </activation>
            </profile>
  • 文件存在与否激活:Maven能够根据项目中某个文件存在与否来决定是否激活profile。
            <profile>
                <id>myProfile</id>
                <activation>
                    <file>
                        <missing>fileName</missing>
                        <exists>anotherFileName</exists>
                    </file>
                </activation>
            </profile>
  • 默认激活:用户可以在定义profile的时候指定其默认激活。不过需要注意:老版本的Maven如果POM中有任何一个profile通过以上其他任意一种方式被激活了,所有的默认激活配置都会失效,新版Maven3.6.3已经不存在这个问题了。
            <profile>
                <id>myProfile</id>
                <activation>
                    <activeByDefault>true</activeByDefault>
                </activation>
            </profile>

2. 查看 Profile 以及是否激活

  • ·mvn help:all-profiles` // 列出所有的 profile。还会显示每个profile是否激活。
  • ·mvn help:active-profiles`  // 列出所有激活的profile。
  • ·mvn help:all-profiles -D propName=propValue· //测试

3. profile的种类:根据具体的需要,可以在以下位置声明profile。

  • pom.xml:pom.xml中声明的profile只对当前项目有效。
  • 用户settings.xml:用户目录下.m2/settings.xml中的profile对本机上该用户所有的Maven项目有效。
  • 全局settings.xml:Maven安装目录下conf/settings.xml中的profile对本机上所有的Maven项目有效。

不同位置的profile可以使用的POM元素也不同。pom.xml中的profile可以修改或者增加很多POM元素。另外两种外部profile则只能使用少数几个元素(为了避免构建的移植性问题),如下

<project> //书中是不是写错了,应该是<profile>???
<repositories></repositories>
<pluginRepositories></pluginRepositories>
<properties></properties>
</project>

4,Web资源过滤

Java Web项目中,除了通常的资源文件(即位于/src/main/resources目录下,这类资源文件打成war包后位于WEB-INF/classes中,也就是说,这类资源文件在打包过后位于应用程序的classpath中),还有另外一类资源文件,,默认它们的源码位于 src/main/webapp目录,经打包后位于WAR包的根目录。这类资源文件称作web资源文件,它们在打包后不位于应用程序的classpath中。

web资源文件默认也不开启过滤,如果想过滤web资源文件,需要通过maven-war-plugin的配置完成。(普通的资源过滤开启方法不适合web资源文件!),具体配置如下

            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <webResources>
                        <resource>
                            <filtering>true</filtering>
                            <directory>src/main/webapp</directory>
                            <!--使用 includes 指定要过滤的文件-->
                            <includes>
                                <include>*.properties</include>
                                <include>**/*.css</include>
                                <include>**/*.js</include>
                            </includes>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>

·mvn clean package -P client-a· //激活名称为client-a的profile。

5. 在profile中激活集成测试(略)

※,

卍,生成项目站点:即介绍项目相关信息的网页显示

※,Maven不仅仅是一个自动化构建工具和一个依赖管理工具,它还能够帮助聚合项目信息,促进团队间的交流。POM可以包含各种项目信息,如项目描述、版本控制系统地址、缺陷跟踪系统地址、许可证信息、开发者信息等。用户可以让Maven自动生成一个Web站点,以Web的形式发布这些信息。

此外,Maven社区提供了大量插件,能让用户生成各种各样的项目审查报告,包括测试覆盖率、静态代码分析、代码变更等。本章详细介绍如何生成Maven站点,以及如何配置各种插件生成项目报告。

※,最简单的站点信息生成:配置  maven-site-plugin,注意不要使用IDEA自动推荐的3.3版本的 maven-site-plugin,有bug。使用目前最新版本3.9.1版本。

<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-site-plugin -->
<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-site-plugin</artifactId>
    <version>3.9.1</version>
</dependency>

配置好之后便可以运行 mvn site 生成站点了,生成后的站点位于 项目的target/site/目录下。

如果这是一个聚合项目,导航栏的上方还会包含子模块的链接。但是如果单击这些链接,将无法转到子模块的项目页面。这是由于多模块Maven项目本身的目录结构导致的。如果将站点发布到服务器上,该问题会自然消失。如果想在本地查看结构正确的站点,则可以maven-site-plugin的stage目标,将站点预发布至某个本地临时目录下:·mvn site:stage -D stagingDirectory=D:\tmp·,这样本地的站点的父子链接也可用了。

※,在默认情况下Maven生成的站点包含了很多项目信息链接,这其实是由一个名为maven-project-info-reports-plugin的插件生成的。在Maven 3中,该插件的配置内置在maven-site-plugin中。该插件会基于POM配置生成项目信息报告。有些时候,用户可能不需要生成某些项目信息项,例如你可能没有邮件列表或者不想在站点中公开源码仓库信息,这时可以配置maven-project-info-reports-plugin选择性地生成信息项。需要注意的是,项目报告插件需要在reporting元素下的plugins元素下进行配置。除了默认的项目报告插件之外,还有一些其他的项目报告插件。其他的报告插件也是在<reporting><plugins>元素下配置。

    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
                <version>3.1.1</version>
                <!--配置后,项目的站点信息只包含依赖和scm信息-->
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>index</report><!--配置一个默认页-->
                            <report>dependencies</report>
                            <report>scm</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>

            <!--javadoc-->
            <plugin>
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.2.0</version>
            </plugin>

            <!--查看源码-->
            <plugin>
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <artifactId>maven-jxr-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <!--在聚合模块整合所有的源码,配置为true-->
                    <aggregate>true</aggregate>
                </configuration>
            </plugin>

            <!--检查代码规范:http://checkstyle.sourceforge.net/-->
            <plugin>
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.1.1</version>
                <configuration>
                    <!--配置使用不同的检查规则,配置之后会报错,待研究-->
                    <!--<configLocation>config/maven_checks.xml</configLocation>-->
                </configuration>
            </plugin>

            <!--Java源代码分析工具:http://pmd.sourceforge.net/-->
            <plugin>
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <artifactId>maven-pmd-plugin</artifactId>
                <version>3.14.0</version>
                <configuration>
                    <!--配置为true以支持聚合模块-->
                    <aggregate>true</aggregate>

                    <!--配置maven-pmd-plugin使用非默认分析规则,配置之后会报错,待研究-->
                    <!--<rulesets>-->
                    <!--    <ruleset>rulesets/braces.xml</ruleset>-->
                    <!--    <ruleset>rulesets/naming.xml</ruleset>-->
                    <!--    <ruleset>rulesets/strings.xml</ruleset>-->
                    <!--</rulesets>-->
                </configuration>
            </plugin>

            <!--仓库代码提交变更-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-changelog-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <type>range</type>
                    <range>60</range>
                </configuration>
            </plugin>

            <!--测试覆盖率-->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.4</version>
            </plugin>
        </plugins>
    </reporting>

※,其他项目报告插件的使用,具体POM配置代码见上面。

  • maven-javadoc-plugin:配置后将会生成javadoc文档。样式就和Java 官方api网站一样。

  • maven-jxr-plugin:Source Xref。在生成站点的时候配置该插件,Maven就会以Web页面的形式将Java源代码展现出来。
  • maven-checkstyle-plugin:CheckStyle是一个用来帮助Java开发人员遵循编码规范的工具,能根据一套规则自动检查Java代码,使得团队能够方便地定义自己的编码规范。【另外,Intellij IDEA有个阿里巴巴Java开发规范的插件.默认情况下】maven-checkstyle-plugin会使用Sun定义的编码规范,读者能够选择其他预置的规则。也可以自定义规则,maven-checkstyle-plugin内置了四种规则

    1. config/sun_checks.xml:Sun定义的编码规范(默认值)
    2. config/maven_checks.xml:Maven社区定义的编码规范
    3. config/turbine_checks.xml:Turbine定义的编码规范
    4. config/avalon_checks.xml:Avalon定义的编码规范。
    • 通常用户所在的组织会有自己的编码规范,这时就需要创建自己的checkstyle规则文件。如在src/main/resources/目录下定义一个checkstyle/my_checks.xml文件,然后配置<configLocation>checkstyle/my_checks.xml</configLocation>即可。maven-checkstyle-plugin实际上是从ClassPath载入规则文件,因此对于它来说,无论规则文件是在当前项目中还是在依赖文件中,处理方式都是一样的。

  • maven-pmd-plugin:PMD是一款强大的Java源代码分析工具,它能够寻找代码中的问题,包括潜在的bug、无用代码、可优化代码、重复代码以及过于复杂的表达式。需要注意的是,除了PMD报告之外,maven-pmd-plugin还会生成一个名为CPD的报告,该报告中包含了代码拷贝粘贴的分析结果。PMD默认使用的规则为rulesets/basic.xml、rulesets/unusedcode.xml和rulesets/importss.xml。要使用其他的规则,可以配置maven-pmd-plugin插件。

  • maven-changelog-plugin:能够基于版本控制系统中就近的变更记录生成三份变更报告,它们分别为:
    • Change Log:基于提交的变更报告,包括每次提交的日期、文件、作者、注释等信息。
    • Developer Activity:基于作者的变更报告,包括作者列表以及每个作者相关的提交次数和涉及文件数目。
    • File Activity:基于文件的变更报告,包括变更的文件列表及每个文件的变更次数。

注意首先需要正确配置scm元素。默认情况下,maven-changelog-plugin生成最近30天的变更记录,可以通过配置更改。

  • cobertura-maven-plugin:在Maven站点中包含Cobertura测试覆盖率报告。

※,自定义网站外观。略

※,创建自定义页面。略

※,国际化。生成支持英语以外的其他语言的项目报告站点。

※,部署站点。为了方便团队和用户得到必要的项目信息,我们需要将Maven站点部署到服务器上。Maven支持多种协议部署站点,包括FTP、SCP和DAV。部署站点需要在 <distributionManagement>元素中配置(和部署构件使用的是同一个元素),代码如下。

    <distributionManagement>
        <site>
            <id>myWebApp-site</id>
            <url>dav:https://www.xxxx.com/sites/app</url>
        </site>
    </distributionManagement>

url的值以dav开头,表示服务器必须支持WEBDAV。此外,为了确保安全性,服务器的访问一般都需要认证。这个时候就需要配置settings.xml文件的server元素,这一点与部署构件至Maven仓库类似。需要注意的是:要确保server的id值与site的id值完全一致。站点部署地址及认证信息配置完成后,只需要输入·mvn clean site-deploy·命令就能让Maven部署站点了。site-deploy是site生命周期的一个阶段,其对应绑定了maven-site-plugin的deploy目标,该目标的工作就是部署Maven项目信息站点。

卍,m2eclipse插件:m2eclipse就是一个在Eclipse中集成Maven的插件,有了该插件,用户可以方便地在Eclipse中执行Maven命令、创建Maven项目、修改POM文件等

卍,编写Maven插件

※,大量的Maven插件可以从Aapche【http://maven.apache.org/plugins/index.html】和Codehaus 【貌似已废弃了】 获得,这里的近百个插件几乎能够满足所有Maven项目的需要。除此之外,还有很多Maven插件分布在Googlecode、Sourceforge、Github等项目托管服务中.

※,编写Maven插件的一般步骤:

1)创建一个maven-plugin项目:插件本身也是Maven项目,特殊的地方在于它的packaging必须是maven-plugin,这种特殊的打包类型能控制Maven为其在生命周期阶段绑定插件处理相关的目标,例如在compile阶段,Maven需要为插件项目构建一个特殊插件描述符文件。 用户可以使用maven-archetype-plugin快速创建一个Maven插件项目。

2)为插件编写目标:每个插件都必须包含一个或者多个目标,Maven称之为Mojo(与POJO对应,后者指Plain Old Java Object,这里指Maven Old Java Object)。编写插件的时候必须提供一个或者多个继承自AbstractMojo的类。
3)为目标提供配置点:大部分Maven插件及其目标都是可配置的,因此在编写Mojo的时候需要注意提供可配置的参数。
4)编写代码实现目标行为:根据实际的需要实现Mojo。
5)错误处理及日志:当Mojo发生异常时,根据情况控制Maven的运行状态。在代码中编写必要的日志以便为用户提供足够的信息。
6)测试插件:编写自动化的测试代码测试行为,然后再实际运行插件以验证其行为。

※,编写一个用于代码行统计的Maven插件【插件实战】

1.  使用 maven-archetype-plugin 生成一个Maven插件项目:`mvn archetype:generate`,选择序号为1293的 maven-archetype-plugin (An archetype which contains a sample Maven plugin.),最新版本为1.4(即使是最新的1.4也很老了,生成后还需要手动改pom文件中的<properties>元素中关于JDK和Maven版本的配置)。生成的pom文件中有一个artifactId为 maven-plugin-api 的依赖(必须的依赖),该依赖中包含了插件开发所必需的类,比如AbstractMojo类。

2. 自定义插件的命名:

  • 插件的artifactId的命名最好符合官方格式:xxxx-maven-plugin,否则以简写运行时,简写名称容易错。
  • maven-___-plugin 这种插件是预留给官方 org.apache.maven.plugins 组下的,自定义插件不要用这种形式。

3,代码示例:

package com.tong.java;

import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Goal which counts lines of code of a project
 * 注意:老版Maven是在注释中注解goal,新版必须使用Mojo注解指名goal。
 */
@Mojo(name = "countlines", defaultPhase = LifecyclePhase.COMPILE)
public class CountMojo extends AbstractMojo {
    private static final String[] INCLUDES_DEFAULTS = {"java", "xml", "properties"};

    //@parameter注解的参数可以在 pom文件中配置
    @Parameter(defaultValue = "${project.basedir}")
    private File baseDir;

    @Parameter(defaultValue = "${project.build.sourceDirectory}")
    private File sourceDirectory;

    @Parameter(defaultValue = "${project.build.testSourceDirectory}")
    private File testSourceDirectory;

    @Parameter(defaultValue = "${project.build.resources}", property = "outputDir")
    private List<Resource> resources;

    @Parameter(defaultValue = "${project.build.testResources}")
    private List<Resource> testResources;

    private String[] includes;

    public void execute() throws MojoExecutionException {
        getLog().info("********TONUS-开始执行**************");
        if (includes == null || includes.length == 0) {
            includes = INCLUDES_DEFAULTS;
        }
        try {
            countDir(sourceDirectory);
            countDir(testSourceDirectory);
            for (Resource resource : resources) {
                countDir(new File(resource.getDirectory()));
            }
            for (Resource testResource : testResources) {
                countDir(new File(testResource.getDirectory()));
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Unable to count lines of code.", e);
        }
    }

    private void countDir(File dir) throws IOException {
        if (!dir.exists()) return;
        List<File> collected = new ArrayList<>();
        collectFiles(collected, dir);
        int lines = 0;
        for (File sourceFile : collected) {
            lines += countLine(sourceFile);
        }
        String path = dir.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
        getLog().info(path + ": " + lines + " lines of code in " + collected.size() + " files");
    }

    private void collectFiles(List<File> collected, File file) {
        if (file.isFile()) {
            for (String include : includes) {
                if (file.getName().endsWith("." + include)) {
                    collected.add(file);
                    break;
                }
            }
        } else {
            // 文件夹
            for (File sub : file.listFiles()) {
                collectFiles(collected, sub);
            }
        }
    }

    private int countLine(File file) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        int line = 0;
        try {
            while (reader.ready()) {
                reader.readLine();
                line++;
            }
        } finally {
            reader.close();
        }

        return line;
    }

}
View Code

4,

※,使用自定义插件

1,编写好插件后,运行 mvn install 将插件构件部署至本地仓库,本地便可使用此插件了。

  • 如果是通过Maven命令两种运行方式之一的插件方式调用此插件,那么在任何一个Maven项目中,无需在pom中配置(当然如果有参数需要配置也可以配置)便都可通过以下命令运行此插件:`mvn com.tong.java:maven-tong-plugin[:1.0.0-SNAPSHOT]:<mygoal>`。(全格式版本号可省略)。也可以通过插件简称(前缀)运行,自定义插件如果使用简写需要在settings.xml中配置组名。<pluginGroup>com.tong.java</pluginGroup>。然后运行·mvn tong:<mygoal>`。插件的简称可以在本地仓库的组目录下( com.tong.java/maven-metadata-local.xml )查看。
  • 如果是通过生命周期调用此插件,那么需要在调用此插件的Maven项目的pom文件中配置此插件(在<build><plugins><plugin>元素下配置),将其绑定至某个生命周期阶段,然后通过生命周期阶段调用。

2,修改代码后要clean install再运行插件命令,这样才是修改后的代码对应的效果,否则还是上次install的代码对应的效果。

※,

卍,Archetype:可以将Archetype理解成Maven项目的模板。很多著名的开源项目(如AppFuse和Apache Wicket)都提供了Archetype方便用户快速创建项目。如果你所在组织的项目都遵循一些通用的配置及结构,则也可以为其创建一个自己的Archetype并进行维护。使用Archetype不仅能让用户快速简单地创建项目;还可以鼓励大家遵循一些项目结构及配置约定。

※,archetype的两种使用方式:交互式 和 脚本式。

※,各种archetype介绍。

※,编写自己的archetype。

※,其他关于archetype的知识。

卍,POM 元素参考

卍,settings.xml中元素参考

卍,常用插件列表

卍,Maven平时记录

  • IDEA中将未识别的Maven工程标记为Maven工程:在工程目录上右键,然后选择 Add Framework Support... 即可。
  • Maven插件使用Java开发的,有些插件的版本需要特定的JDK版本。比如JDK15和maven-surefire-plugin:2.20.1 不兼容!!!!
  • `mvn help:effective-settings`  // 检查当前Maven环境启用的文件的配置
  • ·mvn help:effective-pom·  // 查看当前项目的pom配置,包括所有依赖
  • ·mvn help:active-profiles· // 查看当前处于激活状态的profile
  • `mvn -s ~/.m2/settings_local.xml clean deploy` // 指定使用某个配置文件执行Maven命令
  •  

posted on 2020-12-26 23:34  everest33  阅读(110)  评论(0编辑  收藏  举报