阅读 2610

Maven 实战小结

思维导图

maven

Maven 与构建

什么是 Maven

翻译:知识的积累、专家、内行。跨平台的项目管理工具。Apache 组织的开源项目。主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。

类似于 linux 平台的 yum、apt,前端领域的 npm。Maven 前身为 Ant 目前 tomcat 的源码就是用 Ant 来构建和管理,更先进的工具有 Gradle,Spring 工程在用。

什么是构建

何为构建:编译、运行单元测试、生成文档、打包、部署的过程,这就是构建。

假设我们现在没有 Maven,编译、运行单测、打包等这些都是独立的步骤,必须手工将这些动作整合起来。而有 Maven 之后,我们要做的是使用 Maven 配置好项目,然后输入简单的命令(mvn clean、mvn package 等或者直接点击按钮),使得应用的构建可以像全自动流水线一样,所有繁琐的步骤都能完成。

Maven 是优秀的构建工具,又不仅仅是构建工具:

  • 管理三方类库:提供坐标系统引入依赖(输入坐标自动下载依赖),并自动解决复杂的依赖问题(版本不一致、版本冲突、依赖臃肿等)、并面向所有的开发人员提供了免费的依赖中央仓库。
  • 管理原本分散在各个地方的项目信息,包括项目描述、开发人员、许可证等等。
  • 插件机制。开发人员能够自定义插件能够实现复杂的构建需求。

到这里为止,应该对 Maven 有个初步的印象了。下面来看看怎么使用 Maven。

项目骨架

要使用 Maven 构建项目,必须遵循一些 Maven 的约定,下面就是一个标准的 Maven 项目 骨架

pom:Project Object Model

根目录:工程名
|---src:源码
|---|---main:主程序
|---|---|---java:主程序代码路径
|---|---|---resource:主程序配置文件路径
|---|---test:测试
|---|---|---java:测试代码路径
|---|---|---resource:测试配置文件路径
|---pom.xml:maven 配置文件
复制代码

简单演示

## 1. 使用 archetype 命令生成 maven 简单骨架
mvn archetype:generate -DarchetypeCatalog=internal

## 2. 编译当前生成的项目
mvn compile

## 3. 使用其他命令
mvn test-compile  
mvn package  
mvn clean 
mvn install
mvn depoly 暂时不演示
复制代码

坐标与依赖

什么是坐标

类比为数学中平面几何,坐标(x、y ),任何一个坐标都能唯一标识该平面中的一个点。该点对应到 maven 就是 .jar、.war 等文件的文件。Maven 使用 groupId、artifactId、version、packaging、classifier 等元素来组成自己的坐标,并定义一组这样的规则,只要能提供正确坐标元素 Maven 就能找到对应的构件。

坐标元素:

  • groupId:定义当前 Maven 项目隶属的实际项目。
  • artifactId:定义实际项目中的一个 Maven 项目(模块)。
  • packaging:定义 Maven 项目打包方式。jar、war、pom。默认为 jar。
  • version:定义 Maven 项目当前所处的版本。
  • classifier:区分从同一 artifact 构建的具有不同内容的构件。

classifier 使用场景

  • 区分基于不同 JDK 版本的包
<dependency>  
    <groupId>net.sf.json-lib</groupId>   
    <artifactId>json-lib</artifactId>   
    <version>2.2.2</version>  
    <classifier>jdk13</classifier>    
    <!--<classifier>jdk15</classifier>-->
</dependency> 

复制代码
  • 区分项目的不同组成部分
<dependency>  
    <groupId>net.sf.json-lib</groupId>   
    <artifactId>json-lib</artifactId>   
    <version>2.2.2</version>  
    <classifier>jdk15-javadoc</classifier>    
    <!--<classifier>jdk15-sources</classifier>  -->
</dependency>
复制代码

构件名与坐标是对应的,一般规则是:artifactId-version[-classifier].packaging。

依赖声明

<dependencies>
    <dependency>
        <groupId></groupId>
        <artifactId></artifactId>
        <version></version>
        <type></type>
        <optional></optional>
        <exclusions>
            <exclusion>
                <artifactId></artifactId>
                <groupId></groupId>
            </exclusion>
            ...
        </exclusions>
    </dependency>
    ...
</dependencies>
复制代码
  • groupId、artifactId、version:依赖的基本坐标。
  • type:依赖的类型,对应项目对应的 packaging,一般不必声明。
  • scope:依赖的范围,后面详解。
  • optional:标记依赖是否可选。
  • exclusions:用来排除传递性依赖。

依赖范围

  • compile:编译依赖范围

如果没有指定,默认使用该依赖范围。对于编译、测试、运行三种 classpath 都有效。如:spring-core。

  • test:测试依赖范围

只对于测试 classpath 有效,只需要在编译测试及运行测试才需要,在打包的时候不会打进去。如:JUnit。

  • provided:已提供依赖范围

对于编译和测试 classpath 有效,但运行时无效。如:servlet-api 编译和测试项目的时候都需要,但在实际运行中,容器已经提供,不需要 maven 重复的引用。

  • runtime:运行时依赖范围

对于测试和运行的 classpath 有效,但在编译主代码时无效。如:JDBC 驱动的实现包。只有在执行测试或者运行项目时,才需要具体的 JDBC 驱动。

  • system:系统依赖范围

与 provided 依赖范围完全一致,但是使用该范围时必须通过 systemPath 元素显式地指定依赖文件的路径。由于此类依赖不是通过 maven 仓库解析的,而且往往与本机系统绑定,可能造成构建不可移植,因此应该谨慎使用。systemPath 元素可以引用环境变量,如:

  <dependencies>
    <dependency>
      <groupId>javax.sql</groupId>
      <artifactId>jdbc-stdxt</artifactId>
      <version>2.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
  </dependencies>
复制代码
  • import:导入依赖范围

只在 dependencyManagement 标签中生效,导入已经定义好的 pom 文件中 dependencyManagement 节点内容

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.3.16.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
复制代码

依赖机制与特性

依赖传递

A->B(compile):第一直接依赖
B->C(compile):第二直接依赖
A->C(compile):传递性依赖

当在A中配置

<dependency>  
    <groupId>com.B</groupId>  
    <artifactId>B</artifactId>  
    <version>1.0</version>  
</dependency>
复制代码

则会自动导入 C 包。

传递性依赖的范围如下图所示:

传递性依赖

依赖调解

当传递性依赖出现问题时,能够清楚地知道该传递性依赖是从哪条依赖路径中引入的。

  1. 路径最近者优先原则
  • A->B->C->X(1.0)
  • A->D->X(2.0)

由于只能导入一个版本的包,按照最短路径选择导入 X(2.0)

  1. 第一声明者优先原则
  • A->B->Y(1.0)
  • A->C->Y(2.0)

此时由于依赖路径长度一致,按照第一声明者优先原则。在路径长度一致的前提下,如果 B 依赖在 POM 文件中声明顺序在 C 依赖之前,那么 Y(1.0) 则会被引入。如下依赖可用于测试:

<dependencies>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.4.1</version>
      <exclusions>
        <exclusion>
          <groupId>commons-codec</groupId>
          <artifactId>commons-codec</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.9</version>
      <exclusions>
        <exclusion>
          <groupId>commons-codec</groupId>
          <artifactId>commons-codec</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.10</version>
    </dependency>

</dependencies>
复制代码

这里有一点需要特别注意,看如下依赖:

<dependencies>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.11</version>
    </dependency>

    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.10</version>
    </dependency>
</dependencies>
复制代码

按照两原则,期望得到的结果应该是 1.11 版本的构建将被依赖。但实际结果却依赖了 1.10 版本。what!这不是违反了 maven 依赖调解的最先定义原则? 网友的相似疑问:stackoverflow.com/questions/4…....

其实这个是 dependency 插件的功能,默认采用的是复写的策略,当构建声明处于同一 pom 中,且 groupid 和 artifactId 一致时,以 最新声明为准,后面的覆盖前面的。注意这里没涉及到依赖调解的功能。我的理解是 依赖调解只发生于构建来自不同 pom 时,而此时构建声明处于同一 pom,故不会触发依赖调解。

参考:maven.apache.org/plugins/mav…

可选依赖

A->B、B->X(可选)、B->Y(可选)。项目 A 依赖于项目 B,项目 B 依赖于项目 X 和 Y。理论上项目 A 中,会把 B、X、Y 项目都依赖进来。但是 X、Y 两个依赖对于 B 来讲可能是互斥的,如 B 是数据库隔离包,支持多种数据库 MySQL、Oracle,在构建 B 项目时,需要这两种数据库的支持,但在使用这个工具包时,只会依赖一个数据库。此时就需要在 B 项目 pom 文件中将 X、Y 声明为可选依赖,如下:

<dependency>  
    <groupId>com.X</groupId>  
    <artifactId>X</artifactId>  
    <version>1.0</version>  
    <optionnal>true</optionnal>
</dependency>

<dependency>  
    <groupId>com.Y</groupId>  
    <artifactId>Y</artifactId>  
    <version>1.0</version>  
    <optionnal>true</optionnal>
</dependency>
复制代码

使用 optionnal 元素标识以后,只会对当前项目 B 产生影响,当其他的项目依赖 B 项目时,这两个依赖都不会被传递。项目 A 依赖于项目 B,如果实际应用数据库是 X, 则在 A 的 pom 中就需要显式地声明 X 依赖。

仓库

存放构件(依赖)的地方就叫仓库,用于统一管理。

假设现在有很多个项目都引用了 spring-core 依赖,并不是说每个项目文件夹下都需要放一份该依赖文件。Maven 的做法是统一管理。实际的 Maven 项目不需要各自存储依赖文件,只需要声明这些依赖的坐标,当需要的时候 Maven 会自动根据坐标找到仓库中的构件,并使用。

为了实现复用,比如此时自己有封装好的 jar(比如融合付的银行 sdk 封装),也可以安装或者部署到仓库中,与其他的项目进行共享。

仓库分为本地仓库和远程仓库。其中远程仓库包括:私服和中央仓库。

当声明了一个依赖时,Maven 搜索构建的顺序:

  1. 本地仓库
  2. maven settings profile 中的 repository;
  3. pom.xml 中 profile 中定义的 repository;
  4. pom.xml 中 repositorys (按定义顺序找);
  5. maven settings mirror;
  6. central 中央仓库;

生命周期与插件

Maven 的生命周期是为了对所有构建过程进行的抽象和统一,其中包含项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有的构建步骤。

Maven 的生命周期是抽象的,本身是不做任何实际的工作。实际的任务都交给 插件 来完成。意味着 Maven 只在父类中定义了算法的整体结构,子类通过重写父类的方法,来控制实际行为(设计模式中的 模板方法 Template Method)。伪代码如下:

public abstract class AbstractBuilder {
    public void build() {
        init();
        compile();
        test();
        package();
        integrationTest();
        deploy();
    }
    
    protected abstract void init();
    protected abstract void compile();
    protected abstract void test();
    protected abstract void package();
    protected abstract void integrationTest();
    protected abstract void deploy();
}
复制代码

三套生命周期

Maven 的生命周期并不是一个整体,Maven 拥有三套相互独立的生命周期,它们分别为 clean、default 和 site。

  • clean 生命周期的目的是清理项目;
  • default 生命周期的目的是构建项目;
  • site 生命周期的目的是建立项目站点;

单个生命周期执行顺序

每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。以 clean 生命周期为例,它包含的阶段有 pre-clean、clean和post-clean。当调用 pre-clean 时,只有 pre-clean 阶段得以执行;当调用 clean 的时候,pre-clean和clean阶段会得以顺序执行,以此类推。

各个生命周期之间的关系

三套生命周期本身是相互独立的,用户可以仅调用 clean 生命周期的某个阶段,或者仅仅调用 default 生命周期的某个阶段,而不会对其他生命周期产生任何影响。例如,当用户调用 clean 生命周期的 clean 阶段的时候,不会触发 default 生命周期的任何阶段,反之亦然。

注:所以一般的打包命令都是 clean package xxxxx,先 clean 再 package,就是因为 clean 和 package 是在两个不同的生命周期里面。

生命周期各个阶段详解

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

default

包含 23 个阶段,此处只介绍重点步骤,如下表:

生命周期阶段描述
validate检查工程配置是否正确,完成构建过程的所有必要信息是否能够获取到。
initialize初始化构建状态,例如设置属性。
generate-sources
process-sources处理项目资源文件,处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
generate-resources
process-resources
compile编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
process-classes处理编译生成的文件,例如 Java Class 字节码的加强和优化。
generate-test-sources
process-test-sources处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
test-compile编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
process-test-classes
test使用适当的单元测试框架(例如JUnit)运行测试。
prepare-package在真正打包之前,为准备打包执行任何必要的操作。
package获取编译后的代码,并按照可发布的格式进行打包,例如 JAR、WAR 或者 EAR 文件。
pre-integration-test在集成测试执行之前,执行所需的操作。例如,设置所需的环境变量。
integration-test处理和部署必须的工程包到集成测试能够运行的环境中。
post-integration-test在集成测试被执行后执行必要的操作。例如,清理环境。
verify运行检查操作来验证工程包是有效的,并满足质量要求。
install安装工程包到本地仓库中,该仓库可以作为本地其他工程的依赖。
deploy拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程。

site

生命周期阶段描述
pre-site执行一些在生成项目站点之前需要完成的工作。
site生成项目站点文档。
post-site执行一些在生成项目站点之后需要完成的工作。
site-deploy将生成的项目站点发布到服务器上。

插件

Maven 三套生命周期定义各个阶段不做任何实际工作,实际工作都是由插件来完成的,每个生命周期阶段都是由插件的目标来完成。在 pom 文件中声明如下(打包源码文件插件):

<build>
    <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>
</build>
复制代码

插件目标

一个插件有可能有多个功能、每个功能就是一个目标。比如 maven-dependency-plugin 有十多个目标,每个目标对应了一个功能。插件的目标为 dependency:analyze、dependency:tree和dependency:list。通用写法:冒号前面是插件前缀,冒号后面是插件的目标。比如 compiler:compile。

插件目标

插件绑定

插件绑定

内置绑定

为实现快速构建,Maven 有一套内置的插件绑定。三套生命周期的插件绑定具体如下(其实是各个生命周期阶段与插件的目标的绑定)。其中 default 生命周期的构建方式会其打包类型有关、打包类型在POM中 packaging 指定。一般有 jar、war 两种类型。下面是默认绑定插件与生命周期关系图:

关系

自定义绑定

自定义绑定允许我们自己掌控插件目标与生命周期的结合.以生成项目主代码的源码 jar 为例。使用到的插件和它的目标为:maven-source-plugin:jar-no-fork。将其绑定到 default 生命周期阶段 verify 上(可以任意指定三套生命周期的任意阶段)。

<build>
    <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>
</build>
复制代码

插件配置

  • 使用命令行配置

在 maven 命令中加入 -D 参数,并伴随一个参数键=参数值的形式,来配置插件目标参数。如:maven-surefire-plugin 插件提供一个 maven.test.skip 参数,当值为 true 时会跳过执行测试:

 -- 对比 mvn install
mvn install –Dmaven.test.skip=true
复制代码
  • 使用 pom 全局配置

在声明插件的时候,对插件进行一个全局配置,后面所有使用该插件的都要遵循这个配置。比如指定 maven-compile-plugin 编译 1.7 版本的源文件:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <configuration>
       <fork>true</fork>
       <source>1.7</source>
       <target>1.7</target>
   </configuration>
</plugin>
复制代码
  • 获取插件信息

官方网站

maven.apache.org/plugins/ind…
maven.apache.org/plugins-arc…

命令行获取

  • 使用 maven-help-plugin 插件
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin

mvn help:describe -Dplugin=compiler

mvn help:describe -Dplugin=compiler -Dgoal=compile

mvn help:describe -Dplugin=compiler -Ddetail
复制代码
  • 使用插件内置 help 目标
mvn compiler:help -Ddetail=true -Dgoal=compile
复制代码
  • 插件前缀

如果插件的名称命名为 prefixmavenplugin或者maven{prefix}-maven-plugin 或者 maven-{prefix}-plugin 这样的格式的话,可以直接用 mvn prefix:goal 调用。

如何查看插件的前缀:${MAVEN_HOME}\org\apache\maven\plugins\maven-metadata-central.xml

聚合与继承

聚合:为了一次构建多个项目模块,就需要对多个项目模块进行聚合

<modules>
    <module>模块一</module>
    <module>模块二</module>
    <module>模块三</module>
</modules>
复制代码

继承:为了消除重复,把很多相同的配置提取出来,例如:dependency、grouptId,version 等

<parent>  
    <groupId>com.xxxx.maven</groupId>
    <artifactId>parent-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../ParentProject/pom.xml</relativePath>  
</parent>
复制代码

以下的元素是可以被继承的:

groupId,项目组ID;
version,项目版本;
description,项目描述信息;
organazation,项目的组织信息;
inceptionYear,项目的创始年份;
developers,项目开发者信息;
contributors,项目的贡献者信息;
distributionManagement,项目的部署信息;
issueManagement,项目的缺陷跟踪系统信息;
ciManagement,项目的持续集成系统信息;
scm,项目的版本控制系统信息;
mailingLists,项目的邮件列表信息;
properties,自定义的Maven属性;
dependencies,项目的依赖配置;
dependencyManagement,项目的依赖管理配置;
repositories,项目的仓库配置;
build,包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等;
reporting,包括项目的报告输出目录配置、报告插件配置。

注意下面的元素,这些都是不能被继承的。

  • artifactId
  • name
  • prerequisites

聚合与继承之间的关系:

  • 打方式必须都是 pom
  • 在实际的项目中,一个 pom 既是聚合 pom 又是父 pom

注: 父 pom 中使用 dependencies 引入的依赖也会被子 pom 继承,所以不要将过多的实际依赖放在父 pom,父 pom 只用于管理,使用 dependencyManagement 标签。(组员在这踩过坑)

灵活构建

使用属性、 resources 插件资源过滤功能(filter)和 Maven 的 profile 功能,实现环境的灵活切换

属性

maven.apache.org/pom.html#Pr…

通过 properties 元素用户可以自定义一个或者多个 Maven 属性,然后在 pom 其他的地方使用 ${属性名} 的方式引用该属性,这种方式最大意义在于消除重复。

1. 内置属性

  • ${basedir} 表示项目根目录,即包含 pom.xml 文件的目录
  • ${version} 等同于 ${project.version} 或者 ${pom.version} 表示项目版本

2. POM 属性

所有 pom 中的元素都可以用 project. 例如 ${project.artifactId} 对应了 < project><artifactId> 元素的值。常用的 POM 属性包括:

${project.build.sourceDirectory} : 项目的主源码目录,默认为 src/main/java/.
${project.build.testSourceDirectory} : 项目的测试源码目录,默认为 /src/test/java/.
${project.build.directory} : 项目构建输出目录,默认为 target/.
${project.build.outputDirectory} : 项目主代码编译输出目录,默认为 target/classes/.
${project.build.testOutputDirectory} : 项目测试代码编译输出目录,默认为 target/testclasses/.
${project.groupId}: 项目的 groupId.
${project.artifactId} : 项目的 artifactId.
${project.version} : 项目的 version, 等同于 ${version} ${project.build.finalName} : 项目打包输出文件的名称,默认为 ${project.artifactId}${project.version}

3. 自定义属性

在 pom 中 <properties> 元素下自定义的 Maven 属性

<properties>
    <swagger.version>2.2.2</swagger.version>
</properties>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${swagger.version}</version>
</dependency>
复制代码

4. Settings 属性

所有用的的 settings.xml 中的设定都可以通过 settings. 前缀进行引用与 POM 属性同理。如 ${settings.localRepository} 指向用户本地仓库的地址

5. Java 系统属性

所有 Java 系统属性都可以使用 Maven 属性引用,例如 ${user.home} 指向了用户目录。 可以通过命令行 mvn help:system 查看所有的 Java 系统属性

6. 环境变量属性

所有环境变量都可以使用以 env. 开头的 Maven 属性引用。例如 ${env.JAVA_HOME} 指代了 JAVA_HOME 环境变量的值。也可以通过命令行 mvn help:system 查看所有环境变量。

7. 父级工程属性

上级工程的 pom 中的变量用前缀 ${project.parent} 引用. 上级工程的版本也可以这样引用: ${parent.version}

注:如何读取外部 property 文件?
Properties Maven Plugin,github.com/mojohaus/pr…

fileter

Maven 的 properties filter 功能可以帮你自动替换配置文件中以 ${}包裹的变量。为了方便构建不同的环境,我们通常将不同的配置以 properties 形式配置在 pom 中。 默认情况下,Maven属性只有在POM中才会被解析。资源过滤就是指让 Maven 属性在资源文件(src/main/resourcessrc/test/resources) 中也能被解析。开启资源过滤:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
复制代码

Profile

profile 特性可以让我们定义多个 profile,每个 profile 对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。可以在以下几个地方声明:

  1. pom.xml:这里声明的 profile 只对当前项目有效
  2. 用户 settings.xml:.m2/settings.xml 中的 profile 对该用户的 Maven 项目有效
  3. 全局 settings.xml:conf/settings.xml,对本机上所有 Maven 项目有效

示例:

<project>
    ...
    <profiles>
      <profile>
        <id>dev</id>
        <properties>
            <active.profile>dev</active.profile>
            <key1>value1</key1>
            <key2>value2</key2>
        </properties>

        <!-- 默认激活配置 -->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <!-- 在该 profile 下才会引入的依赖 -->
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>3.2.4.RELEASE</version>
            </dependency>
        <dependencies>
        <!-- 在该 profile 下才会加载的变量文件 -->
        <build>
            <filters>
                <filter>../profile/test-pre.properties</filter>
            </filters>
        </build>
            
      </profile>
    </profiles>
    ...
</project>
复制代码

profile 中可以生效的标签

> <repositories>  
> <pluginRepositories>  
> <dependencies>  
> <plugins>  
> <properties> (not actually available in the main POM, but used behind the scenes)  
> <modules>  
> <reporting>  
> <dependencyManagement>  
> <distributionManagement>  
> a subset of the <build> element, which consists of:  
    > <defaultGoal>  
    > <resources>  
    > <testResources>  
    > <finalName>  
复制代码

高级特性

  • 使用 SNAPSHOT 来管理未正式发布的构件

当使用 SNAPSHOT(快照版本)管理构件版本时,每次都会去远程仓库上最新的依赖。应用场景描述:

  • 不需要发布到远程仓库的包,比如一个服务的提供者,一个实现。
  • 需要发布到远程仓库的包,但进度还处于开发阶段,随时可能发生变化

在 version 后面加上 "-SNAPSHOT",自动部署到远程仓库中的 SNAPSHOT 对应目录下。

  • 裁剪反应堆,聚合项目打包指定个别模块打包

裁剪反应堆应用场景描述:一个聚合工程中包含了三个主应用(如:网关服务、服务提供者、助手项目),结构大致如下:

-- 工程主目录
------ gateway 项目(可运行)
------ provider 项目(可运行)
------ assist 项目(可运行)
------ api 项目(被依赖)
------ common 项目(被依赖)
------ pom.xml
复制代码

当在主目录下执行 mvn package 后,会把 gateway、provider、assist 项目都打出包来,此时如果需要单独打三个项目时,就需要用到裁剪反应堆。若想单独打包 provider 项目则可使用如下命令:

mvn package -pl provider -am
复制代码

若想同时打包 gateway、assist 则使用如下命令:

mvn package -pl gateway,assist -am
复制代码

反应堆详细参数 mvn -h

-am --also-make 同时构建所列模块的依赖模块;
-amd -also-make-dependents 同时构建依赖于所列模块的模块;
-pl --projects 构建制定的模块,模块间用逗号分隔;
-rf -resume-from 从指定的模块恢复反应堆。

实用插件

  • 使用 versions 插件批量修改父 pom 版本号

-- https://blog.csdn.net/GGBomb2/article/details/78316068
-- 在顶级 pom 目录中设置版本号。
- 注:在 windows 下的 powershell 中 1.1.3-RELEASE 得加双引号
- 注:CMD 不用加
mvn versions:set -DnewVersion=1.1.3-RELEASE
-- 设置不正确时可以撤销新版本号的设置
mvn versions:revert
-- 确认新版本号无误后提交新版本号的设置
mvn versions:commit
复制代码
  • 使用 dependency 插件分析项目内的依赖

mvn dependency:analyze

# 只想看依赖树中包含 groupId 为 javax.serlet 的枝干
mvn dependency:tree -Dincludes=javax.servlet
# 不想看依赖树中包含 groupId 为 javax.serlet 的枝干
mvn dependency:tree -Dexcludes=javax.servlet
复制代码

参数的格式(pattern)定义如下:
[groupId]:[artifactId]:[type]:[version]

每个部分(冒号分割的部分)是支持 * 通配符的,如果要指定多个格式则可以用 , 分割,如:

mvn dependency:tree -Dincludes=javax.servlet,org.apache.*
复制代码
  • 使用 deploy 插件进行多模块项目区别发布(部分需要发布,部分不需要)

<properties>
    <maven.deploy.skip>true</maven.deploy.skip>
</properties>
复制代码
  • 使用 shade 插件做配置文件合并等工作

my.oschina.net/u/2377110/b…

  • 使用 assembly 插件做聚合项目的输出

my.oschina.net/u/2377110/b…

项目启发

  1. 从 Maven 插件的设计上,体现了模板的方法的设计思想,是不是可以用在我们的项目上?

备忘

  • 打包语句
mvn clean install -Ptest -Dmaven.test.skip=true  -X -DskipTests package
复制代码
  • setting 文件
<?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">

    <!-- localRepository Default: ~/.m2/repository -->
    <localRepository>本地 maven 仓库地址</localRepository>

    <pluginGroups>
        <pluginGroup>org.codehaus.cargo</pluginGroup>
        <pluginGroup>org.apache.maven.plugins</pluginGroup>
        <pluginGroup>org.codehaus.mojo</pluginGroup>
    </pluginGroups>

    <proxies>
    </proxies>

    <!-- 当需要上传构建才需要进行身份认证 -->
    <!--<servers>
        <server>
            <id>xxx_release</id>
            <username></username>
            <password></password>
        </server>
        <server>
            <id>xxx_snapshots</id>
            <username></username>
            <password></password>
        </server>
    </servers>-->
        
    <!-- 设置私服才需要放开这段配置 -->
    <!-- <profiles>
        <profile>
            <id>nexus</id>
            <repositories>
                <repository>
                    <id>仓库id</id>
                    <url>私服地址</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                        <checksumPolicy>warn</checksumPolicy>
                    </releases>
                    <snapshots>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                        <checksumPolicy>warn</checksumPolicy>
                    </snapshots>
                </repository>
            </repositories>

            <pluginRepositories>
                <pluginRepository>
                    <id>仓库id</id>
                    <url>私服地址</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                        <checksumPolicy>warn</checksumPolicy>
                    </releases>
                    <snapshots>
                        <enabled>false</enabled>
                        <updatePolicy>always</updatePolicy>
                        <checksumPolicy>warn</checksumPolicy>
                    </snapshots>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>

    <activeProfiles>
        <activeProfile>nexus</activeProfile>
    </activeProfiles> -->

    <!-- 使用阿里云 maven 镜像仓库 -->
    <mirrors>
        <mirror>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
</settings>
复制代码

遇到的问题

  • idea 下项目路径中包含中文编译失败控制台乱码

blog.csdn.net/qq_28862535…

  • springboot 项目 profile 占位符不生效

stackoverflow.com/questions/3…

参考资料:

文章分类
后端
文章标签