Maven

55次阅读

共计 77712 个字符,预计需要花费 195 分钟才能阅读完成。

Maven

一.Maven 简介

1.1 何为 maven

Maven 可翻译为 ” 知识的积累 ” or” 专家 ”, 是一款成功的开源跨平台的项目管理工具, 无论小型的开源类库项目, 还是大型的企业级应用; 无论传统的瀑布式开发, 还是流行的敏捷模式,Maven 都能大显身手.

1.1.1 构建工具

​ 我们一直在不停的寻找避免重复的方法, 设计的重复, 编码的重复, 文档的重复, 当然还有构建的重复.Maven 最大化的消除了构建的重复, 抽象了构建生命周期, 并且为绝大部分的构建任务提供了已实现的插件, 我们不需要再定义过程, 甚至不需要去实现这些过程中的一些任务, 只需要遵循 Maven 中的约定.

​ 同时 Maven 帮助我们标准化构建过程, 以前十个项目可能有十种构建方式, 有了 Maven 后, 所有项目的构建命令都是一直且简单的. 因此 Maven 作为一个构建工具:

  1. 可以帮我们自动化构建,
  2. 可以帮我们抽象构建过程
  3. 提供构建任务是实现
  4. 跨平台
  5. 对外提供一直的操作接口

1.1.2 不仅仅是构建工具

​ Maven 不仅是构建工具, 还是一个依赖管理工具和项目信息管理工具, 提供中央仓库来帮忙我们自动下载构建, 通过引入一套经纬机制来系统准确的定位每一个构建(artifact).

1.1.3 Maven

​ 在 Maven 之前, 有过程式的 Make 和 Ant, 开发者需要显示的指定每一个目标, 以及完成该目标所需要执行的任务. 针对每一个项目, 开发者都需要重新编写这一过程, 而其中就隐含着大量重复.

​ 而 Maven 是声明式的, 项目构建过程和过程各个阶段所需的工作都由插件实现, 并且大部分插件都是现成的, 开发者只需要声明项目的基本元素,Maven 就执行内置的, 完整的构建过程.

二.Maven 的使用

2.1 pom 文件

​ Maven 项目的核心是 pom.xml,POM(Project Object Model, 项目对象模型)定义了项目的基本信息, 用于描述项目如何构建, 声明项目依赖等.

<modelVersion>4.0.0</modelVersion>:modelVersion 指定了当前 Pom 模型的版本, 固定为 4.0.0

<groupId>com.lsy</groupId>:groupId 定义了项目属于哪个组, 这个组往往和项目所在的组织和公司相关

<artifactId>hello-world</artifactId>:artifactId 定义了当前 Maven 项目在组中唯一的 ID

<version>1.0-SNAPSHOT</version>:version 指定了 Hello World 项目当前的版本, 其中 SNAPSHOT 意为快照, 说明该项目还处于开发中

<name>Maven Hello World Project</name>:name 元素声明了一个对于用户更为友好的项目名称. 非必须

​ 当运行 mvn clean compile 命令:clean 告诉 Maven 清理输出目录 target/,compile 告诉 Maven 编译项目主代码, 从输出中看到 Maven 首先执行 clean:clean 任务, 删除 target/ 目录(默认情况下,Maven 构建的所有输出都在 target 目录中, 接着执行 resources:resources 任务(未定义项目资源), 最后执行 compiler:compile 任务, 将项目主代码编译至 target/classes 目录

​ 其中 clean:clean,resources:resources 和 compiler:compile 对应了 Maven 插件及插件目标, 比如 clean:clean 是 clean 插件的 clean 目标,compiler:compile 是 compiler 插件的 compile 目标

2.2 测试

​ 首先添加 Junit 依赖

<dependencies>

​ <dependency>

​ <groupId>junit</groupId>

​ <artifactId>junit</artifactId>

​ <version>4.7</version>

​ <scope>test</scope>

​ </dependency>

<dependencies>

​ 其中 scope 元素为依赖范围, 若依赖范围为 test 则表示该依赖只对测试有效, 如果不声明依赖范围, 则默认是 compile, 表示该依赖对主代码和测试代码均有效

当运行 mvn clean test 命令,Maven 实际执行的不止 test 任务, 而是 clean:clean,resources:resources,compiler:compile,resources:testResources 以及 compiler:testCompile, 即在执行测试之前, 会先自动执行项目资源处理, 主代码编译, 测试资源处理, 测试代码编译等工作, 这是 Maven 生命周期的一个特性.

​ 由于 Maven 核心插件之一 compiler 插件默认支持 Java1.3, 因此需要配置插件支持 Java1.8

<build>

​ <plugins>

​ <plugin>

​ <groupId>org.apache.maven.plugins</groupId>

​ <artifactId>maven-compiler-plugin</artifactId>

​ <configuration>

​ <source>1.8<source>

​ <target>1.8</target>

​ </configuration>

​ </plugin>

​ </plugins>

</build>

2.3 打包和运行

​ 将项目进行编译, 测试之后, 下一个重要的步骤就是打包(package), 当没有指定打包类型时, 默认打包类型是 jar, 当执行 mvn clean package 命令,Maven 会在打包之前进行编译, 测试等操作, 之后通过 jar:jar 任务负责打包, 实际上就是 jar 插件的 jar 目标将项目主代码打包成一个 hello-world-1.0-SNAPSHOT.jar 的文件

​ 当其他项目需要直接引用这个 jar 时, 接下来需要一个安装的步骤, 执行 mvn clean install, 此命令执行了安装任务 install:install, 将项目输出的 jar 安装到了 Maven 本地仓库中, 而构件只有被下载到本地仓库后, 才能由 Maven 项目使用.

2.4 使用 Archetype 生成项目骨架

​ mvn archetype:generate, 当运行插件时, 格式为:groupId:artifactId:version:goal, 默认使用最新的稳定版

三. 坐标和依赖

3.1 坐标

  • Maven 的一大功能还是管理项目依赖, 为了能自动化解析任何一个 Java 构件,Maven 就必须将它们唯一标识, 这就依赖管理的底层基础 — 坐标
  • 在实际生活中, 我们可以将地址看作是一种坐标, 省, 市, 区, 街道等一系列信息可以唯一标识城市中的任意居住地址, 对应在 Maven 的世界中拥有非常巨大的构件, 也就是平常使用的一些 jar,war 等文件
  • Maven 定义了这样一规则: 世界上任意一个构建都可以使用 Maven 坐标唯一标识,Maven 坐标的元素包括

    • groupId: 定义当前 Maven 项目隶属公司的实际项目
    • artifactId: 该元素定义实际项目中的一个 Maven 模块, 推荐做法使用实际项目名称为 artifact 的前缀, 便于寻找实际构建
    • version: 该元素定义 Maven 项目当前所处的版本
    • package: 该元素定义 Maven 项目的打包方式, 默认为 jar 包
    • classifier: 该元素用来帮助定义构建输出一些附属构建, 附属构件与主构件对应
<groupId\>org.sonatype.nexus</groupId\>  
<artifactId\>nexus-indexer</artifactId\>  
<version\>2.0.0</version\>  
<packaging\>jar</packaging\>  
​  
<!-->   
1\. 以上 5 个元素,groupId,artifactId,version 是必须定义的,packaging 是可选的, 而 classifier 是不能直接定义的  
2\. 项目构件的文件名格式:artifactId-version\[-classifier\].packaging

3.2 依赖

3.2.1 依赖的配置

  • 项目要引用 Maven 中的构建, 就需要在 pom 文件中, 通过坐标来使用依赖, 在根元素 project 下的 dependencies 可以包含一个或多个 dependency 元素, 用以声明一个或多个项目依赖

    • groupId,artifactId 和 version: 依赖的基本坐标, 对于任何一个依赖来说, 基本坐标是最重要的
    • type: 依赖的类型, 对应项目坐标定义的 packaging, 一般不必声明, 默认为 jar
    • scope: 依赖的范围
    • optional: 标记依赖是否可选
    • exclusions: 用来排除传递性依赖

3.2.2 依赖的范围

  • Maven 项目在编译项目主代码时需要使用一套 classpath, 如编译项目主代码时需要使用 spring-core, 该文件以依赖的方式被引入到 classpath 中. 其次,Maven 在编译和执行测试的时候会使用另外一套 classpath, 依赖同样会引入到相应的 classpath 中, 最后在运行 Maven 项目时, 又会使用一套 classpath
  • 依赖范围就是用来控制依赖与三种 classpath(编译 classpath, 测试 classpath, 运行 classpath)的关系

    • compile: 编译依赖范围, 没有指定时, 为默认依赖范围. 使用此依赖范围的 Maven 依赖, 对于编译, 测试运行三种 classpath 都有效, 典型的例子为:spring-core, 在编译, 测试, 运行阶段都需要使用该依赖
    • test: 测试依赖范围, 使用此依赖范围的 Maven 依赖, 只对于测试的 classpath 有效, 在编译主代码或者运行项目时将无法使用此类依赖, 典型的例子就是 JUnit, 它只有在编译器测试代码及运行测试的时候才需要
    • provided: 已提供依赖范围, 使用此依赖范围的 Maven 依赖, 对于编译和测试 classpath 有效, 但在运行时无效, 典型的例子就是 servlet-api, 编译和测试项目的时候需要该依赖, 但在运行项目的时候, 由于容器已经提供, 就不需要 Maven 重复引入了
    • runtime: 运行时依赖范围, 使用此依赖范围的 Maven 依赖, 对于测试和运行 classpath 有效, 但在编译主代码时无效, 典型的例子是 JDBC 驱动实现, 项目主代码的编译只需要 JDK 提供的 JDBC 接口, 只有在执行测试或运行项目的时候才需要实现上述接口的具体 JDBC 驱动
    • system: 系统依赖范围, 该依赖与三种 classpath 的关系, 和 provided 依赖范围完全一致, 但是, 使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径, 由于此类依赖不是通过 Maven 仓库解析的, 而且往往与本机系统绑定, 可能造成构建的不可移植性

      <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: 导入依赖范围, 该依赖范围不会对三种 classpath 产生实际的影响, 主要用于导入其他 pom 文件中的 dependencyManagement 元素对于依赖版本约束的内容

3.2.3 传递性依赖

  • 在使用 Maven 依赖时, 如 Spring Framework, 此依赖又会依赖其他的开源库, 因此实际中往往会下载一个很大的如 spring-framework-2.5.6-with-dependencies.zip 包, 这样往往就引入了很多不必要的依赖, 而 Maven 的传递性依赖机制就可以很好的解决这一问题

    • 当 A 项目引入一个 compile 范围的 B 依赖, 而 B 依赖中有一个 compile 范围的 C 依赖, 那么 C 依赖同样会成为 A 的 compile 范围依赖

  • 传递性依赖和依赖范围

    • 假设 A 依赖于 B,B 依赖于 C, 那么 A 对于 B 是第一直接依赖,B 对于 C 是第二直接依赖,A 对于 C 是传递性依赖
    • 当第二直接依赖是 compile 的时候, 传递性依赖与第一直接依赖范围一致
    • 当第二直接依赖是 test 的时候, 依赖不会得以传递
    • 当第二直接依赖是 provided 的时候, 只传递第一直接依赖范围为 provided 的依赖, 且传递性依赖范围为 provided
    • 当第二直接依赖是 runtime 的时候, 传递性地依赖的范围与第一直接依赖的范围一致, 但 compile 例外, 此时传递性依赖的范围为 runtime

3.2.4 可选依赖

  • 假如有这样一个依赖关系,A 依赖于 B,B 依赖于 X 和 Y,B 对于 X 和 Y 的依赖都是可选依赖, 根据传递性依赖的定义, 如果这三个依赖的范围都是 compile, 那么 X,Y 就是 A 的 compile 范围传递性依赖, 但是由于 X,Y 都是可选依赖, 所以依赖不会得以传递, 因此 X,Y 不会对 A 有任何影响

    • 为什么会有可选依赖这一特性呢?

      • 当 B 实现了两个特性, 特性一依赖于 X, 特性二依赖于 Y, 并且这两个特性是互斥的, 用户不可能同时使用两个特性, 比如 B 是一个持久层隔离工具包, 支持多种数据库, 在使用这个工具包的时候, 只会依赖一种数据库
  
<dependencies\>  
 <dependency\>  
 <groupId\>mysql</groupId\>  
 <artifact\>mysql-connector-java</artifact\>  
 <version\>5.6.0</version\>  
 <optional\>true</optional\>  
 </dependency\>  
 <dependency\>  
 <groupId\>postgresql</groupId\>  
 <artifactpostgresql</artifact>  
 <version\>8.4-701.jdbc3</version\>  
 <optional\>true</optional\>  
 </dependency\>  
</dependencies\>

*   以上使用 <optional\> 元素表示两个依赖为可选依赖, 他们只会对当前项目 B 产生影响, 当其他项目依赖于 B 时, 这两个依赖不会被传递, 所以当 A 依赖于 B 项目时, 如果要使用 mysql 数据库, 那么需要显式的声明 mysql-connector-java 这一依赖
    
*   关于可选依赖, 在理想的情况下, 是不应该使用可选依赖的, 使用可选依赖的原因是某一个项目中实现了多个特性, 而根据单一职责原则, 应该针对不同的特性分别创建一个 Maven 项目, 用户根据需要选择使用其中某一个依赖
    

3.2.5 排除依赖

  • 传递性依赖会给项目隐式的引入很多依赖, 这极大的简化了项目依赖的管理, 但是有时候这种特性䧥带来问题

    • 例如, 当前项目有一个第三方依赖, 而这个依赖由于某些原因依赖了另一个类库的 SNAPSHOT, 那么整个 SNAPSHOT 就会成为当前项目的传递性依赖, 而 SNAPSHOT 的不稳定性会直接影响到当前的项目, 这时候就需要排除掉该 SNAPSHOT, 并且在当前项目中声明该类库的某个正式发布版

      <dependencies>
      <dependency>
      <groupId>com.lsy.myproject</groupId>
      <artifactId>myproject-a</artifactId>
      <version>1.0.0</version>
      <exclusions>
      <exclusion>
      <groupId>com.lsy.myproject</groupId>
      <artifactId>myproject-b</artifactId>
      </exclusion>
      </exclusions>
      </dependency>
      <dependency>
      <groupId>com.lsy.myproject</groupId>
      <artifactId>myproject-b</artifactId>
      <version>1.1.0</version>
      </dependency>
      </dependencies>

3.2.6 归类依赖

  • 当一些依赖来自同一个项目的不同模块, 这些依赖的版本都应该是相同的, 将来升级也是一起升级, 如 Spring Framework, 这时可以使用 properties 元素定义 Maven 属性

    <properties>
    <springframework.version>4.2.1</springframework.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <verison>${springframework.version}</verison>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <verison>${springframework.version}</verison>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <verison>${springframework.version}</verison>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <verison>${springframework.version}</verison>
    </dependency>
    </dependencies>

3.2.7 优化依赖

  • 在软件开发过程中, 通常会通过重构等方式不断优化自己代码, 同样, 对于 Maven 项目的依赖也需要对其进行优化

    • 去除多余的依赖
    • 显示声明某些必要的依赖
  • Maven 会自动解析所有项目的直接依赖和间接依赖, 并且根据规则判断每个依赖范围, 对于一些依赖冲突, 也能进行调整, 以确保任何一个构建只有唯一的版本在依赖中存在, 此称为解析依赖

    • mvn dependency:list 查看当前项目的已解析依赖
    • mvn dependency:tree 以树结构查看已解析依赖
    • mvn dependency:analyze 解析依赖

      • Used undeclared dependencies: 指项目中使用到的, 但是没有显示声明的依赖, 这种依赖意味着潜在的风险, 当前项目直接在使用它们, 所以需要显示声明任何项目中直接用到的依赖
      • Unused declared dependencies: 指项目中未使使用的. 但显示声明的依赖

四. 仓库

  • 之前已经介绍了 Maven 的坐标和依赖, 坐标和依赖是任何一个构件在 Maven 世界中的逻辑表示方式, 而构建的物理表示方式是文件,Maven 通过仓库来同一管理这些文件
  • 在 Maven 世界中, 任何一个以依赖, 插件或项目构建的输出, 都可以成为构件

    • 例如:log4j-1.2.15.jar,maven-compiler-plugin-2.0.2.jar 或项目打包后的 myproject-1.0.0-SNAPSHOT.jar
  • 在一台工作站上, 可能会有几十个项目, 所有项目在 /lib 目录下都会有自己所需的依赖包, 而这些依赖中都有大量的重复, 每个项目各自存储自己所需的依赖包不仅造成磁盘空间的浪费, 而且也难于同一管理, 文件的复制等操作
  • 得益于坐标机制, 任何 Maven 项目使用任何一个构建的方式都是相同的, 因此,Maven 可以在某个位置统一存储所有 Maven 项目共享的构件, 这个统一的位置就是仓库, 为了实现重用, 项目构建完毕后生成的构建也可以安装或部署到仓库中

4.1 仓库的布局

  • 任何一个构建都有其唯一的坐标, 根据这个坐标可以定义其在仓库中的唯一存储路径, 这就是 Maven 的仓库布局方式, 如 log4j:log4j:1.2.15 这一依赖, 其对应的仓库路径为 log4j/log4j/1.2.15/log4j-1.2.15.jar

    • 路径与坐标的关系为:groupId/artifactId/version/artifactId-verision.packaging

4.2 仓库的分类

  • 对于 Maven 来说, 仓库分为两类

    • 本地仓库
    • 远程仓库

      • 中央仓库:Maven 核心自带的仓库服务器, 它包含了绝大部分开源的构件
      • 私服: 另一种特殊的远程仓库, 为了节省宽带和时间, 应该在局域网内部构建一个私有仓库服务器, 用其代理所有外部的远程仓库, 内部项目部署到私服上供其它项目使用
      • 其他公共库
  • 当 Maven 根据坐标寻找构件时, 它首先会查看本地仓库, 如果本地仓库存在此构件, 则直接使用, 如果本地仓库不存在此构件, 或者需要查看是否有更新的构件版本,Maven 就会去远程仓库查找, 发现需要的构件后下载到本地仓库再使用, 若本地和远程都没有, 则报错

4.3 本地仓库

  • 一般来说, 在 Maven 项目目录下, 没有注入 lib/ 这样用来存放依赖文件的目录, 当 Maven 执行编译或测试时, 如果需要使用依赖文件, 它总是基于坐标使用本地仓库的依赖文件

    • 默认情况, 在用户目录下路径名为.m2/repository/ 的仓库目录, 当想自定义本地仓库目录地址时, 可以编辑~/.m2/setting.xml, 设置 localRepository 元素来指定仓库地址

      <settings>
      <localRepository>D:/java/repository/</localRepository>
      </settings>

    • 默认情况下,~/.m2/settings.xml 文件是不存在的, 用户需要从 Maven 安装目录复制 $M2_HOME/conf/settings.xml 文件再编辑, 建议不要直接修改全局目录的 settings.xml, 而是在用户目录下进行修改
  • 一个构建只有在本地仓库中, 才能由其它 Maven 项目使用

    1. 依赖 Maven 从远程仓库下载到本地仓库中
    2. 将本地项目的构件安装到 Maven 本地仓库中 mvn clean install

4.4 远程仓库

  • 安装好 Maven 后, 如果不执行任何 Maven 命令, 本地仓库目录是不存在的, 只有输入第一条 Maven 命令后,Maven 才会创建本地仓库, 并根据配置和需要, 从远程仓库下载至本地仓库

    • 这就好比藏书, 本地仓库好比书房, 远程仓库好比书店, 我需要读书时先去书房找, 当书房没有时, 就去书店买回放到书房, 并且一般对每个人来说, 书房只有一个, 而外面的书店可以有多个

4.4.1 中央仓库

  • 最原始的本地仓库是空的, 所以 Maven 必须知道至少一个可用的远程仓库, 才能在执行 Maven 命令时下载到需要的构建, 中央仓库就是这样一个默认的远程仓库, 可以通过解压工具打开 $M2_HOME/lib/maven-model-builder-3.0.jar 中的 org/apache/maven/model/pom-4.0.0.xml 文件

    <repositories>
    <repository>
    <id>central</id>
    <name>Maven Repository Switchboard</name>
    <url>http://repo1.maven/org/maven2&lt;/url>
    <layout>default</layout>
    <snapshots>
    <enable>false</enable>
    </snapshots>
    </repository>
    </repositories>

    • 这段配置是所有 Maven 项目都会继承的超级 Pom 文件, 这段配置使用 id central 对中央仓库进行唯一标识, 其名称为 Maven Repository Switchboard, 它使用 default 仓库布局, 也就是在之前介绍的仓库布局, 最后 snapshots 元素表示不从该仓库下载快照版

4.4.2 私服

  • 私服是一种特殊的远程仓库, 它是架设在局域网内的仓库服务, 私服代理广域网上的远程仓库, 供局域网内的 Maven 用户使用, 当 Maven 需要下载构建的时候, 它从私服请求, 如果私服上不存在该构件, 则从外部的远程仓库下载, 缓存在私服上之后, 再为 Maven 的下载提供服务

    • 此外, 一些无法从外部下载的构件也可以从本地上传到私服上供大家使用
  • 私服的好处

    • 节省外网带宽: 建立私服同样可以减少组织自己的开支, 大量的对于外部仓库的重复请求会消耗很大的带宽, 利用私服代理外部仓库之后, 对外的重复构件下载便可以消除, 即降低外网带宽的压力
    • 加速 Maven 构件: 不停的连接请求外部仓库是十分耗时的, 但 Maven 的一些内部机制 (如快照更新检查井) 要求 Maven 在执行构建时不停的检查远程仓库数据, 因此, 当项目配置很多外部仓库时, 构建速度就会降低
    • 部署第三方构件: 当某个构件无法从任何一个外部远程仓库获取, 建立私服之后, 便可以将这些构件部署到这个内部的仓库中, 供内部的 Maven 项目使用
    • 提高稳定性, 增强控制:Maven 构建高度依赖远程仓库, 因此, 大哥 Internet 不稳定的时候,Maven 构建也会变得不稳定, 甚至无法构建, 使用私服后, 即是短暂时没有 Internet 连接, 由于私服中有大量缓存,Maven 依然可以正常运行, 并且私服中有很多额外的权限功能控制
    • 降低中央仓库的负荷: 每天中央仓库都需要面对大量的下载请求, 使用私库可以降低对于中央仓库的负荷
  • 远程仓库的配置

    • 很多情况下, 默认的中央仓库无法满足项目的需求, 可能项目需要的构件存在于另一个远程仓库中, 可以通过 Pom/settings 文件中来配置该仓库

      <!–> POM 文件 </!–>
      <project>
      …..
      <repositories>
      <repository>
      <id>jboss</id>
      <name>JBoss Repository</name>
      <url>http://repository.jboss.com/m…;/url>
      <release>
      <enabled>true</enabled>
      </release>
      <snapshots>
      <enabled>false</enabled>
      </snapshots>
      <layout>default</layout>
      </repository>
      </repositories>
      </project>

      <!–> settings 文件 </!–>
      <profiles>
      <profile>
      <id>dev</id>
      <activation>
      <activatedByDefault>true</activatedByDefault>
      </activation>
      <repositories>
      <repository>
      <id>jboss</id>
      <name>JBoss Repository</name>
      <url>http://repository.jboss.com/m…;/url>
      <release>
      <enabled>true</enabled>
      </release>
      <snapshots>
      <enabled>false</enabled>
      </snapshots>
      <layout>default</layout>
      </repository>
      </repositories>
      </profile>
      </profiles>

      • 在 repositories 元素下, 可以使用 repository 子元素声明一个或多个远程仓库, 并且声明一个 id, 任何一个仓库声明的 id 必须是唯一 id
      • Maven 自带的中央仓库使用的 id 为 central, 如果其他仓库使用该 id, 则会覆盖中央仓库的配置, 该配置中的 url 指向了仓库的地址
      • 该配置中的 release 和 snapshots 元素用来控制 Maven 对于发布版构件和快照版构件的下载, 对于 release 和 snapshots 元素来说除了 enabled 子元素, 还有 updatePolicy 和 checksumPolicy 元素

        • updatePolicy: 用来配置 Maven 从远程仓库检查更新的频率, 默认为 daily

          • daily: 每天
          • never: 从不
          • always: 每次
          • interval :X : 每隔 X 分钟一次
        • checksumPolicy: 配置 Maven 检查文件失败时的策略, 默认为 warn

          • fail:Maven 遇到验证和错误就让构建失败
          • warn:Maven 遇到验证和错误就发出警告信息
          • ignore:Maven 遇到验证和错误时完全忽略
          
        <snapshots\>  
         <enabled\>true</enabled\>  
         <updatePolicy\>daily</updatePolicy\>  
         <checksumPolicy\>ignore</checksumPolicy\>  
        </snapshots\>
        
    
*   远程仓库的验证
    
    *   大部分远程仓库无需认证就可以访问, 但出于安全考虑, 我们需要提供一些认证信息才能访问一些远程仓库
        
    *   配置认证信息和配置仓库信息不同, 仓库信息可以直接配置在 POM 文件中, 但是认证信息必须配置在 settings.xml 文件中
        
    *   其中 server 元素的 id 必须与 POM 文件中需要认证的 repository 元素的 id 完全一致
        
          
        <settings\>  
         ......  
         <servers\>  
         <server\>  
         <id\>my-project</id\>  
         <username\>user</username\>  
         <password\>password</password\>  
         </server\>  
         </servers\>  
         .......  
        </settings\>
        
    
*   部署至远程仓库
    
    *   私服一大作用就是部署第三方构件, 包括组织内部生成的构件以及无法从外部仓库直接获取的构件, 无论是日常开发中生成的构件, 还是正式版本发布的构件, 都需要部署到仓库中
        
        *   distributionManagement 包含 repository 和 snapshotRepository 子元素, 前者表示发布版构件的仓库, 后者表示快照版的仓库
            
        *   往远程仓库部署构件时, 往往需要认证, 认证配置需在 settings.xml 中创建一个 server 元素, 其 id 与仓库的 id 匹配, 无论从远程仓库下载构件, 还是部署构件至远程仓库, 当需要认证时, 配置方式都是一样的
            
        
          
        <!--> 在 POM 文件中配置 distributionManagement </!-->  
        <project\>  
         .....  
         <distributionManagement\>  
         <repository\>  
         <id\>jboss-release</id\>  
         <name\>JBoss Release Repository</name\>  
         <url\>http://192.168.1.99/content/repository/jboss-release</url\>  
         </repository\>  
         <snapshotRepository\>  
         <id\>jboss-snapshots</id\>  
         <name\>JBoss Snapshots Repository</name\>  
         <url\>http://192.168.1.99/content/repository/jboss-snapshots</url\>  
         </snapshotRepository\>  
         </distributionManagement\>  
         .....  
        </project\>
        

4.5 快照

  • 在 Maven 的世界中, 任何一个项目或者构件都必须有自己的版本, 版本的值可能是 1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT 或者 2.1-20191214.221414-13, 其中 1.0.0,1.3-alpha- 4 和 2.0 是稳定的发布版本, 而 2.1-SNAPSHOT 和 2.1-20091214.221414-13 是不稳定的快照版本
  • 对于一个稳定的版本, 如果仓库中已经包含, 那么 Maven 就不会再去对照远程仓库进行更新, 除非每次执行 Maven 命令前, 清除本地仓库中等待稳定版本, 而对于一个正在迭代的项目, 如果要实时更新版本的内容就需要频繁的修改新的版本名称, 这样是对版本号的滥用
  • 针对这种情况, 使用快照版时,Maven 会自动为构件打上时间戳, 因此,Maven 就能随时找到仓库中该构建最新版本的文件, 一旦有新的更新, 就会去同步到本地仓库. 当项目经过完善的测试后需要发布的时候, 再将快照版本更改为发布版本
  • 快照版本只应该在组织内部的项目或模块之间依赖使用, 因为这时, 组织对这些快照版本的依赖具有完全的理解和控制权, 项目不应该依赖任何组织外部的快照版本依赖, 由于快照版本的不稳定性, 随时可能发生变化, 这样的依赖会有潜在的危险

4.6 从仓库解析依赖的机制

  • 当本地仓库没有依赖构件的时候,Maven 会自动从远程仓库下载, 当依赖版本为快照版本时,Maven 会自动找到最新的快照, 这背后的依赖机制可以概括如下

    1. 当依赖的范围时 system 的时候,Maven 直接从本地文件系统解析构件
    2. 根据依赖坐标计算仓库路径后, 尝试从本地仓库寻找构件, 如果发现相应的构件, 则解析成功
    3. 在本地仓库不存在相应构件的情况下, 如果依赖的版本时显式的发布版本构件, 如 1.2,2.1-beta- 1 等, 则遍历所有的远程仓库, 发现后, 下载并解析使用
    4. 如果依赖的版本时 RELEASE 或 LATEST, 则基于更新策略读取所有远程仓库的元数据 groupId/artifact/maven-metadata.xml, 将其与本地仓库的对应元数据合并后, 计算出 RELEASE 或 LATEST 真实的值, 然后基于这个真实的值检查本地仓库和远程仓库
    5. 如果依赖的版本是 SNAPSHOT, 则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml, 将其与本地仓库的对应数据合并后, 得到最新的快照版本的值, 然后基于该值检查本地仓库, 或者从远程仓库下载
    6. 如果最后解析得到的构件版本是时间戳格式, 如 1.4.1-20191104.121455-8, 则复制其时间戳格式的文件至非时间戳格式, 如 SNAPSHOT, 并使用该非时间戳格式的构件

      <!–> 基于 groupId 和 artifactId 的 maven-metadata.xml </!–>
      <?xml version=”1.0″ encoding=”UTF-8″?>
      <metadata>
      <groupId>org.sonatype.nexus</groupId>
      <artifactId>nexus</artifactId>
      <versioning>
      <latest>1.4.2-SNAPSHOT</latest>
      <release>1.3.7</release>
      <versions>
      <version>1.3.5</version>
      <version>1.3.6</version>
      <version>1.3.7</version>
      <version>1.4.0-SNAPSHOT</version>
      <version>1.4.1-SNAPSHOT</version>
      <version>1.4.2-SNAPSHOT</version>
      </versions>
      <lastUpdated>20191214221133</lastUpdated>
      </versioning>
      </metadata>

  • 该 XML 文件列出了仓库中存在的构件所有可用的版本, 同时 latest 元素指向了这些版本中最新的那个版本 1.4.2-SNAPSHOT, 而 release 元素指向了这些版本中最新的发布版本 1.3.7,Maven 通过合并多个远程仓库及本地仓库的元数据, 就能计算出基于所有仓库的 latest 和 release

    • 需要注意的是, 在依赖声明使用 LATEST 和 RELEASE 是不推荐的做法, 因为 Maven 随时可能解析到不同的构件, 且 Maven 不会明确告诉用户这样的变化

4.7 镜像

  • 如果仓库 X 可以提供仓库 Y 存储的所有内容, 那么就可以认为 X 是 Y 的一个镜像, 由于地理位置的因素, 中央仓库的下载速度会比较慢, 这时我们可以配置 Maven 使用镜像来代替中央仓库, 编辑 settings.xml

    <settings>
    ……
    <mirrors>
    <mirror>
    <id>maven.net.cn</id>
    <name>one of the central mirror in china</name>
    <url>http://maven.net.cn/content/g…;/url>
    <mirrorOf>central</mirrorOf>
    </mirror>
    </mirrors>
    ……
    </settings>

    • <mirrorOf> 的值为 central, 表示该配置为中央仓库的镜像, 任何对于中央仓库的请求都会转至该镜像, 用户也可以使用同样的方法配置其他仓库的镜像, 另外三个元素 id,name,url 与一般仓库配置无异, 表示该镜像仓库的唯一标识, 名称以及地址

      • 若该镜像要进行验证, 即基于该 id 配置仓库认证
  
<settings\>  
 .....  
 <mirrors\>  
 <mirror\>  
 <id\>internal-repository</id\>  
 <name\>Internal Repository Mananger</name\>  
 <url\>http://192.168.1.100/maven2/</url\>  
 <mirrorOf\>\*</mirrorOf\>  
 </mirror\>  
 </mirrors\>  
 .....  
</settings\>
  • 以上 mirrorOf 元素的值为 *, 表示该配置是所有 Maven 仓库的镜像, 对于任何远程仓库的请求都会被转至该指定的仓库, 如果镜像仓库需要验证, 则配置一个 id 为 internal-repository 的 <server> 即可

    • <mirrorOf>*<mirrorOf>: 匹配所有远程仓库
    • <mirrorOf>external:*<mirrorOf>: 匹配所有远程仓库, 使用 localhost 的除外, 使用 file:// 协议的除外, 也就是说, 匹配所有不在本机上的远程仓库
    • <mirrorOf>repo1,repo2<mirrorOf>: 匹配仓库 repo1 和 repo2, 用逗号分隔多个仓库
    • <mirrorOf>*,! repo1<mirrorOf>: 匹配所有的远程仓库, 除 repo1 外, 使用感叹号将仓库从匹配中排除
  • 需要注意的是, 由于镜像仓库完全屏蔽了被镜像仓库, 当镜像仓库不稳定或停止服务时,Maven 仍将无法访问被镜像仓库, 因此无法下载构件

五. 生命周期和插件

5.1 Maven 的生命周期

  • 在 Maven 出现之前, 项目构建的生命周期就已经存在, 但是不同的公司和开发人员虽然同样在做构件工作, 其对于不同的项目却不能够重用, 只能重新定制开发, 而 Maven 的生命周期就是为了对所有的构件过程进行抽象和统一

    • 这个生命周期包含了项目的清理, 初始化, 编译, 测试, 打包, 集成测试, 验证, 部署和站点生成等几乎所有的构件步骤, 也就是说几乎所有项目的构件, 都能映射到这样一个生命周期上
  • Maven 的生命周期是抽象的, 这意味着生命周期本身不做任何实际的工作, 在 Maven 的设计中, 实际的任务 (如编译主代码) 都交由插件完成, 这种思想和设计模式的模方法类似, 在父类中定义算法的整体结构, 子类可以通过实现或重写父类的方法来控制实际的行为

    public abstract class Template{
    public void build(){
    initialize();
    compile();
    test();
    packagee();
    integrationTest();
    deploy();
    }

    protect abstract void initialize();
    protect abstract void compile();
    protect abstract void test();
    protect abstract void packagee();
    protect abstract void integrationTest();
    protect abstract void deploy();
    }

    • 在 Maven 的生命周期中抽象了各个步骤, 定义了他们的次序, 但是没有提供具体的实现, 而通过插件机制为每个构件步骤绑定一个或多个插件行为, 而且 Maven 为大多数构件步骤都绑定了默认的插件, 例如, 针对编译的 maven-compiler-plguin, 针对测试的 maven-surefire-plugin 等
    • Maven 定义的生命周期和插件机制一方面保证了所有 Maven 项目有一致的构件标准, 另一方面又通过默认的插件简化和稳定实际项目的构件, 此外, 该机制还提供了足够的扩展, 用户可以通过配置现有的插件或自定义插件来自定义构件行为
  • 三套声明周期

    • Maven 拥有三套相互独立的生命周期, 他们分别为 clean,default 和 site, 每个生命周期包含一些阶段, 这些阶段是有序的, 并且后面的阶段依赖于前面的阶段, 但是三套声明周期本身是互相独立的

      1. clean 生命周期: 清理项目

        • pre-clean: 执行一些清理前需要完成的工作
        • clean: 清理上次构件生成的文件
        • post-clean: 执行一些清理后需要完成的工作
      2. default 声明周期: 定义了真正的构件所需执行的所有步骤

        • validate
        • initialize
        • generate-sources
        • process-sources: 处理项目主资源文件, 一般来说针对 /src/main/resources 目录的内容进行变量替换等工作后, 复制到项目输出的主 classpath 目录中
        • compile: 编译项目的主源码, 一般来说针对 /src/main/java 目录下的 Java 文件至目录输出的主 classpath 目录中
        • process-classes
        • generate-test-sources
        • process-test-sources: 处理项目测试资源文件, 一般来说针对 /src/test/resources 目录的内容进行变量替换工作后, 复制到项目输出的测试 classpath 目录
        • test-compile: 编译项目的测试代码, 一般来说针对 /src/test/java 目录下的 java 文件至输出的测试 classpath 目录中
        • test: 使用单元测试框架运行测试, 测试代码不会被打包或部署
        • prepare-package
        • package: 接收编译好的代码, 打包成可发布的格式, 如 jar
        • pre-integration-test
        • integration-test
        • post-integration-test
        • verify
        • install: 将包安装到 Maven 本地仓库, 供本地其他 Maven 项目使用
        • deploy: 将最终的包复制到远程仓库, 供其他开发人员和 Maven 项目使用
      3. site 声明周期: 建立和发布项目站点,Maven 能够基于 POM 所包含的信息, 自动生成一个友好的站点供交流和发布项目信息

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

      • 从命令行执行 Maven 任务最主要方式就是调用 Maven 的生命周期, 各个生命周期是相互独立的, 而生命周期的阶段是有前后依赖关系的

        • mvn clean: 该命令调用 clean 生命周期的 clean 阶段, 实际执行阶段为 pre-clean 和 clean
        • mvn test: 该命令调用 default 生命周期的 test 阶段, 实际执行的阶段为 default 生命周期的 validate,initialize 直到 test 的所有阶段, 这也解释为什么在执行测试的时候, 项目代码能够自动编译
        • mvn clean install: 该命令调用 clean 生命周期的 clean 阶段和 default 生命周期的 install 阶段
        • mvn clean deploy site-deploy: 该命令调用 clean 生命周期的 clean 阶段,default 生命周期的 deploy 阶段和 site 生命周期的 site-deploy 阶段

5.2 插件

5.2.1 插件目标和绑定

  • Maven 的核心仅仅定义了抽象的生命周期, 具体的任务是交由插件完成的, 插件以独立的构件形式存在

    • 对于插件本身而言, 为了能够复用代码, 它往往能够完成多个任务, 为每个功能编写一个插件显然不合理, 因为这些任务背后有大量可复用的代码, 因此, 这些功能聚集到一个插件里面, 每个功能就是一个插件目标
  • Maven 的生命周期和插件相互绑定, 用以完成实际的构件任务, 具体而言, 是生命周期的阶段与插件的目标相互绑定, 以完成某个具体的构件任务

    • 例如: 编译这一任务对应了 default 生命周期的 compile 这一阶段, 而 maven-compiler-plugin 这一插件的 compile 目标能完成此任务

  • 自定义绑定

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

      • 当需要创建项目的源码 jar 包,maven-source-plugin 可以帮我们完成任务, 但是内置的插件绑定关系中并没有涉及这一任务, 因此需要自行配置, 它的 jar-no-fork 目标能将项目主代码打包成 jar 文件, 可以将其绑定到 default 生命周期的 verify 阶段, 在执行完集成测试和安装构件之前创建源码 jar 包

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

      • 在 POM 的 build 元素下的 plugins 子元素中声明插件的使用, 上例用到了是 maven-source-plugin, 其 groupId 为 org.apache.maven.plugins, 用户总是应该声明一个非快照版本, 这样可以避免由于插件版本变化造成的构件不稳定性

        • 除了基本的插件坐标声明外, 还有插件执行配置,executions 下每个 execution 子元素可以用来配置执行一个任务, 该例配置了一个 id 为 attach-sources 的任务, 通过 phase 配置将其绑定到 verify 生命周期阶段上, 再通过 goals 配置指定要执行的插件目标, 在运行 mvn verify 时就会执行该任务
        • 有时候. 即是不通过 phase 元素配置生命周期阶段, 插件目标也能绑定到生命周期中去, 原因是: 有很多插件的目标在编写时就已经定义了默认绑定阶段, 可以使用 maven-help-plugin 查看详细信息

          • mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1 -Ddetail

            • Bound to phase : package 默认绑定到 package 生命周期
      • 当插件目标被绑定到不同生命周期阶段的时候, 其执行顺序会由生命周期阶段的先后顺序决定, 如果多个目标被绑定到同一个阶段, 他们的顺序就由插件声明的先后顺序决定

5.2.2 插件配置

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

    • 命令行插件配置

      • 在日常的 Maven 使用中, 我们通常从命令行输入并执行 Maven 命令, 很多插件目标的参数都支持从命令行配置, 用户可以在 Maven 命令中使用 - D 参数, 并伴随一个参数键 = 参数值的形式, 来配置插件目标的参数

        • mvn install -Dmaven.test.skip = ture: 给 maven.surefire-plugin 提供一个 maventest.skip 参数, 当参数为 true 时, 就会跳过执行测试

          • 参数 - D 是 Java 自带的, 其功能是通过命令行设置一个 Java 系统属性,Maven 简单地重用了该参数, 在准备插件的时候检查系统属性来实现插件参数的配置
    • POM 中插件全局配置

      • 并不是所有的插件参数都适合用命令行配置, 有些参数的值从项目创建到项目发布都不会改变, 或者说很少改变, 这种情况下在 POM 文件中一次性配置比重复在命令行输入更合理

        • 用户可以在声明插件的时候, 对插件进行一个全局的配置, 所有基于该插件的目标任务, 都会使用这些配置, 如:maven-compiler-plugin 来编译 1.8 版本的源文件, 生成与 JVM1.8 兼容的字节码文件

          <build>
          <plugins>
          <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifact>maven-compiler-plugin</artifact>
          <version>2.1</version>
          <configuration>
          <source>1.8</source>
          <target>1.8</target>
          </configuration>
          </plugin>
          </plugins>
          </build>

5.2.3 获取插件信息

  • 当遇到一个构建任务时, 不仅需要知道去哪里找到合适的插件, 还需要详细的了解插件的配置点, 由于 Maven 的插件非常多, 其中大部分没有完善的文档, 因此, 通过帮助命令来了解插件显得非常重要

    1. 使用 maven-help-plugin 描述插件

      • 除了访问在线的插件文档外, 可以借助 maven-help-plugin 来获取插件的详细信息

        • mvn help:decribe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1

          • 这里执行的是 maven-help-plugin 的 decribe 目标, 在参数 plugin 中输入需要描述插件的 groupId,artifactId 和 version,Maven 在命令行的输出 maven-compiler-plugin 的简要信息, 包括插件的坐标, 目标前缀和目标等信息
      • 在描述插件时, 可以省去版本信息,Maven 会自动获取最新版本来进行表述, 并可以用目标前缀来替换坐标

        • mvn help:describe -Dplugin=compiler
      • 如果仅仅描述某个插件目标的信息, 则加上 goal 参数, 更详细的信息, 则加上 detail 参数

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

5.2.4 插件解析机制

  • 为了方便用户使用和配置插件,Maven 不需要用户提供完整的插件坐标信息, 就可以解析得到正确的插件

    • 插件仓库

      • 与依赖构件一样, 插件构件同样基于坐标存储在 Maven 仓库中, 在需要的时候 Maven 先从本地仓库寻找, 如果不存在, 则从远程仓库查找, 找到插件之后, 再下载到本地仓库使用
      • 不同于 repositories 以及 repository 子元素, 插件的远程仓库使用 pluginRepositories 和 pluginRepository 配置

        <pluginRepositories>
        <pluginRepository>
        <id>central</id>
        <name>Maven Plugin Resository</name>
        <url>http://repo1.maven.org/maven2&lt;/url>
        <layout>default</layout>
        <snapshots>
        <enabled>false</enabled>
        </snapshots>
        <release>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
        </release>
        </pluginRepository>
        </pluginRepositories>

  • 插件默认的 groupId

    • 在 POM 中配置插件时, 如果该插件时 Maven 的官方插件(即 groupId 为 org.apache.maven.plugins), 就可以省略 groupId,Maven 在解析该插件的时候, 会自动使用默认 groupId 不起

      <build>
      <plugins>
      <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.1</version>
      <configuration>
      <source>1.8</source>
      <target>1.8</target>
      </configuration>
      </plugin>
      </plugins>
      </build>

  • 解析插件版本

    • 同样为了简化插件的配置和使用, 在用户没有提供插件版本的情况下,Maven 会自动解析插件版本

      • 首先 Maven 在超级 POM 中为所有核心插件设定了版本, 超级 POM 是所有 Maven 项目的父 POM, 所有项目都继承这个超级 POM 配置, 因此即使用户不加任何配置,Maven 在使用核心插件时, 它的版本就已经确定了
      • 如果用户使用的某个插件没有设定版本, 并且这个插件也不属于核心插件,Maven 机会去检查所有仓库中可用的版本, 通过仓库元数据 groupId/artifactId/maven-metadata.xml 文件, 如 maven-compiler-plugin 插件为例, 它在 org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml

        <?xml version=”1.0″ encoding=”UTF-8″?>
        <metadata>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <versioning>
        <latest>2.1</latest>
        <release>2.1</release>
        <versions>
        <version>2.0-beta-1</version>
        <version>2.1</version>
        <version>2.2</version>
        <version>2.3</version>
        </versions>
        <lastUpdated>20190102092331</lastUpdated>
        </versioning>
        </metadata>

      • Maven 遍历本地仓库和所有远程仓库, 将仓库元数据合并后, 就能计算出 latest 和 release 的值,maven3 将版本解析到最新的非快照版, 如 2.1

5.2.5 解析插件前缀

  • Maven 命令支持使用插件前缀来简化插件的使用, 插件前缀和 groupId:artifact 是一一对应的, 这种匹配关系存储在仓库元数据中, 与之前提到的 groupId/artifactId/maven-metadata.xml 不同, 这里仓库元数据为 groupId/maven-metadata.xml, 一般插件都位于 /org/apache/maven/plugins/ 和 org/code-haus/mojo/, 可以通过 settings.xml 配置让 Maven 检查其他 groupId 上的插件仓库元数据

    <settings>
    <pluginGroups>
    <pluginGroup>com.my.plugins</pluginGroup>
    </pluginGroups>
    </settings>

  • 在仓库的元数据文件中可以看到插件的前缀定义

    <metadata>
    <plugins>
    <plugin>
    <name>Maven Clean Plugin</name>
    <prefix>clean</prefix>
    <artifact>maven-clean-plugin</artifact>
    </plugin>
    <plugin>
    <name>Maven Compiler Plugin</name>
    <prefix>compile</prefix>
    <artifact>maven-compile-plugin</artifact>
    </plugin>
    <plugin>
    <name>Maven Dependency Plugin</name>
    <prefix>dependency</prefix>
    <artifact>maven-dependency-plugin</artifact>
    </plugin>
    </plugins>
    </metadata>

六. 聚合与继承

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

6.1 集合

  • aggregator

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lsy.project</groupId>
    <artifact>project-aggregator</artifact>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Aggregator</name>

    <modules>
    <module>project-email</module>
    <module>project-register</module>
    </modules>

    • 对于聚合模块, 其打包方式 packaging 的值必须为 pom
    • 通过 modules 元素来实现聚合, 用户可以通过在一个打包方式为 pom 的 Maven 项目中声明任意数量的 module 元素来实现模块的聚合, 这里每个 module 的值都是一个当前 POM 的相对目录

      • 如 aggregator 的 POM 路径为 …/project-aggregator/pom.xml, 那么 project-email 对应 的目录为 …/project-aggregator/project-email/, 并且目录中包含了 pom.xml,src/main/java,src/test/java 等内容, 离开了 project-aggregator 也能独立创建 =
    • 聚合模块和其他模块的目录结构并非一定要父子关系, 也可以是平行关系

      <modules>
      <module>../prject-email</module>
      <module>../project-register</module>
      </modules>

    • 当在聚合模块中执行 mvn clean install 时,Maven 首先解析聚合模块的 POM, 分析要构件的模块, 并计算出一个反应堆构件顺序(Reactor Build Order), 然后根据这个顺序构件各个模块
    • 聚合模块的构件顺序

      • Maven 按序读取 POM 文件, 如果 POM 没有依赖模块, 那么就构件模块, 否则就先构件其依赖模块

6.2 继承

  • 从以上的聚合模块和其他模块中可以看到, 多个被管理模块的 POM 文件中会有大量重复的相同配置, 他们有相同的 groupId 和 version, 相同的依赖, 相同的插件配置, 而通过 POM 文案的继承可以消除重复

    • project-parent 父模块

      <modelVersion>4.0.0</modelVersion>
      <groupId>com.lsy.project</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <packaging>pom</packaging>
      <name>Parent</name>

      • 父模块的 POM 文件使用与其他模块一直的 groupId 和 version, 它的 packaging 方式为 pom, 与聚合模块一致
      • 父模块主要是为了消除配置的重复, 因此它本身不包含除 POM 文件之外的项目文件, 也就不需要 src/main/java 之类的文件夹了
    • project-email 子模块

      <parent>
      <groupId>com.lsy.project</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <relativePath>../project-parent/pom.xml</relativePath>
      </parent>

      <artifactId>project-email</artifactId>
      <name>Email</name>

      <dependencies>
      …..
      </dependencies>

      • 使用 parent 元素声明父模块,parent 下子元素 groupId,artifactId,version 指定父模块的坐标, 元素 relativePath 表示父模块 POM 的相对路径, 表示 Email 模块和其父模块是在平行的目录下

        • 当构件项目时,Maven 会首先根据 relativePath 检查父 POM 文件, 如果找不到再从本地仓库找,relativePath 的默认值为../pom.xml, 也就是说,Maven 默认父 POM 在上一层目录
      • 子模块没有 groupId 和 version, 是因为子模块隐式的从父模块继承了这两个元素, 从而消除了不必要的配置
  • 可继承的 POM 元素

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

    • dependencies 元素是可以被继承的, 说明依赖是会被继承的, 所以我们可以将子模块共有的依赖配置到父模块中, 子模块就可以移除这些依赖, 简化配置
    • 上述方法是可行的, 但是可能将来会有新的模块并不需要父模块中的一些依赖, 这就会产生不合理的现象, 从而 Maven 提供了 dependencyManagement 元素, 既能让子模块继承到父模块的依赖配置, 又能保证子模块依赖使用的灵活性

      • 在 dependencyManagement 元素下的依赖声明不会引入实际的依赖, 不过它能够约束 dependencies 下的依赖使用
    <modelVersion\>4.0.0</modelVersion\>  
    <groupId\>com.lsy.project</groupId\>  
    <artifactId\>project-parent</artifactId\>  
    <version\>1.0.0-SNAPSHOT</version\>  
    <packaging\>pom</packaging\>  
    <name\>Parent</name\>  
    ​  
    <properties\>  
     <springframework.version\>4.3.1</springframework.version\>  
    </properties\>  
    ​  
    <dependencyManagement\>  
     <dependencies\>  
     <dependency\>  
     <groupId\>org.springframework</groupId\>  
     <artifactId\>spring-core</artifactId\>  
     <version\>${springframwork.version}</version\>  
     </dependency\>  
     <dependency\>  
     <groupId\>org.springframework</groupId\>  
     <artifactId\>spring-beans</artifactId\>  
     <version\>${springframwork.version}</version\>  
     </dependency\>  
     <dependency\>  
     <groupId\>org.springframework</groupId\>  
     <artifactId\>spring-context</artifactId\>  
     <version\>${springframwork.version}</version\>  
     </dependency\>  
     </dependencies\>  
    </dependencyManagement\>
    
    *   这里使用 dependencyManagement 声明的依赖既不会给 parent 模块引入依赖, 也不会给子模块引入依赖, 不过这段配置是会被继承的
        
    *   子模块 POM
        
          
        <parent\>  
         <groupId\>com.lsy.project</groupId\>  
         <artifactId\>project-parent</artifactId\>  
         <version\>1.0.0-SNAPSHOT</version\>  
         <relativePath\>../project-parent/pom.xml</relativePath\>  
        </parent\>  
        ​  
        <artifactId\>project-email</artifactId\>  
        <name\>Email</name\>  
        ​  
         <dependencies\>  
         <dependency\>  
         <groupId\>org.springframework</groupId\>  
         <artifactId\>spring-core</artifactId\>  
         </dependency\>  
         <dependency\>  
         <groupId\>org.springframework</groupId\>  
         <artifactId\>spring-beans</artifactId\>  
         </dependency\>  
         <dependency\>  
         <groupId\>org.springframework</groupId\>  
         <artifactId\>spring-context</artifactId\>  
         </dependency\>  
         </dependencies\>
        
        *   使用这种依赖管理机制, 可以在父 POM 中使用 dependencyManagement 声明依赖能够统一项目规范中的依赖版本, 当版本在父 POM 中声明之后, 子模块使用依赖时就不需要声明了. 也不会发生多个子模块使用依赖版本不一致的情况
            
            *   scoper 元素的 import 依赖范围只在 dependencyManagement 元素下才有效果, 使用该范围的依赖通常指向一个 POM 文件, 作用是将 POM 中的 dependencyManagement 配置导入并合并到当前 POM 的 dependencyManagement 元素中
                
            *   所以除了复制配置和继承父模块这两种方式外, 还可以通过 import 范围依赖导入这一配置
                
            
              
            <dependencyManagement\>  
             <dependencies\>  
             <dependency\>com.lsy.project</dependency\>  
             <artifactId\>project-parent</artifactId\>  
             <version\>1.0-SNAPSHOT</version\>  
             <type\>pom</type\>  
             <scope\>import</scope\>  
             </dependencies\>  
            </dependencyManagement\>
            
  • 插件管理

    • Maven 提供了 dependencyManagement 元素帮助管理依赖, 类似的,Maven 也提供了 pluginManagement 元素管理插件, 该元素中配置的依赖同样不会造成实际的插件调用行为, 而 POM 文件中配置了真正的 plugin 元素, 并且 groupId 和 artifact 一致时, 才会产生实际的插件行为
    • 父模块

      <build>
      <pluginManagement>
      <plugins>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-source-plugin</artifactId>
      <version>3.1.1</version>
      <executions>
      <execution>
      <id>attach-source</id>
      <phase>verify</phase>
      <goals>
      <goal>jar-no-fork</goal>
      </goals>
      </execution>
      </executions>
      </plugin>
      </plugins>
      </pluginManagement>
      </build>

    • 子模块

      <build>
      <plugins>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifact>maven-source-plugin</artifact>
      </plugin>
      </plugins>
      </build>

      • 子模块使用了 maven-source-plugin 插件, 同时又继承了父模块的 pluginManagement 配置
  • 集合与继承的关系

    • 多模块的聚合与继承其实是两个概念, 其目的是完全不同的, 前者为了方便快速的构件项目, 后者主要为了消除重复配置

      • 对于聚合模块来说: 它知道有哪些模块被聚合, 但是那些被聚合的模块并不知道这个聚合模块的存在
      • 对于继承关系的父 POM 来说: 它不知道有哪些子模块继承于它, 但是那些子模块都必须知道自己的父模块是什么
    • 在实际项目中, 一个 POM 往往即是聚合模块, 又是父模块
  • 约定优于配置

    • Maven 提倡 ” 约定优于配置 ”,Maven 只需要一个简单的 POM 文件, 就可以完成清除, 构件等任务

      • 源码目录:src/main/java/
      • 编译输出目录为:target/classes/
      • 打包方式为:jar
      • 包输出目录为:target/
    • Maven 此机制的来源就是超级 POM 文件, 此文件在 $MAVEN_HOME/lib/maven-model-builder-x.x.x.jar 中的 org/apache/maven/model/pom-4.0.0.xml 路径下

      <repositories>
      <repository>
      <id>central</id>
      <name>Maven Repository Swithboard</name>
      <url>http://repo1.maven.org/maven2&lt;/url>
      <layout>default</layout>
      <snapshots>
      <enabled>false</enabled>
      </snapshots>
      </repository>
      </repositories>


      <pluginRepositories>
      <pluginRepository>
      <id>central</id>
      <name>Maven Plugin Repository</name>
      <url>http://repo1.maven.org/maven2&lt;/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>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>
      <plugins>
      <plugin>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.3</version>
      </plugin>
      <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>2.3</version>
      </plugin>
      <plugin>
      <artifactId>maven-clean-plugin</artifactId>
      <version>2.3</version>
      </plugin>
      <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.0.3</version>
      </plugin>
      ……
      </plugins>
      </pluginManagement>
      </build>

七. Nexus 私服

7.1 Nexus 内置的仓库

  • Nexus 仓库有四种类型

    1. group: 仓库组
    2. hosted: 宿主仓库
    3. proxy: 代理仓库
    4. virtual: 虚拟仓库


*   Maven 可以从宿主仓库下载构件,Maven 也可以从代理仓库下载构件, 而代理仓库会简介的从远程仓库下载并缓存构件
    
*   而一般为了方便, 我们会从仓库组下载构件, 而仓库组没有实际的内容, 它只是管理一组实际的仓库, 当它接收到请求时, 它会转向其包含的宿主仓库或代理仓库获得实际构件的内容
    
  • 仓库的 Policy 属性

    • Release: 发布版
    • Snapshot: 快照版

7.2 配置 Maven 从 Nexus 下载构件

  • 在 POM 文件中配置仓库和插件仓库 —— 只对当前项目有效

    <project>
    …….
    <repositories>
    <repository>
    <id>nexus</id>
    <name>Nexus</name>
    <url>http://localhost:8081/nexus/content/groupId/public/</url>
    <snapshots>
    <enabled>true</enabled>
    </snapshots>
    </repository>
    </repositories>
    <pluginRepositories>
    <pluginRepository>
    <id>nexus</id>
    <Name>Nexus</Name>
    <url>http://localhost:8081/nexus/content/groupId/public/</url>
    <release>
    <enabled>true</enabled>
    </release>
    <snapshots>
    <enabled>true</enabled>
    </snapshots>
    </pluginRepository>
    </pluginRepositories>
    …….
    </project>

  • 在 settings.xml 文件中配置 — 全局有效

    • settings.xml 文件并不支持直接配置 repositories 和 pluginRepositories, 但是可以通过 Maven 提供的 Profile 机制, 让用户将仓库配置到 settings.xml 中的 Profile 中
<settings\>  
 ....  
 <profiles\>  
 <profile\>  
 <id\>nexus</id\>  
 <repositories\>  
 <repository\>  
 <id\>nexus</id\>  
 <name\>Nexus</name\>  
 <url\>http://localhost:8081/nexus/content/groupId/public/</url\>  
 <release\>  
 <enabled\>true</enabled\>  
 </release\>  
 <snapshots\>  
 <enabled\>ture</enabled\>  
 </snapshots\>  
 </repository\>  
 </repositories\>  
   
 <pluginRepositories\>  
 <pluginRepository\>  
 <id\>nexus</id\>  
 <Name\>Nexus</Name\>  
 <url\>http://localhost:8081/nexus/content/groupId/public/</url\>  
 <release\>  
 <enabled\>true</enabled\>  
 </release\>  
 <snapshots\>  
 <enabled\>true</enabled\>  
 </snapshots\>  
 </pluginRepository\>  
 </pluginRepositories\>  
 </profile\>  
 </profiles\>  
   
 <!--> 激活指定 id 的 profile </!-->  
 <activeProfiles\>  
 <activeProfile\>nexus</activeProfile\>  
 </activeProfiles\>  
</settings\>
  • 以上配置已经能让 Maven 项目从 Nexus 私服下载构件了, 但是 Maven 仍然会去访问中央仓库, 现在我们想要将所有请求都仅仅通过私服, 这就需要借助于 Maven 镜像了

    <settings>
    …..
    <mirrors>
    <mirror>
    <id>nexus</id>
    <mirrorOf>*</mirrorOf>
    <url>http://localhost:8081/nexus/content/groupId/public/</url>
    </mirror>
    </mirrors>

    <profiles>
    <profile>
    <id>nexus</id>
    <repositories>
    <repository>
    <id>central</id>
    <name>central</name>
    <url>http://central&lt;/url>
    <release>
    <enabled>true</enabled>
    </release>
    <snapshots>
    <enabled>ture</enabled>
    </snapshots>
    </repository>
    </repositories>

    <pluginRepositories>
    <pluginRepository>
    <id>central</id>
    <Name>Nexus</Name>
    <url>http://central&lt;/url>
    <release>
    <enabled>true</enabled>
    </release>
    <snapshots>
    <enabled>true</enabled>
    </snapshots>
    </pluginRepository>
    </pluginRepositories>
    </profile>
    </profiles>

    <!–> 激活指定 id 的 profile </!–>
    <activeProfiles>
    <activeProfile>nexus</activeProfile>
    </activeProfiles>
    </settings>

    • 这里仓库和插件仓库配置的 id 都为 central, 它们将会覆盖 POM 中央仓库的配置, 并且这里的 url 已经无关紧要, 因为所有的请求都会通过镜像访问私服地址

7.3 部署构件至 Nexus

  • 如果只是为了代理外部公共仓库, 那么 Nexus 的代理仓库就已经完全足够了, 对于另一类 Nexus 仓库 — 宿主仓库来说, 他们主要作用是存储组织内部的, 或一些无法从公共仓库获得的第三方构件, 用户可以通过配置 Maven 自动部署构件至 Nexus 的宿主仓库

    • 使用 Maven 部署构件至 Nexus

      • 日常开发生成的快照版本构件可以直接部署到 Nexus 中策略为 Snapshot 的宿主仓库中, 项目正式发布的构件则应该部署到 Nexus 中策略为 Release 的宿主仓库中
      • POM 文件配置

        <project>
        …..
        <distributeManagement>
        <repository>
        <id>nexus-releases</id>
        <name>Nexus Release Repository</name>
        <url>http://localhost:8081/nexus/content/repositories/
        release/</url>
        </repository>
        <snapshotRepository>
        <id>nexus-snapshots</id>
        <name>Nexus Snapshots Repository</name>
        <url>http://localhost:8081/nexus/content/repositories/
        snapshots</url>
        </snapshotRepository>
        </distributeManagement>
        …..
        </project>

      • Nexus 的仓库对于匿名用户只是可读的, 为了能够部署构件, 还需要在 settings.xml 中配置认证信息

        <settings>
        ….
        <servers>
        <server>
        <id>nexus-releases</id>
        <username>admin</username>
        <password>admin123</password>
        </server>
        <server>
        <id>nexus-snapshots</id>
        <username>admin</username>
        <password>admin123</password>
        </server>
        </servers>
        ….
        </settings>

八. Profile

  • 一个优秀的构件系统必须足够灵活, 它应该能够让项目在不同的环境下都能够成功的构件, 例如: 开发环境, 测试环境和产品环境, 这些环境的数据库配置不尽相同, 那么项目构建时就需要能够识别其所在的环境并正确使用配置

8.1 属性

<properties>
<springframework.version>4.3.1</springframework.version>
</properties>

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

  • 这是最常见的 Maven 属性使用的方式, 通过 properties 元素使得用户自定义一个或多个 Maven 属性, 然后在 POM 的其他地方使用 ${属性名称}的方式来引用该属性, 这种做法的最大意义是消除重复
  • Maven 的属性有六类

    1. 内置属性

      1. ${basedir}: 表示项目根目录, 即包含 pom.xml 文件的目录
      2. ${version}: 表示项目的版本
    2. POM 属性: 用户可以使用该类属性引用 POM 文件中对应元素的值

      • ${project.artifactId}: 对应 <project><artifactId> 元素的值

        • ${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/test-classes
        • ${project.build.groupId}: 项目的 groupId
        • ${project.build.artifactId}: 项目的 artifactId
        • ${project.build.build.finalName}: 项目打包输出文件的名称, 默认为 ${project.artifactId}

          -${project.version}

    3. 自定义属性:<properties> 元素下自定义的 Maven 属性
    4. settings 属性: 与 POM 属性同理, 用户使用 settings. 来引用, 如:${settings.localRepository}指向用户本地仓库的地址
    5. Java 系统属性: 所有 Java 系统属性都可以使用 Maven 属性引用, 如 ${user.home}指向用户目录

      • 可以通过 mvn help:system 查看所有的 java 系统属性
    6. 环境变量属性: 所有环境变量都可以用 env. 来引用, 例如:${env.JAVA_HOME}

      • 可以通过 mvn help:system 查看所有的环境变量

8.2 资源过滤

  • 在不同的开发环境中, 项目的源码应该使用不同的方式进行构件, 最常见的就是数据库的配置了, 在开发中, 有些项目会在 src/main/resources/ 目录下放置不同环境下的数据库配置

    database.jdbc.driverClass = com.mysql.jdbc.Driver
    database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev
    database.jdbc.username = dev
    database.jdbc.password = dev-passwd

    database.jdbc.driverClass = com.mysql.jdbc.Driver
    database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test
    database.jdbc.username = test
    database.jdbc.password = test-passwd

  • 为了应对环境的变化, 我们可以使用 Maven 属性将这些会发生变化的部分提取出来

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

  • 在 settings.xml 中通过 profile 定义不同环境下的配置数据

    <profiles>
    <profile>
    <id>dev</id>
    <properties>
    <db.driver>com.mysql.jdbc.Driver</db.driver>
    <db.url>jdbc:mysql://localhost:3306/dev</db.url>
    <db.username>dev</db.username>
    <db.password>dev-passwd</db.password>
    </properties>
    </profile>

    <profile>
    <id>test</id>
    <properties>
    <db.driver>com.mysql.jdbc.Driver</db.driver>
    <db.url>jdbc:mysql://localhost:3306/test</db.url>
    <db.username>test</db.username>
    <db.password>test-passwd</db.password>
    </properties>
    </profile>
    </profiles>

    • 需要注意的是:

      • Maven 属性默认只会在 POM 文件中才会被解析, 也就是说 ${db.username}放到 POM 文件中会变成 dev 或 test, 但是在 src/main/resources/ 目录下仍然还是 ${db.username}, 因此, 需要让 Maven 解析资源文件中的 Maven 属性
      • 资源文件处理的是 maven-reosurces-plugin 做事, 它默认的行为只是将项目主资源文件复制到主代码编译输出目录
  • 开启资源过滤

    • Maven 默认的主资源目录和测试目录的定义是在超级 POM 文件中
<resources\>  
 <resource\>  
 <directory\>${project.basedir}/src/main/resources</directory\>  
 <filtering\>true</filtering\>  
 </resource\>  
</resources\>  
<testResources\>  
 <testResource\>  
 <directory\>${project.basedir}/src/test/resources</directory\>  
 <filtering\>true</filtering\>  
 </testResource\>  
</testResources\>
  • 通过 mvn clean install -Pdev : 通过 mvn 的 - P 参数表示在命令行激活一个 profile=dev 的 profile

8.3 Profile

  • 激活 Pofile

    1. 命令行激活

      • 用户可以使用 mvn 命令行参数 -P 加上 profile 的 id 来激活 profile, 多个 id 之间逗号分隔

        • mvn clean install -Pdev,-Ptest
    2. settings 文件显示激活

      • 如果用户希望某个 profile 默认一直处于激活状态, 就可以配置 settings.xml 文件的 activeProfile 元素, 表示其配置的 profile 对所有项目都处于激活状态

        <settings>
        …..
        <activeProfiles>
        <activeProfile>dev</activeProfile>
        </activeProfiles>
        …..
        </settings>

    3. 系统属性激活

      • 用户可以配置当前某系统属性存在时, 自动激活 profile

        <profiles>
        <profile>
        <activation>
        <property>
        <name>test</name>
        </property>
        </activation>
        …..
        </profile>
        </profiles>

      • 用户可以配置当前某系统属性存在且等于 a 时, 自动激活 profile

        <profiles>
        <profile>
        <activation>
        <property>
        <name>test</name>
        <value>a</value>
        </property>
        </activation>
        …..
        </profile>
        </profiles>

      • 用户可以在命令行声明系统属性

        • mvn clean install -Dtest=x
    4. 操作系统环境变量激活

      • Profile 还可以根据操作系统环境激活

        <profiles>
        <profile>
        <activation>
        <os>
        <name>Windows10</name>
        </os>
        </activation>
        …..
        </profile>
        </profiles>

    5. 文件存在与否激活

      • Maven 可以根据项目中某个文件是否存在来决定是否激活 profile

        <profiles>
        <profile>
        <activation>
        <file>
        <missing>a.properties</missing>
        <exists>b.properties</exists>
        </file>
        </activation>
        …..
        </profile>
        </profiles>

    6. 默认激活

      • 用户可以在定义 profile 的时候指定其默认激活

        <profiles>
        <profile>
        <id>dev</id>
        <activation>
        <activeByDefault>true</activeByDefault>
        </activation>
        …..
        </profile>
        </profiles>

  • 以上激活优先级从上之下依次减小

    • 可以通过 mvn help:active-profiles 来查看当前激活的 profile
    • 可以通过 mvn help:all-profiles 来查看所有的 profile
  • profile 的种类

    • 根据具体的需要, 可以在一下位置声明 profile

      Maven

一.Maven 简介

1.1 何为 maven

Maven 可翻译为 ” 知识的积累 ” or” 专家 ”, 是一款成功的开源跨平台的项目管理工具, 无论小型的开源类库项目, 还是大型的企业级应用; 无论传统的瀑布式开发, 还是流行的敏捷模式,Maven 都能大显身手.

1.1.1 构建工具

​ 我们一直在不停的寻找避免重复的方法, 设计的重复, 编码的重复, 文档的重复, 当然还有构建的重复.Maven 最大化的消除了构建的重复, 抽象了构建生命周期, 并且为绝大部分的构建任务提供了已实现的插件, 我们不需要再定义过程, 甚至不需要去实现这些过程中的一些任务, 只需要遵循 Maven 中的约定.

​ 同时 Maven 帮助我们标准化构建过程, 以前十个项目可能有十种构建方式, 有了 Maven 后, 所有项目的构建命令都是一直且简单的. 因此 Maven 作为一个构建工具:

  1. 可以帮我们自动化构建,
  2. 可以帮我们抽象构建过程
  3. 提供构建任务是实现
  4. 跨平台
  5. 对外提供一直的操作接口

1.1.2 不仅仅是构建工具

​ Maven 不仅是构建工具, 还是一个依赖管理工具和项目信息管理工具, 提供中央仓库来帮忙我们自动下载构建, 通过引入一套经纬机制来系统准确的定位每一个构建(artifact).

1.1.3 Maven

​ 在 Maven 之前, 有过程式的 Make 和 Ant, 开发者需要显示的指定每一个目标, 以及完成该目标所需要执行的任务. 针对每一个项目, 开发者都需要重新编写这一过程, 而其中就隐含着大量重复.

​ 而 Maven 是声明式的, 项目构建过程和过程各个阶段所需的工作都由插件实现, 并且大部分插件都是现成的, 开发者只需要声明项目的基本元素,Maven 就执行内置的, 完整的构建过程.

二.Maven 的使用

2.1 pom 文件

​ Maven 项目的核心是 pom.xml,POM(Project Object Model, 项目对象模型)定义了项目的基本信息, 用于描述项目如何构建, 声明项目依赖等.

<modelVersion>4.0.0</modelVersion>:modelVersion 指定了当前 Pom 模型的版本, 固定为 4.0.0

<groupId>com.lsy</groupId>:groupId 定义了项目属于哪个组, 这个组往往和项目所在的组织和公司相关

<artifactId>hello-world</artifactId>:artifactId 定义了当前 Maven 项目在组中唯一的 ID

<version>1.0-SNAPSHOT</version>:version 指定了 Hello World 项目当前的版本, 其中 SNAPSHOT 意为快照, 说明该项目还处于开发中

<name>Maven Hello World Project</name>:name 元素声明了一个对于用户更为友好的项目名称. 非必须

​ 当运行 mvn clean compile 命令:clean 告诉 Maven 清理输出目录 target/,compile 告诉 Maven 编译项目主代码, 从输出中看到 Maven 首先执行 clean:clean 任务, 删除 target/ 目录(默认情况下,Maven 构建的所有输出都在 target 目录中, 接着执行 resources:resources 任务(未定义项目资源), 最后执行 compiler:compile 任务, 将项目主代码编译至 target/classes 目录

​ 其中 clean:clean,resources:resources 和 compiler:compile 对应了 Maven 插件及插件目标, 比如 clean:clean 是 clean 插件的 clean 目标,compiler:compile 是 compiler 插件的 compile 目标

2.2 测试

​ 首先添加 Junit 依赖

<dependencies>

​ <dependency>

​ <groupId>junit</groupId>

​ <artifactId>junit</artifactId>

​ <version>4.7</version>

​ <scope>test</scope>

​ </dependency>

<dependencies>

​ 其中 scope 元素为依赖范围, 若依赖范围为 test 则表示该依赖只对测试有效, 如果不声明依赖范围, 则默认是 compile, 表示该依赖对主代码和测试代码均有效

当运行 mvn clean test 命令,Maven 实际执行的不止 test 任务, 而是 clean:clean,resources:resources,compiler:compile,resources:testResources 以及 compiler:testCompile, 即在执行测试之前, 会先自动执行项目资源处理, 主代码编译, 测试资源处理, 测试代码编译等工作, 这是 Maven 生命周期的一个特性.

​ 由于 Maven 核心插件之一 compiler 插件默认支持 Java1.3, 因此需要配置插件支持 Java1.8

<build>

​ <plugins>

​ <plugin>

​ <groupId>org.apache.maven.plugins</groupId>

​ <artifactId>maven-compiler-plugin</artifactId>

​ <configuration>

​ <source>1.8<source>

​ <target>1.8</target>

​ </configuration>

​ </plugin>

​ </plugins>

</build>

2.3 打包和运行

​ 将项目进行编译, 测试之后, 下一个重要的步骤就是打包(package), 当没有指定打包类型时, 默认打包类型是 jar, 当执行 mvn clean package 命令,Maven 会在打包之前进行编译, 测试等操作, 之后通过 jar:jar 任务负责打包, 实际上就是 jar 插件的 jar 目标将项目主代码打包成一个 hello-world-1.0-SNAPSHOT.jar 的文件

​ 当其他项目需要直接引用这个 jar 时, 接下来需要一个安装的步骤, 执行 mvn clean install, 此命令执行了安装任务 install:install, 将项目输出的 jar 安装到了 Maven 本地仓库中, 而构件只有被下载到本地仓库后, 才能由 Maven 项目使用.

2.4 使用 Archetype 生成项目骨架

​ mvn archetype:generate, 当运行插件时, 格式为:groupId:artifactId:version:goal, 默认使用最新的稳定版

三. 坐标和依赖

3.1 坐标

  • Maven 的一大功能还是管理项目依赖, 为了能自动化解析任何一个 Java 构件,Maven 就必须将它们唯一标识, 这就依赖管理的底层基础 — 坐标
  • 在实际生活中, 我们可以将地址看作是一种坐标, 省, 市, 区, 街道等一系列信息可以唯一标识城市中的任意居住地址, 对应在 Maven 的世界中拥有非常巨大的构件, 也就是平常使用的一些 jar,war 等文件
  • Maven 定义了这样一规则: 世界上任意一个构建都可以使用 Maven 坐标唯一标识,Maven 坐标的元素包括

    • groupId: 定义当前 Maven 项目隶属公司的实际项目
    • artifactId: 该元素定义实际项目中的一个 Maven 模块, 推荐做法使用实际项目名称为 artifact 的前缀, 便于寻找实际构建
    • version: 该元素定义 Maven 项目当前所处的版本
    • package: 该元素定义 Maven 项目的打包方式, 默认为 jar 包
    • classifier: 该元素用来帮助定义构建输出一些附属构建, 附属构件与主构件对应
    <groupId>org.sonatype.nexus</groupId>
    <artifactId>nexus-indexer</artifactId>
    <version>2.0.0</version>
    <packaging>jar</packaging>
    
    <!--> 
    1. 以上 5 个元素,groupId,artifactId,version 是必须定义的,packaging 是可选的, 而 classifier 是不能直接定义的
    2. 项目构件的文件名格式:artifactId-version[-classifier].packaging

3.2 依赖

3.2.1 依赖的配置

  • 项目要引用 Maven 中的构建, 就需要在 pom 文件中, 通过坐标来使用依赖, 在根元素 project 下的 dependencies 可以包含一个或多个 dependency 元素, 用以声明一个或多个项目依赖

    • groupId,artifactId 和 version: 依赖的基本坐标, 对于任何一个依赖来说, 基本坐标是最重要的
    • type: 依赖的类型, 对应项目坐标定义的 packaging, 一般不必声明, 默认为 jar
    • scope: 依赖的范围
    • optional: 标记依赖是否可选
    • exclusions: 用来排除传递性依赖

3.2.2 依赖的范围

  • Maven 项目在编译项目主代码时需要使用一套 classpath, 如编译项目主代码时需要使用 spring-core, 该文件以依赖的方式被引入到 classpath 中. 其次,Maven 在编译和执行测试的时候会使用另外一套 classpath, 依赖同样会引入到相应的 classpath 中, 最后在运行 Maven 项目时, 又会使用一套 classpath
  • 依赖范围就是用来控制依赖与三种 classpath(编译 classpath, 测试 classpath, 运行 classpath)的关系

    • compile: 编译依赖范围, 没有指定时, 为默认依赖范围. 使用此依赖范围的 Maven 依赖, 对于编译, 测试运行三种 classpath 都有效, 典型的例子为:spring-core, 在编译, 测试, 运行阶段都需要使用该依赖
    • test: 测试依赖范围, 使用此依赖范围的 Maven 依赖, 只对于测试的 classpath 有效, 在编译主代码或者运行项目时将无法使用此类依赖, 典型的例子就是 JUnit, 它只有在编译器测试代码及运行测试的时候才需要
    • provided: 已提供依赖范围, 使用此依赖范围的 Maven 依赖, 对于编译和测试 classpath 有效, 但在运行时无效, 典型的例子就是 servlet-api, 编译和测试项目的时候需要该依赖, 但在运行项目的时候, 由于容器已经提供, 就不需要 Maven 重复引入了
    • runtime: 运行时依赖范围, 使用此依赖范围的 Maven 依赖, 对于测试和运行 classpath 有效, 但在编译主代码时无效, 典型的例子是 JDBC 驱动实现, 项目主代码的编译只需要 JDK 提供的 JDBC 接口, 只有在执行测试或运行项目的时候才需要实现上述接口的具体 JDBC 驱动
    • system: 系统依赖范围, 该依赖与三种 classpath 的关系, 和 provided 依赖范围完全一致, 但是, 使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径, 由于此类依赖不是通过 Maven 仓库解析的, 而且往往与本机系统绑定, 可能造成构建的不可移植性

      <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: 导入依赖范围, 该依赖范围不会对三种 classpath 产生实际的影响, 主要用于导入其他 pom 文件中的 dependencyManagement 元素对于依赖版本约束的内容

3.2.3 传递性依赖

  • 在使用 Maven 依赖时, 如 Spring Framework, 此依赖又会依赖其他的开源库, 因此实际中往往会下载一个很大的如 spring-framework-2.5.6-with-dependencies.zip 包, 这样往往就引入了很多不必要的依赖, 而 Maven 的传递性依赖机制就可以很好的解决这一问题

    • 当 A 项目引入一个 compile 范围的 B 依赖, 而 B 依赖中有一个 compile 范围的 C 依赖, 那么 C 依赖同样会成为 A 的 compile 范围依赖

  • 传递性依赖和依赖范围

    • 假设 A 依赖于 B,B 依赖于 C, 那么 A 对于 B 是第一直接依赖,B 对于 C 是第二直接依赖,A 对于 C 是传递性依赖
    • 当第二直接依赖是 compile 的时候, 传递性依赖与第一直接依赖范围一致
    • 当第二直接依赖是 test 的时候, 依赖不会得以传递
    • 当第二直接依赖是 provided 的时候, 只传递第一直接依赖范围为 provided 的依赖, 且传递性依赖范围为 provided
    • 当第二直接依赖是 runtime 的时候, 传递性地依赖的范围与第一直接依赖的范围一致, 但 compile 例外, 此时传递性依赖的范围为 runtime

3.2.4 可选依赖

  • 假如有这样一个依赖关系,A 依赖于 B,B 依赖于 X 和 Y,B 对于 X 和 Y 的依赖都是可选依赖, 根据传递性依赖的定义, 如果这三个依赖的范围都是 compile, 那么 X,Y 就是 A 的 compile 范围传递性依赖, 但是由于 X,Y 都是可选依赖, 所以依赖不会得以传递, 因此 X,Y 不会对 A 有任何影响

    • 为什么会有可选依赖这一特性呢?

      • 当 B 实现了两个特性, 特性一依赖于 X, 特性二依赖于 Y, 并且这两个特性是互斥的, 用户不可能同时使用两个特性, 比如 B 是一个持久层隔离工具包, 支持多种数据库, 在使用这个工具包的时候, 只会依赖一种数据库
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifact>mysql-connector-java</artifact>
        <version>5.6.0</version>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>postgresql</groupId>
        <artifactpostgresql</artifact>
        <version>8.4-701.jdbc3</version>
        <optional>true</optional>
    </dependency>
</dependencies>
  • 以上使用 <optional> 元素表示两个依赖为可选依赖, 他们只会对当前项目 B 产生影响, 当其他项目依赖于 B 时, 这两个依赖不会被传递, 所以当 A 依赖于 B 项目时, 如果要使用 mysql 数据库, 那么需要显式的声明 mysql-connector-java 这一依赖
  • 关于可选依赖, 在理想的情况下, 是不应该使用可选依赖的, 使用可选依赖的原因是某一个项目中实现了多个特性, 而根据单一职责原则, 应该针对不同的特性分别创建一个 Maven 项目, 用户根据需要选择使用其中某一个依赖

3.2.5 排除依赖

  • 传递性依赖会给项目隐式的引入很多依赖, 这极大的简化了项目依赖的管理, 但是有时候这种特性䧥带来问题

    • 例如, 当前项目有一个第三方依赖, 而这个依赖由于某些原因依赖了另一个类库的 SNAPSHOT, 那么整个 SNAPSHOT 就会成为当前项目的传递性依赖, 而 SNAPSHOT 的不稳定性会直接影响到当前的项目, 这时候就需要排除掉该 SNAPSHOT, 并且在当前项目中声明该类库的某个正式发布版

      <dependencies>
          <dependency>
              <groupId>com.lsy.myproject</groupId>
              <artifactId>myproject-a</artifactId>
              <version>1.0.0</version>
              <exclusions>
                  <exclusion>
                      <groupId>com.lsy.myproject</groupId>
                      <artifactId>myproject-b</artifactId>
                  </exclusion>
              </exclusions>
          </dependency>
          <dependency>
              <groupId>com.lsy.myproject</groupId>
              <artifactId>myproject-b</artifactId>
              <version>1.1.0</version>
          </dependency>
      </dependencies>

3.2.6 归类依赖

  • 当一些依赖来自同一个项目的不同模块, 这些依赖的版本都应该是相同的, 将来升级也是一起升级, 如 Spring Framework, 这时可以使用 properties 元素定义 Maven 属性

    <properties>
        <springframework.version>4.2.1</springframework.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <verison>${springframework.version}</verison>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <verison>${springframework.version}</verison>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <verison>${springframework.version}</verison>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <verison>${springframework.version}</verison>
        </dependency>
    </dependencies>

3.2.7 优化依赖

  • 在软件开发过程中, 通常会通过重构等方式不断优化自己代码, 同样, 对于 Maven 项目的依赖也需要对其进行优化

    • 去除多余的依赖
    • 显示声明某些必要的依赖
  • Maven 会自动解析所有项目的直接依赖和间接依赖, 并且根据规则判断每个依赖范围, 对于一些依赖冲突, 也能进行调整, 以确保任何一个构建只有唯一的版本在依赖中存在, 此称为解析依赖

    • mvn dependency:list 查看当前项目的已解析依赖
    • mvn dependency:tree 以树结构查看已解析依赖
    • mvn dependency:analyze 解析依赖

      • Used undeclared dependencies: 指项目中使用到的, 但是没有显示声明的依赖, 这种依赖意味着潜在的风险, 当前项目直接在使用它们, 所以需要显示声明任何项目中直接用到的依赖
      • Unused declared dependencies: 指项目中未使使用的. 但显示声明的依赖

四. 仓库

  • 之前已经介绍了 Maven 的坐标和依赖, 坐标和依赖是任何一个构件在 Maven 世界中的逻辑表示方式, 而构建的物理表示方式是文件,Maven 通过仓库来同一管理这些文件
  • 在 Maven 世界中, 任何一个以依赖, 插件或项目构建的输出, 都可以成为构件

    • 例如:log4j-1.2.15.jar,maven-compiler-plugin-2.0.2.jar 或项目打包后的 myproject-1.0.0-SNAPSHOT.jar
  • 在一台工作站上, 可能会有几十个项目, 所有项目在 /lib 目录下都会有自己所需的依赖包, 而这些依赖中都有大量的重复, 每个项目各自存储自己所需的依赖包不仅造成磁盘空间的浪费, 而且也难于同一管理, 文件的复制等操作
  • 得益于坐标机制, 任何 Maven 项目使用任何一个构建的方式都是相同的, 因此,Maven 可以在某个位置统一存储所有 Maven 项目共享的构件, 这个统一的位置就是仓库, 为了实现重用, 项目构建完毕后生成的构建也可以安装或部署到仓库中

4.1 仓库的布局

  • 任何一个构建都有其唯一的坐标, 根据这个坐标可以定义其在仓库中的唯一存储路径, 这就是 Maven 的仓库布局方式, 如 log4j:log4j:1.2.15 这一依赖, 其对应的仓库路径为 log4j/log4j/1.2.15/log4j-1.2.15.jar

    • 路径与坐标的关系为:groupId/artifactId/version/artifactId-verision.packaging

4.2 仓库的分类

  • 对于 Maven 来说, 仓库分为两类

    • 本地仓库
    • 远程仓库

      • 中央仓库:Maven 核心自带的仓库服务器, 它包含了绝大部分开源的构件
      • 私服: 另一种特殊的远程仓库, 为了节省宽带和时间, 应该在局域网内部构建一个私有仓库服务器, 用其代理所有外部的远程仓库, 内部项目部署到私服上供其它项目使用
      • 其他公共库
  • 当 Maven 根据坐标寻找构件时, 它首先会查看本地仓库, 如果本地仓库存在此构件, 则直接使用, 如果本地仓库不存在此构件, 或者需要查看是否有更新的构件版本,Maven 就会去远程仓库查找, 发现需要的构件后下载到本地仓库再使用, 若本地和远程都没有, 则报错

4.3 本地仓库

  • 一般来说, 在 Maven 项目目录下, 没有注入 lib/ 这样用来存放依赖文件的目录, 当 Maven 执行编译或测试时, 如果需要使用依赖文件, 它总是基于坐标使用本地仓库的依赖文件

    • 默认情况, 在用户目录下路径名为.m2/repository/ 的仓库目录, 当想自定义本地仓库目录地址时, 可以编辑~/.m2/setting.xml, 设置 localRepository 元素来指定仓库地址

      <settings>
          <localRepository>D:/java/repository/</localRepository>
      </settings>
    • 默认情况下,~/.m2/settings.xml 文件是不存在的, 用户需要从 Maven 安装目录复制 $M2_HOME/conf/settings.xml 文件再编辑, 建议不要直接修改全局目录的 settings.xml, 而是在用户目录下进行修改
  • 一个构建只有在本地仓库中, 才能由其它 Maven 项目使用

    1. 依赖 Maven 从远程仓库下载到本地仓库中
    2. 将本地项目的构件安装到 Maven 本地仓库中 mvn clean install

4.4 远程仓库

  • 安装好 Maven 后, 如果不执行任何 Maven 命令, 本地仓库目录是不存在的, 只有输入第一条 Maven 命令后,Maven 才会创建本地仓库, 并根据配置和需要, 从远程仓库下载至本地仓库

    • 这就好比藏书, 本地仓库好比书房, 远程仓库好比书店, 我需要读书时先去书房找, 当书房没有时, 就去书店买回放到书房, 并且一般对每个人来说, 书房只有一个, 而外面的书店可以有多个

4.4.1 中央仓库

  • 最原始的本地仓库是空的, 所以 Maven 必须知道至少一个可用的远程仓库, 才能在执行 Maven 命令时下载到需要的构建, 中央仓库就是这样一个默认的远程仓库, 可以通过解压工具打开 $M2_HOME/lib/maven-model-builder-3.0.jar 中的 org/apache/maven/model/pom-4.0.0.xml 文件

    <repositories>
        <repository>
            <id>central</id>
            <name>Maven Repository Switchboard</name>
            <url>http://repo1.maven/org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enable>false</enable>
            </snapshots>
        </repository>
    </repositories>
    • 这段配置是所有 Maven 项目都会继承的超级 Pom 文件, 这段配置使用 id central 对中央仓库进行唯一标识, 其名称为 Maven Repository Switchboard, 它使用 default 仓库布局, 也就是在之前介绍的仓库布局, 最后 snapshots 元素表示不从该仓库下载快照版

4.4.2 私服

  • 私服是一种特殊的远程仓库, 它是架设在局域网内的仓库服务, 私服代理广域网上的远程仓库, 供局域网内的 Maven 用户使用, 当 Maven 需要下载构建的时候, 它从私服请求, 如果私服上不存在该构件, 则从外部的远程仓库下载, 缓存在私服上之后, 再为 Maven 的下载提供服务

    • 此外, 一些无法从外部下载的构件也可以从本地上传到私服上供大家使用
  • 私服的好处

    • 节省外网带宽: 建立私服同样可以减少组织自己的开支, 大量的对于外部仓库的重复请求会消耗很大的带宽, 利用私服代理外部仓库之后, 对外的重复构件下载便可以消除, 即降低外网带宽的压力
    • 加速 Maven 构件: 不停的连接请求外部仓库是十分耗时的, 但 Maven 的一些内部机制 (如快照更新检查井) 要求 Maven 在执行构建时不停的检查远程仓库数据, 因此, 当项目配置很多外部仓库时, 构建速度就会降低
    • 部署第三方构件: 当某个构件无法从任何一个外部远程仓库获取, 建立私服之后, 便可以将这些构件部署到这个内部的仓库中, 供内部的 Maven 项目使用
    • 提高稳定性, 增强控制:Maven 构建高度依赖远程仓库, 因此, 大哥 Internet 不稳定的时候,Maven 构建也会变得不稳定, 甚至无法构建, 使用私服后, 即是短暂时没有 Internet 连接, 由于私服中有大量缓存,Maven 依然可以正常运行, 并且私服中有很多额外的权限功能控制
    • 降低中央仓库的负荷: 每天中央仓库都需要面对大量的下载请求, 使用私库可以降低对于中央仓库的负荷
  • 远程仓库的配置

    • 很多情况下, 默认的中央仓库无法满足项目的需求, 可能项目需要的构件存在于另一个远程仓库中, 可以通过 Pom/settings 文件中来配置该仓库

      <!--> POM 文件 </!-->
      <project>
          .....
          <repositories>
              <repository>
                  <id>jboss</id>
                  <name>JBoss Repository</name>
                  <url>http://repository.jboss.com/maven2/</url>
                  <release>
                      <enabled>true</enabled>
                  </release>
                  <snapshots>
                      <enabled>false</enabled>
                  </snapshots>
                  <layout>default</layout>
              </repository>
          </repositories>
      </project>
      
      <!--> settings 文件 </!-->
      <profiles>
          <profile>
              <id>dev</id>
              <activation>
                  <activatedByDefault>true</activatedByDefault>
              </activation>
              <repositories>
              <repository>
                  <id>jboss</id>
                  <name>JBoss Repository</name>
                  <url>http://repository.jboss.com/maven2/</url>
                  <release>
                      <enabled>true</enabled>
                  </release>
                  <snapshots>
                      <enabled>false</enabled>
                  </snapshots>
                  <layout>default</layout>
              </repository>
          </repositories>
          </profile>
      </profiles>
      • 在 repositories 元素下, 可以使用 repository 子元素声明一个或多个远程仓库, 并且声明一个 id, 任何一个仓库声明的 id 必须是唯一 id
      • Maven 自带的中央仓库使用的 id 为 central, 如果其他仓库使用该 id, 则会覆盖中央仓库的配置, 该配置中的 url 指向了仓库的地址
      • 该配置中的 release 和 snapshots 元素用来控制 Maven 对于发布版构件和快照版构件的下载, 对于 release 和 snapshots 元素来说除了 enabled 子元素, 还有 updatePolicy 和 checksumPolicy 元素

        • updatePolicy: 用来配置 Maven 从远程仓库检查更新的频率, 默认为 daily

          • daily: 每天
          • never: 从不
          • always: 每次
          • interval :X : 每隔 X 分钟一次
        • checksumPolicy: 配置 Maven 检查文件失败时的策略, 默认为 warn

          • fail:Maven 遇到验证和错误就让构建失败
          • warn:Maven 遇到验证和错误就发出警告信息
          • ignore:Maven 遇到验证和错误时完全忽略
<snapshots>
    <enabled>true</enabled>
    <updatePolicy>daily</updatePolicy>
    <checksumPolicy>ignore</checksumPolicy>
</snapshots>
  • 远程仓库的验证

    • 大部分远程仓库无需认证就可以访问, 但出于安全考虑, 我们需要提供一些认证信息才能访问一些远程仓库
    • 配置认证信息和配置仓库信息不同, 仓库信息可以直接配置在 POM 文件中, 但是认证信息必须配置在 settings.xml 文件中
    • 其中 server 元素的 id 必须与 POM 文件中需要认证的 repository 元素的 id 完全一致

      <settings>
          ......
          <servers>
              <server>
                  <id>my-project</id>
                  <username>user</username>
                  <password>password</password>
              </server>
          </servers>
          .......
      </settings>
  • 部署至远程仓库

    • 私服一大作用就是部署第三方构件, 包括组织内部生成的构件以及无法从外部仓库直接获取的构件, 无论是日常开发中生成的构件, 还是正式版本发布的构件, 都需要部署到仓库中

      • distributionManagement 包含 repository 和 snapshotRepository 子元素, 前者表示发布版构件的仓库, 后者表示快照版的仓库
      • 往远程仓库部署构件时, 往往需要认证, 认证配置需在 settings.xml 中创建一个 server 元素, 其 id 与仓库的 id 匹配, 无论从远程仓库下载构件, 还是部署构件至远程仓库, 当需要认证时, 配置方式都是一样的

        <!--> 在 POM 文件中配置 distributionManagement </!-->
        <project>
            .....
            <distributionManagement>
                <repository>
                    <id>jboss-release</id>
                    <name>JBoss Release Repository</name>
                    <url>http://192.168.1.99/content/repository/jboss-release</url>
                </repository>
                <snapshotRepository>
                    <id>jboss-snapshots</id>
                    <name>JBoss Snapshots Repository</name>
                    <url>http://192.168.1.99/content/repository/jboss-snapshots</url>
                </snapshotRepository>
            </distributionManagement>
            .....
        </project>

4.5 快照

  • 在 Maven 的世界中, 任何一个项目或者构件都必须有自己的版本, 版本的值可能是 1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT 或者 2.1-20191214.221414-13, 其中 1.0.0,1.3-alpha- 4 和 2.0 是稳定的发布版本, 而 2.1-SNAPSHOT 和 2.1-20091214.221414-13 是不稳定的快照版本
  • 对于一个稳定的版本, 如果仓库中已经包含, 那么 Maven 就不会再去对照远程仓库进行更新, 除非每次执行 Maven 命令前, 清除本地仓库中等待稳定版本, 而对于一个正在迭代的项目, 如果要实时更新版本的内容就需要频繁的修改新的版本名称, 这样是对版本号的滥用
  • 针对这种情况, 使用快照版时,Maven 会自动为构件打上时间戳, 因此,Maven 就能随时找到仓库中该构建最新版本的文件, 一旦有新的更新, 就会去同步到本地仓库. 当项目经过完善的测试后需要发布的时候, 再将快照版本更改为发布版本
  • 快照版本只应该在组织内部的项目或模块之间依赖使用, 因为这时, 组织对这些快照版本的依赖具有完全的理解和控制权, 项目不应该依赖任何组织外部的快照版本依赖, 由于快照版本的不稳定性, 随时可能发生变化, 这样的依赖会有潜在的危险

4.6 从仓库解析依赖的机制

  • 当本地仓库没有依赖构件的时候,Maven 会自动从远程仓库下载, 当依赖版本为快照版本时,Maven 会自动找到最新的快照, 这背后的依赖机制可以概括如下

    1. 当依赖的范围时 system 的时候,Maven 直接从本地文件系统解析构件
    2. 根据依赖坐标计算仓库路径后, 尝试从本地仓库寻找构件, 如果发现相应的构件, 则解析成功
    3. 在本地仓库不存在相应构件的情况下, 如果依赖的版本时显式的发布版本构件, 如 1.2,2.1-beta- 1 等, 则遍历所有的远程仓库, 发现后, 下载并解析使用
    4. 如果依赖的版本时 RELEASE 或 LATEST, 则基于更新策略读取所有远程仓库的元数据 groupId/artifact/maven-metadata.xml, 将其与本地仓库的对应元数据合并后, 计算出 RELEASE 或 LATEST 真实的值, 然后基于这个真实的值检查本地仓库和远程仓库
    5. 如果依赖的版本是 SNAPSHOT, 则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml, 将其与本地仓库的对应数据合并后, 得到最新的快照版本的值, 然后基于该值检查本地仓库, 或者从远程仓库下载
    6. 如果最后解析得到的构件版本是时间戳格式, 如 1.4.1-20191104.121455-8, 则复制其时间戳格式的文件至非时间戳格式, 如 SNAPSHOT, 并使用该非时间戳格式的构件

      <!-->  基于 groupId 和 artifactId 的 maven-metadata.xml </!-->
      <?xml version="1.0" encoding="UTF-8"?>
      <metadata>
          <groupId>org.sonatype.nexus</groupId>
          <artifactId>nexus</artifactId>
          <versioning>
              <latest>1.4.2-SNAPSHOT</latest>
              <release>1.3.7</release>
              <versions>
                  <version>1.3.5</version>
                  <version>1.3.6</version>
                  <version>1.3.7</version>
                  <version>1.4.0-SNAPSHOT</version>
                  <version>1.4.1-SNAPSHOT</version>
                  <version>1.4.2-SNAPSHOT</version>
              </versions>
              <lastUpdated>20191214221133</lastUpdated>
          </versioning>
      </metadata>
  • 该 XML 文件列出了仓库中存在的构件所有可用的版本, 同时 latest 元素指向了这些版本中最新的那个版本 1.4.2-SNAPSHOT, 而 release 元素指向了这些版本中最新的发布版本 1.3.7,Maven 通过合并多个远程仓库及本地仓库的元数据, 就能计算出基于所有仓库的 latest 和 release

    • 需要注意的是, 在依赖声明使用 LATEST 和 RELEASE 是不推荐的做法, 因为 Maven 随时可能解析到不同的构件, 且 Maven 不会明确告诉用户这样的变化

4.7 镜像

  • 如果仓库 X 可以提供仓库 Y 存储的所有内容, 那么就可以认为 X 是 Y 的一个镜像, 由于地理位置的因素, 中央仓库的下载速度会比较慢, 这时我们可以配置 Maven 使用镜像来代替中央仓库, 编辑 settings.xml

    <settings>
        ......
        <mirrors>
            <mirror>
                <id>maven.net.cn</id>
                <name>one of the central mirror in china</name>
                <url>http://maven.net.cn/content/groups/public/</url>
                <mirrorOf>central</mirrorOf>
            </mirror>
        </mirrors>
        ......
    </settings>
    • <mirrorOf> 的值为 central, 表示该配置为中央仓库的镜像, 任何对于中央仓库的请求都会转至该镜像, 用户也可以使用同样的方法配置其他仓库的镜像, 另外三个元素 id,name,url 与一般仓库配置无异, 表示该镜像仓库的唯一标识, 名称以及地址

      • 若该镜像要进行验证, 即基于该 id 配置仓库认证
<settings>
    .....
    <mirrors>
        <mirror>
            <id>internal-repository</id>
            <name>Internal Repository Mananger</name>
            <url>http://192.168.1.100/maven2/</url>
            <mirrorOf>*</mirrorOf>
        </mirror>
    </mirrors>
    .....
</settings>
  • 以上 mirrorOf 元素的值为 *, 表示该配置是所有 Maven 仓库的镜像, 对于任何远程仓库的请求都会被转至该指定的仓库, 如果镜像仓库需要验证, 则配置一个 id 为 internal-repository 的 <server> 即可

    • <mirrorOf>*<mirrorOf>: 匹配所有远程仓库
    • <mirrorOf>external:*<mirrorOf>: 匹配所有远程仓库, 使用 localhost 的除外, 使用 file:// 协议的除外, 也就是说, 匹配所有不在本机上的远程仓库
    • <mirrorOf>repo1,repo2<mirrorOf>: 匹配仓库 repo1 和 repo2, 用逗号分隔多个仓库
    • <mirrorOf>*,! repo1<mirrorOf>: 匹配所有的远程仓库, 除 repo1 外, 使用感叹号将仓库从匹配中排除
  • 需要注意的是, 由于镜像仓库完全屏蔽了被镜像仓库, 当镜像仓库不稳定或停止服务时,Maven 仍将无法访问被镜像仓库, 因此无法下载构件

五. 生命周期和插件

5.1 Maven 的生命周期

  • 在 Maven 出现之前, 项目构建的生命周期就已经存在, 但是不同的公司和开发人员虽然同样在做构件工作, 其对于不同的项目却不能够重用, 只能重新定制开发, 而 Maven 的生命周期就是为了对所有的构件过程进行抽象和统一

    • 这个生命周期包含了项目的清理, 初始化, 编译, 测试, 打包, 集成测试, 验证, 部署和站点生成等几乎所有的构件步骤, 也就是说几乎所有项目的构件, 都能映射到这样一个生命周期上
  • Maven 的生命周期是抽象的, 这意味着生命周期本身不做任何实际的工作, 在 Maven 的设计中, 实际的任务 (如编译主代码) 都交由插件完成, 这种思想和设计模式的模方法类似, 在父类中定义算法的整体结构, 子类可以通过实现或重写父类的方法来控制实际的行为

    public abstract class Template{public void build(){initialize();
            compile();
            test();
            packagee();
            integrationTest();
            deploy();}
        
        protect abstract void initialize();
        protect abstract void compile();
        protect abstract void test();
        protect abstract void packagee();
        protect abstract void integrationTest();
        protect abstract void deploy();}
    • 在 Maven 的生命周期中抽象了各个步骤, 定义了他们的次序, 但是没有提供具体的实现, 而通过插件机制为每个构件步骤绑定一个或多个插件行为, 而且 Maven 为大多数构件步骤都绑定了默认的插件, 例如, 针对编译的 maven-compiler-plguin, 针对测试的 maven-surefire-plugin 等
    • Maven 定义的生命周期和插件机制一方面保证了所有 Maven 项目有一致的构件标准, 另一方面又通过默认的插件简化和稳定实际项目的构件, 此外, 该机制还提供了足够的扩展, 用户可以通过配置现有的插件或自定义插件来自定义构件行为
  • 三套声明周期

    • Maven 拥有三套相互独立的生命周期, 他们分别为 clean,default 和 site, 每个生命周期包含一些阶段, 这些阶段是有序的, 并且后面的阶段依赖于前面的阶段, 但是三套声明周期本身是互相独立的

      1. clean 生命周期: 清理项目

        • pre-clean: 执行一些清理前需要完成的工作
        • clean: 清理上次构件生成的文件
        • post-clean: 执行一些清理后需要完成的工作
      2. default 声明周期: 定义了真正的构件所需执行的所有步骤

        • validate
        • initialize
        • generate-sources
        • process-sources: 处理项目主资源文件, 一般来说针对 /src/main/resources 目录的内容进行变量替换等工作后, 复制到项目输出的主 classpath 目录中
        • compile: 编译项目的主源码, 一般来说针对 /src/main/java 目录下的 Java 文件至目录输出的主 classpath 目录中
        • process-classes
        • generate-test-sources
        • process-test-sources: 处理项目测试资源文件, 一般来说针对 /src/test/resources 目录的内容进行变量替换工作后, 复制到项目输出的测试 classpath 目录
        • test-compile: 编译项目的测试代码, 一般来说针对 /src/test/java 目录下的 java 文件至输出的测试 classpath 目录中
        • test: 使用单元测试框架运行测试, 测试代码不会被打包或部署
        • prepare-package
        • package: 接收编译好的代码, 打包成可发布的格式, 如 jar
        • pre-integration-test
        • integration-test
        • post-integration-test
        • verify
        • install: 将包安装到 Maven 本地仓库, 供本地其他 Maven 项目使用
        • deploy: 将最终的包复制到远程仓库, 供其他开发人员和 Maven 项目使用
      3. site 声明周期: 建立和发布项目站点,Maven 能够基于 POM 所包含的信息, 自动生成一个友好的站点供交流和发布项目信息

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

      • 从命令行执行 Maven 任务最主要方式就是调用 Maven 的生命周期, 各个生命周期是相互独立的, 而生命周期的阶段是有前后依赖关系的

        • mvn clean: 该命令调用 clean 生命周期的 clean 阶段, 实际执行阶段为 pre-clean 和 clean
        • mvn test: 该命令调用 default 生命周期的 test 阶段, 实际执行的阶段为 default 生命周期的 validate,initialize 直到 test 的所有阶段, 这也解释为什么在执行测试的时候, 项目代码能够自动编译
        • mvn clean install: 该命令调用 clean 生命周期的 clean 阶段和 default 生命周期的 install 阶段
        • mvn clean deploy site-deploy: 该命令调用 clean 生命周期的 clean 阶段,default 生命周期的 deploy 阶段和 site 生命周期的 site-deploy 阶段

5.2 插件

5.2.1 插件目标和绑定

  • Maven 的核心仅仅定义了抽象的生命周期, 具体的任务是交由插件完成的, 插件以独立的构件形式存在

    • 对于插件本身而言, 为了能够复用代码, 它往往能够完成多个任务, 为每个功能编写一个插件显然不合理, 因为这些任务背后有大量可复用的代码, 因此, 这些功能聚集到一个插件里面, 每个功能就是一个插件目标
  • Maven 的生命周期和插件相互绑定, 用以完成实际的构件任务, 具体而言, 是生命周期的阶段与插件的目标相互绑定, 以完成某个具体的构件任务

    • 例如: 编译这一任务对应了 default 生命周期的 compile 这一阶段, 而 maven-compiler-plugin 这一插件的 compile 目标能完成此任务

  • 自定义绑定

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

      • 当需要创建项目的源码 jar 包,maven-source-plugin 可以帮我们完成任务, 但是内置的插件绑定关系中并没有涉及这一任务, 因此需要自行配置, 它的 jar-no-fork 目标能将项目主代码打包成 jar 文件, 可以将其绑定到 default 生命周期的 verify 阶段, 在执行完集成测试和安装构件之前创建源码 jar 包

        <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>
      • 在 POM 的 build 元素下的 plugins 子元素中声明插件的使用, 上例用到了是 maven-source-plugin, 其 groupId 为 org.apache.maven.plugins, 用户总是应该声明一个非快照版本, 这样可以避免由于插件版本变化造成的构件不稳定性

        • 除了基本的插件坐标声明外, 还有插件执行配置,executions 下每个 execution 子元素可以用来配置执行一个任务, 该例配置了一个 id 为 attach-sources 的任务, 通过 phase 配置将其绑定到 verify 生命周期阶段上, 再通过 goals 配置指定要执行的插件目标, 在运行 mvn verify 时就会执行该任务
        • 有时候. 即是不通过 phase 元素配置生命周期阶段, 插件目标也能绑定到生命周期中去, 原因是: 有很多插件的目标在编写时就已经定义了默认绑定阶段, 可以使用 maven-help-plugin 查看详细信息

          • mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1 -Ddetail

            • Bound to phase : package 默认绑定到 package 生命周期
      • 当插件目标被绑定到不同生命周期阶段的时候, 其执行顺序会由生命周期阶段的先后顺序决定, 如果多个目标被绑定到同一个阶段, 他们的顺序就由插件声明的先后顺序决定

5.2.2 插件配置

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

    • 命令行插件配置

      • 在日常的 Maven 使用中, 我们通常从命令行输入并执行 Maven 命令, 很多插件目标的参数都支持从命令行配置, 用户可以在 Maven 命令中使用 - D 参数, 并伴随一个参数键 = 参数值的形式, 来配置插件目标的参数

        • mvn install -Dmaven.test.skip = ture: 给 maven.surefire-plugin 提供一个 maventest.skip 参数, 当参数为 true 时, 就会跳过执行测试

          • 参数 - D 是 Java 自带的, 其功能是通过命令行设置一个 Java 系统属性,Maven 简单地重用了该参数, 在准备插件的时候检查系统属性来实现插件参数的配置
    • POM 中插件全局配置

      • 并不是所有的插件参数都适合用命令行配置, 有些参数的值从项目创建到项目发布都不会改变, 或者说很少改变, 这种情况下在 POM 文件中一次性配置比重复在命令行输入更合理

        • 用户可以在声明插件的时候, 对插件进行一个全局的配置, 所有基于该插件的目标任务, 都会使用这些配置, 如:maven-compiler-plugin 来编译 1.8 版本的源文件, 生成与 JVM1.8 兼容的字节码文件

          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifact>maven-compiler-plugin</artifact>
                      <version>2.1</version>
                      <configuration>
                          <source>1.8</source>
                          <target>1.8</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>

5.2.3 获取插件信息

  • 当遇到一个构建任务时, 不仅需要知道去哪里找到合适的插件, 还需要详细的了解插件的配置点, 由于 Maven 的插件非常多, 其中大部分没有完善的文档, 因此, 通过帮助命令来了解插件显得非常重要

    1. 使用 maven-help-plugin 描述插件

      • 除了访问在线的插件文档外, 可以借助 maven-help-plugin 来获取插件的详细信息

        • mvn help:decribe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1

          • 这里执行的是 maven-help-plugin 的 decribe 目标, 在参数 plugin 中输入需要描述插件的 groupId,artifactId 和 version,Maven 在命令行的输出 maven-compiler-plugin 的简要信息, 包括插件的坐标, 目标前缀和目标等信息
      • 在描述插件时, 可以省去版本信息,Maven 会自动获取最新版本来进行表述, 并可以用目标前缀来替换坐标

        • mvn help:describe -Dplugin=compiler
      • 如果仅仅描述某个插件目标的信息, 则加上 goal 参数, 更详细的信息, 则加上 detail 参数

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

5.2.4 插件解析机制

  • 为了方便用户使用和配置插件,Maven 不需要用户提供完整的插件坐标信息, 就可以解析得到正确的插件

    • 插件仓库

      • 与依赖构件一样, 插件构件同样基于坐标存储在 Maven 仓库中, 在需要的时候 Maven 先从本地仓库寻找, 如果不存在, 则从远程仓库查找, 找到插件之后, 再下载到本地仓库使用
      • 不同于 repositories 以及 repository 子元素, 插件的远程仓库使用 pluginRepositories 和 pluginRepository 配置

        <pluginRepositories>
            <pluginRepository>
                <id>central</id>
                <name>Maven Plugin Resository</name>
                <url>http://repo1.maven.org/maven2</url>
                <layout>default</layout>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <release>
                    <enabled>true</enabled>
                    <updatePolicy>never</updatePolicy>
                </release>
            </pluginRepository>
        </pluginRepositories>
  • 插件默认的 groupId

    • 在 POM 中配置插件时, 如果该插件时 Maven 的官方插件(即 groupId 为 org.apache.maven.plugins), 就可以省略 groupId,Maven 在解析该插件的时候, 会自动使用默认 groupId 不起

      <build>
          <plugins>
              <plugin>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>2.1</version>
                  <configuration>
                      <source>1.8</source>
                      <target>1.8</target>
                  </configuration>
              </plugin>
          </plugins>
      </build>
  • 解析插件版本

    • 同样为了简化插件的配置和使用, 在用户没有提供插件版本的情况下,Maven 会自动解析插件版本

      • 首先 Maven 在超级 POM 中为所有核心插件设定了版本, 超级 POM 是所有 Maven 项目的父 POM, 所有项目都继承这个超级 POM 配置, 因此即使用户不加任何配置,Maven 在使用核心插件时, 它的版本就已经确定了
      • 如果用户使用的某个插件没有设定版本, 并且这个插件也不属于核心插件,Maven 机会去检查所有仓库中可用的版本, 通过仓库元数据 groupId/artifactId/maven-metadata.xml 文件, 如 maven-compiler-plugin 插件为例, 它在 org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml

        <?xml version="1.0" encoding="UTF-8"?>
        <metadata>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <versioning>
                <latest>2.1</latest>
                <release>2.1</release>
                <versions>
                    <version>2.0-beta-1</version>
                    <version>2.1</version>
                    <version>2.2</version>
                    <version>2.3</version>
                </versions>
                <lastUpdated>20190102092331</lastUpdated>
            </versioning>
        </metadata>
      • Maven 遍历本地仓库和所有远程仓库, 将仓库元数据合并后, 就能计算出 latest 和 release 的值,maven3 将版本解析到最新的非快照版, 如 2.1

5.2.5 解析插件前缀

  • Maven 命令支持使用插件前缀来简化插件的使用, 插件前缀和 groupId:artifact 是一一对应的, 这种匹配关系存储在仓库元数据中, 与之前提到的 groupId/artifactId/maven-metadata.xml 不同, 这里仓库元数据为 groupId/maven-metadata.xml, 一般插件都位于 /org/apache/maven/plugins/ 和 org/code-haus/mojo/, 可以通过 settings.xml 配置让 Maven 检查其他 groupId 上的插件仓库元数据

    <settings>
        <pluginGroups>
            <pluginGroup>com.my.plugins</pluginGroup>
        </pluginGroups>
    </settings>
  • 在仓库的元数据文件中可以看到插件的前缀定义

    <metadata>
        <plugins>
            <plugin>
                <name>Maven Clean Plugin</name>
                <prefix>clean</prefix>
                <artifact>maven-clean-plugin</artifact>
            </plugin>
            <plugin>
                <name>Maven Compiler Plugin</name>
                <prefix>compile</prefix>
                <artifact>maven-compile-plugin</artifact>
            </plugin>
            <plugin>
                <name>Maven Dependency Plugin</name>
                <prefix>dependency</prefix>
                <artifact>maven-dependency-plugin</artifact>
            </plugin>
        </plugins>
    </metadata>

六. 聚合与继承

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

6.1 集合

  • aggregator

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lsy.project</groupId>
    <artifact>project-aggregator</artifact>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Aggregator</name>
    
    <modules>
        <module>project-email</module>
        <module>project-register</module>
    </modules>
    • 对于聚合模块, 其打包方式 packaging 的值必须为 pom
    • 通过 modules 元素来实现聚合, 用户可以通过在一个打包方式为 pom 的 Maven 项目中声明任意数量的 module 元素来实现模块的聚合, 这里每个 module 的值都是一个当前 POM 的相对目录

      • 如 aggregator 的 POM 路径为 …/project-aggregator/pom.xml, 那么 project-email 对应 的目录为 …/project-aggregator/project-email/, 并且目录中包含了 pom.xml,src/main/java,src/test/java 等内容, 离开了 project-aggregator 也能独立创建 =
    • 聚合模块和其他模块的目录结构并非一定要父子关系, 也可以是平行关系

      <modules>
          <module>../prject-email</module>
          <module>../project-register</module>
      </modules>
    • 当在聚合模块中执行 mvn clean install 时,Maven 首先解析聚合模块的 POM, 分析要构件的模块, 并计算出一个反应堆构件顺序(Reactor Build Order), 然后根据这个顺序构件各个模块
    • 聚合模块的构件顺序

      • Maven 按序读取 POM 文件, 如果 POM 没有依赖模块, 那么就构件模块, 否则就先构件其依赖模块

6.2 继承

  • 从以上的聚合模块和其他模块中可以看到, 多个被管理模块的 POM 文件中会有大量重复的相同配置, 他们有相同的 groupId 和 version, 相同的依赖, 相同的插件配置, 而通过 POM 文案的继承可以消除重复

    • project-parent 父模块

      <modelVersion>4.0.0</modelVersion>
      <groupId>com.lsy.project</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <packaging>pom</packaging>
      <name>Parent</name>
      
      • 父模块的 POM 文件使用与其他模块一直的 groupId 和 version, 它的 packaging 方式为 pom, 与聚合模块一致
      • 父模块主要是为了消除配置的重复, 因此它本身不包含除 POM 文件之外的项目文件, 也就不需要 src/main/java 之类的文件夹了
    • project-email 子模块

      <parent>
          <groupId>com.lsy.project</groupId>
          <artifactId>project-parent</artifactId>
          <version>1.0.0-SNAPSHOT</version>
          <relativePath>../project-parent/pom.xml</relativePath>
      </parent>
      
      <artifactId>project-email</artifactId>
      <name>Email</name>
      
      <dependencies>
          .....
      </dependencies>
      • 使用 parent 元素声明父模块,parent 下子元素 groupId,artifactId,version 指定父模块的坐标, 元素 relativePath 表示父模块 POM 的相对路径, 表示 Email 模块和其父模块是在平行的目录下

        • 当构件项目时,Maven 会首先根据 relativePath 检查父 POM 文件, 如果找不到再从本地仓库找,relativePath 的默认值为../pom.xml, 也就是说,Maven 默认父 POM 在上一层目录
      • 子模块没有 groupId 和 version, 是因为子模块隐式的从父模块继承了这两个元素, 从而消除了不必要的配置
  • 可继承的 POM 元素

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

    • dependencies 元素是可以被继承的, 说明依赖是会被继承的, 所以我们可以将子模块共有的依赖配置到父模块中, 子模块就可以移除这些依赖, 简化配置
    • 上述方法是可行的, 但是可能将来会有新的模块并不需要父模块中的一些依赖, 这就会产生不合理的现象, 从而 Maven 提供了 dependencyManagement 元素, 既能让子模块继承到父模块的依赖配置, 又能保证子模块依赖使用的灵活性

      • 在 dependencyManagement 元素下的依赖声明不会引入实际的依赖, 不过它能够约束 dependencies 下的依赖使用
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.lsy.project</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <packaging>pom</packaging>
      <name>Parent</name>
      
      <properties>
          <springframework.version>4.3.1</springframework.version>
      </properties>
      
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-core</artifactId>
                  <version>${springframwork.version}</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-beans</artifactId>
                  <version>${springframwork.version}</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>${springframwork.version}</version>
              </dependency>
          </dependencies>
      </dependencyManagement>
* 这里使用 dependencyManagement 声明的依赖既不会给 parent 模块引入依赖, 也不会给子模块引入依赖, 不过这段配置是会被继承的

* 子模块 POM

  ```xml
  <parent>
      <groupId>com.lsy.project</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <relativePath>../project-parent/pom.xml</relativePath>
  </parent>
  
  <artifactId>project-email</artifactId>
  <name>Email</name>
  
      <dependencies>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-core</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-beans</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
          </dependency>
      </dependencies>
  ```

  * 使用这种依赖管理机制, 可以在父 POM 中使用 dependencyManagement 声明依赖能够统一项目规范中的依赖版本, 当版本在父 POM 中声明之后, 子模块使用依赖时就不需要声明了. 也不会发生多个子模块使用依赖版本不一致的情况

    * scoper 元素的 import 依赖范围只在 dependencyManagement 元素下才有效果, 使用该范围的依赖通常指向一个 POM 文件, 作用是将 POM 中的 dependencyManagement 配置导入并合并到当前 POM 的 dependencyManagement 元素中
    * 所以除了复制配置和继承父模块这两种方式外, 还可以通过 import 范围依赖导入这一配置

    ```xml
    <dependencyManagement>
        <dependencies>
            <dependency>com.lsy.project</dependency>
            <artifactId>project-parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependencies>
    </dependencyManagement>
    ```


  • 插件管理

    • Maven 提供了 dependencyManagement 元素帮助管理依赖, 类似的,Maven 也提供了 pluginManagement 元素管理插件, 该元素中配置的依赖同样不会造成实际的插件调用行为, 而 POM 文件中配置了真正的 plugin 元素, 并且 groupId 和 artifact 一致时, 才会产生实际的插件行为
    • 父模块

      <build>
          <pluginManagement>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-source-plugin</artifactId>
                      <version>3.1.1</version>
                      <executions>
                          <execution>
                              <id>attach-source</id>
                              <phase>verify</phase>
                              <goals>
                                  <goal>jar-no-fork</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </pluginManagement>
      </build>
    • 子模块

      <build>
          <plugins>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifact>maven-source-plugin</artifact>
              </plugin>
          </plugins>
      </build>
      • 子模块使用了 maven-source-plugin 插件, 同时又继承了父模块的 pluginManagement 配置
  • 集合与继承的关系

    • 多模块的聚合与继承其实是两个概念, 其目的是完全不同的, 前者为了方便快速的构件项目, 后者主要为了消除重复配置

      • 对于聚合模块来说: 它知道有哪些模块被聚合, 但是那些被聚合的模块并不知道这个聚合模块的存在
      • 对于继承关系的父 POM 来说: 它不知道有哪些子模块继承于它, 但是那些子模块都必须知道自己的父模块是什么
    • 在实际项目中, 一个 POM 往往即是聚合模块, 又是父模块
  • 约定优于配置

    • Maven 提倡 ” 约定优于配置 ”,Maven 只需要一个简单的 POM 文件, 就可以完成清除, 构件等任务

      • 源码目录:src/main/java/
      • 编译输出目录为:target/classes/
      • 打包方式为:jar
      • 包输出目录为:target/
    • Maven 此机制的来源就是超级 POM 文件, 此文件在 $MAVEN_HOME/lib/maven-model-builder-x.x.x.jar 中的 org/apache/maven/model/pom-4.0.0.xml 路径下

      <repositories>
          <repository>
              <id>central</id>
              <name>Maven Repository Swithboard</name>
              <url>http://repo1.maven.org/maven2</url>
              <layout>default</layout>
              <snapshots>
                  <enabled>false</enabled>
              </snapshots>
          </repository>
      </repositories>
      
      
      <pluginRepositories>
          <pluginRepository>
              <id>central</id>
              <name>Maven Plugin Repository</name>
              <url>http://repo1.maven.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>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>
          <plugins>
              <plugin>
                  <artifactId>maven-antrun-plugin</artifactId>
                  <version>1.3</version>
              </plugin>
              <plugin>
                  <artifactId>maven-assembly-plugin</artifactId>
                  <version>2.3</version>
              </plugin>
              <plugin>
                  <artifactId>maven-clean-plugin</artifactId>
                  <version>2.3</version>
              </plugin>
              <plugin>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>2.0.3</version>
              </plugin>
              ......
          </plugins>
      </pluginManagement>
      </build>

七. Nexus 私服

7.1 Nexus 内置的仓库

  • Nexus 仓库有四种类型

    1. group: 仓库组
    2. hosted: 宿主仓库
    3. proxy: 代理仓库
    4. virtual: 虚拟仓库
    • Maven 可以从宿主仓库下载构件,Maven 也可以从代理仓库下载构件, 而代理仓库会简介的从远程仓库下载并缓存构件
    • 而一般为了方便, 我们会从仓库组下载构件, 而仓库组没有实际的内容, 它只是管理一组实际的仓库, 当它接收到请求时, 它会转向其包含的宿主仓库或代理仓库获得实际构件的内容
    • 仓库的 Policy 属性

      • Release: 发布版
      • Snapshot: 快照版

    7.2 配置 Maven 从 Nexus 下载构件

    • 在 POM 文件中配置仓库和插件仓库 —— 只对当前项目有效

      <project>
          .......
          <repositories>
              <repository>
                  <id>nexus</id>
                  <name>Nexus</name>
                  <url>http://localhost:8081/nexus/content/groupId/public/</url>
                  <snapshots>
                      <enabled>true</enabled>
                  </snapshots>
              </repository>
          </repositories>
          <pluginRepositories>
              <pluginRepository>
                  <id>nexus</id>
                  <Name>Nexus</Name>
                  <url>http://localhost:8081/nexus/content/groupId/public/</url>
                    <release>
                      <enabled>true</enabled>
                  </release>
                  <snapshots>
                      <enabled>true</enabled>
                  </snapshots>
              </pluginRepository>
          </pluginRepositories>
          .......
      </project>
    • 在 settings.xml 文件中配置 — 全局有效

      • settings.xml 文件并不支持直接配置 repositories 和 pluginRepositories, 但是可以通过 Maven 提供的 Profile 机制, 让用户将仓库配置到 settings.xml 中的 Profile 中
      <settings>
          ....
          <profiles>
              <profile>
                  <id>nexus</id>
                  <repositories>
                      <repository>
                            <id>nexus</id>
                          <name>Nexus</name>
                          <url>http://localhost:8081/nexus/content/groupId/public/</url>
                          <release>
                              <enabled>true</enabled>
                          </release>
                          <snapshots>
                              <enabled>ture</enabled>
                          </snapshots>
                      </repository>
                  </repositories>
                  
                  <pluginRepositories>
                      <pluginRepository>
                          <id>nexus</id>
                          <Name>Nexus</Name>
                          <url>http://localhost:8081/nexus/content/groupId/public/</url>
                          <release>
                              <enabled>true</enabled>
                          </release>
                          <snapshots>
                              <enabled>true</enabled>
                          </snapshots>
                      </pluginRepository>
                  </pluginRepositories>
              </profile>
          </profiles>
          
          <!--> 激活指定 id 的 profile </!-->
          <activeProfiles>
              <activeProfile>nexus</activeProfile>
          </activeProfiles>
      </settings>
    • 以上配置已经能让 Maven 项目从 Nexus 私服下载构件了, 但是 Maven 仍然会去访问中央仓库, 现在我们想要将所有请求都仅仅通过私服, 这就需要借助于 Maven 镜像了

      <settings>
          .....
          <mirrors>
              <mirror>
                  <id>nexus</id>
                  <mirrorOf>*</mirrorOf>
                  <url>http://localhost:8081/nexus/content/groupId/public/</url>
              </mirror>
          </mirrors>
          
          <profiles>
              <profile>
                  <id>nexus</id>
                  <repositories>
                      <repository>
                            <id>central</id>
                          <name>central</name>
                          <url>http://central</url>
                          <release>
                              <enabled>true</enabled>
                          </release>
                          <snapshots>
                              <enabled>ture</enabled>
                          </snapshots>
                      </repository>
                  </repositories>
                  
                  <pluginRepositories>
                      <pluginRepository>
                          <id>central</id>
                          <Name>Nexus</Name>
                          <url>http://central</url>
                          <release>
                              <enabled>true</enabled>
                          </release>
                          <snapshots>
                              <enabled>true</enabled>
                          </snapshots>
                      </pluginRepository>
                  </pluginRepositories>
              </profile>
          </profiles>
          
          <!--> 激活指定 id 的 profile </!-->
          <activeProfiles>
              <activeProfile>nexus</activeProfile>
          </activeProfiles>
      </settings>
      • 这里仓库和插件仓库配置的 id 都为 central, 它们将会覆盖 POM 中央仓库的配置, 并且这里的 url 已经无关紧要, 因为所有的请求都会通过镜像访问私服地址

    7.3 部署构件至 Nexus

    • 如果只是为了代理外部公共仓库, 那么 Nexus 的代理仓库就已经完全足够了, 对于另一类 Nexus 仓库 — 宿主仓库来说, 他们主要作用是存储组织内部的, 或一些无法从公共仓库获得的第三方构件, 用户可以通过配置 Maven 自动部署构件至 Nexus 的宿主仓库

      • 使用 Maven 部署构件至 Nexus

        • 日常开发生成的快照版本构件可以直接部署到 Nexus 中策略为 Snapshot 的宿主仓库中, 项目正式发布的构件则应该部署到 Nexus 中策略为 Release 的宿主仓库中
        • POM 文件配置

          <project>
              .....
              <distributeManagement>
                  <repository>
                      <id>nexus-releases</id>
                      <name>Nexus Release Repository</name>
                      <url>http://localhost:8081/nexus/content/repositories/
                          release/</url>
                  </repository>
                  <snapshotRepository>
                      <id>nexus-snapshots</id>
                      <name>Nexus Snapshots Repository</name>
                      <url>http://localhost:8081/nexus/content/repositories/
                          snapshots</url>
                  </snapshotRepository>
              </distributeManagement>
              .....
          </project>
        • Nexus 的仓库对于匿名用户只是可读的, 为了能够部署构件, 还需要在 settings.xml 中配置认证信息

          <settings>
              ....
              <servers>
                  <server>
                      <id>nexus-releases</id>
                      <username>admin</username>
                      <password>admin123</password>
                  </server>
                  <server>
                      <id>nexus-snapshots</id>
                      <username>admin</username>
                      <password>admin123</password>
                  </server>
              </servers>
              ....
          </settings>

    八. Profile

    • 一个优秀的构件系统必须足够灵活, 它应该能够让项目在不同的环境下都能够成功的构件, 例如: 开发环境, 测试环境和产品环境, 这些环境的数据库配置不尽相同, 那么项目构建时就需要能够识别其所在的环境并正确使用配置

    8.1 属性

    <properties>
        <springframework.version>4.3.1</springframework.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframwork.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframwork.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    • 这是最常见的 Maven 属性使用的方式, 通过 properties 元素使得用户自定义一个或多个 Maven 属性, 然后在 POM 的其他地方使用 ${属性名称}的方式来引用该属性, 这种做法的最大意义是消除重复
    • Maven 的属性有六类

      1. 内置属性

        1. ${basedir}: 表示项目根目录, 即包含 pom.xml 文件的目录
        2. ${version}: 表示项目的版本
      2. POM 属性: 用户可以使用该类属性引用 POM 文件中对应元素的值

        • ${project.artifactId}: 对应 <project><artifactId> 元素的值

          • ${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/test-classes
          • ${project.build.groupId}: 项目的 groupId
          • ${project.build.artifactId}: 项目的 artifactId
          • ${project.build.build.finalName}: 项目打包输出文件的名称, 默认为 \${project.artifactId}

            -&dollar;{project.version}

      3. 自定义属性:<properties> 元素下自定义的 Maven 属性
      4. settings 属性: 与 POM 属性同理, 用户使用 settings. 来引用, 如:&dollar;{settings.localRepository}指向用户本地仓库的地址
      5. Java 系统属性: 所有 Java 系统属性都可以使用 Maven 属性引用, 如 &dollar;{user.home}指向用户目录

        • 可以通过 mvn help:system 查看所有的 java 系统属性
      6. 环境变量属性: 所有环境变量都可以用 env. 来引用, 例如:&dollar;{env.JAVA_HOME}

        • 可以通过 mvn help:system 查看所有的环境变量

    8.2 资源过滤

    • 在不同的开发环境中, 项目的源码应该使用不同的方式进行构件, 最常见的就是数据库的配置了, 在开发中, 有些项目会在 src/main/resources/ 目录下放置不同环境下的数据库配置

      database.jdbc.driverClass = com.mysql.jdbc.Driver
      database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev
      database.jdbc.username = dev
      database.jdbc.password = dev-passwd
      
      database.jdbc.driverClass = com.mysql.jdbc.Driver
      database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test
      database.jdbc.username = test
      database.jdbc.password = test-passwd
    • 为了应对环境的变化, 我们可以使用 Maven 属性将这些会发生变化的部分提取出来

      database.jdbc.driverClass = ${db.driver}
      database.jdbc.connectionURL = ${db.url}
      database.jdbc.username = ${db.username}
      database.jdbc.password = ${db.password}
    • 在 settings.xml 中通过 profile 定义不同环境下的配置数据

      <profiles>
          <profile>
              <id>dev</id>
              <properties>
                  <db.driver>com.mysql.jdbc.Driver</db.driver>
                  <db.url>jdbc:mysql://localhost:3306/dev</db.url>
                  <db.username>dev</db.username>
                  <db.password>dev-passwd</db.password>
              </properties>
          </profile>
          
              <profile>
              <id>test</id>
              <properties>
                  <db.driver>com.mysql.jdbc.Driver</db.driver>
                  <db.url>jdbc:mysql://localhost:3306/test</db.url>
                  <db.username>test</db.username>
                  <db.password>test-passwd</db.password>
              </properties>
          </profile>
      </profiles>
      • 需要注意的是:

        • Maven 属性默认只会在 POM 文件中才会被解析, 也就是说 &dollar;{db.username}放到 POM 文件中会变成 dev 或 test, 但是在 src/main/resources/ 目录下仍然还是 &dollar;{db.username}, 因此, 需要让 Maven 解析资源文件中的 Maven 属性
        • 资源文件处理的是 maven-reosurces-plugin 做事, 它默认的行为只是将项目主资源文件复制到主代码编译输出目录
    • 开启资源过滤

      • Maven 默认的主资源目录和测试目录的定义是在超级 POM 文件中
      <resources>
          <resource>
              <directory>${project.basedir}/src/main/resources</directory>
              <filtering>true</filtering>
          </resource>
      </resources>
      <testResources>
          <testResource>
              <directory>${project.basedir}/src/test/resources</directory>
              <filtering>true</filtering>
          </testResource>
      </testResources>
    • 通过 mvn clean install -Pdev : 通过 mvn 的 - P 参数表示在命令行激活一个 profile=dev 的 profile

    8.3 Profile

    • 激活 Pofile

      1. 命令行激活

        • 用户可以使用 mvn 命令行参数 -P 加上 profile 的 id 来激活 profile, 多个 id 之间逗号分隔

          • mvn clean install -Pdev,-Ptest
      2. settings 文件显示激活

        • 如果用户希望某个 profile 默认一直处于激活状态, 就可以配置 settings.xml 文件的 activeProfile 元素, 表示其配置的 profile 对所有项目都处于激活状态

          <settings>
              .....
              <activeProfiles>
                  <activeProfile>dev</activeProfile>
              </activeProfiles>
              .....
          </settings>
      3. 系统属性激活

        • 用户可以配置当前某系统属性存在时, 自动激活 profile

          <profiles>
              <profile>
                  <activation>
                      <property>
                          <name>test</name>
                      </property>
                  </activation>
                  .....
              </profile>
          </profiles>
        • 用户可以配置当前某系统属性存在且等于 a 时, 自动激活 profile

          <profiles>
              <profile>
                  <activation>
                      <property>
                          <name>test</name>
                          <value>a</value>
                      </property>
                  </activation>
                  .....
              </profile>
          </profiles>
        • 用户可以在命令行声明系统属性

          • mvn clean install -Dtest=x
      4. 操作系统环境变量激活

        • Profile 还可以根据操作系统环境激活

          <profiles>
              <profile>
                  <activation>
                      <os>
                          <name>Windows10</name>
                      </os>
                  </activation>
                  .....
              </profile>
          </profiles>
      5. 文件存在与否激活

        • Maven 可以根据项目中某个文件是否存在来决定是否激活 profile

          <profiles>
              <profile>
                  <activation>
                      <file>
                          <missing>a.properties</missing>
                          <exists>b.properties</exists>
                      </file>
                  </activation>
                  .....
              </profile>
          </profiles>
      6. 默认激活

        • 用户可以在定义 profile 的时候指定其默认激活

          <profiles>
              <profile>
                  <id>dev</id>
                  <activation>
                      <activeByDefault>true</activeByDefault>
                  </activation>
                  .....
              </profile>
          </profiles>
    • 以上激活优先级从上之下依次减小

      • 可以通过 mvn help:active-profiles 来查看当前激活的 profile
      • 可以通过 mvn help:all-profiles 来查看所有的 profile
    • profile 的种类

      • 根据具体的需要, 可以在一下位置声明 profile

        • pom.xml: 很显然,pom.xml 中声明的 profile 只对当前项目有效
        • 用户 settings.xml: 用户目录下.m2/settings.xml 中的 profile 只对本机该用户所有的 Maven 项目有效
        • 全局 settings.xml:Maven 安装目录下 conf/settings.xml 中 profile 对本机所有 Maven 项目都有效
    • POM 中 profile 可使用的元素

      <project>
          <repository></repository>
          <pluginRepository></pluginRepository>
          <distributionManagement></distributionManagement>
          <dependencies></dependencies>
          <dependencyManagement></dependencyManagement>
          <modules></modules>
          <properties></properties>
          <reporting></reporting>
          <build>
              <plugins></plugins>
              <defaultGoal></defaultGoal>
              <resources></resources>
              <testResources></testResources>
              <finalName></finalName>
          </build>
      </project>
        *   pom.xml: 很显然,pom.xml 中声明的 profile 只对当前项目有效
            
        *   用户 settings.xml: 用户目录下.m2/settings.xml 中的 profile 只对本机该用户所有的 Maven 项目有效
            
        *   全局 settings.xml:Maven 安装目录下 conf/settings.xml 中 profile 对本机所有 Maven 项目都有效
            
    • POM 中 profile 可使用的元素

    正文完
     0