廖神maven笔记

02廖神maven笔记

Maven 介绍

存在问题:为什么引入 Maven?

  1. 因为我们项目使用的外部包很多,我们需要把外部包的 jar 包放到 classpath 下,因为程序是需要读取.class 文件才能正确运行,而 jar 是.class 文件的打包所以我们需要把 jar 包放到 classpath 中。

    • classpath 是什么作用

      • 程序运行的时候只认识.java 文件编译后的.class 字节码文件,而我们需要告诉这些文件位于哪个路径下
  2. 我们还需要确定项目的目录结构,例如,src 目录存放 Java 源码,resources 目录存放配置文件,bin 目录存放编译生成的.class 文件。

  3. 我们还需要配置环境,例如 JDK 的版本,编译打包的流程,当前代码的版本号

  4. 最后,除了使用 Eclipse 这样的 IDE 进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。因为在服务器上部署可没有图形化界面让你设置

  • 上这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的 Java 项目管理和构建工具。这就是 Maven

Maven 特点

  • 提供了标准化项目结构(项目目录结构)
  • 提供了标准化的构建流程(编译,测试,打包)
  • 提供了一套依赖管理机制

Maven 的缺点

  • Maven 的整个体系相对庞大,想要完全掌握相对困难;
  • 如果项目的依赖过多,在第一次导入项目的时候需要花很长的时间来加载所需要的依赖;
  • 由于某种不可抗拒力,国内的开发者在使用 Maven 中央仓库的时候,下载速度过慢。

安装 Maven

  • 廖大神的网站
  • maven 是一个软件,需要单独下载,只不过流行的 ide 整合了 maven

Maven 目录结构

  • Maven 目录结构图:

  • 项目的根目录 a-maven-project 是项目名,它有一个项目描述文件 pom.xml,存放 Java 源码的目录是 src/main/java,存放资源文件的目录是 src/main/resources,存放测试源码的目录是 src/test/java,存放测试资源的目录是 src/test/resources,最后,所有编译、打包生成的文件都放在 target 目录里。这些就是一个 Maven 项目的标准目录结构。所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven 就可以正常使用。

  • 项目描述文件 pom.xml

    • <project ...>
      	<modelVersion>4.0.0</modelVersion>
      	<groupId>com.itranswarp.learnjava</groupId>
      	<artifactId>hello</artifactId>
      	<version>1.0</version>
      	<packaging>jar</packaging>
      	<properties>
              ...
      	</properties>
      	<dependencies>
              <dependency>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
                  <version>1.2</version>
              </dependency>
      	</dependencies>
      </project>
      
  • 其中,groupId 类似于 Java 的包名,通常是公司或组织名称,artifactId 类似于 Java 的类名,通常是项目名称,再加上 version,一个 Maven 工程就是由 groupIdartifactIdversion 作为唯一标识。我们在引用其他第三方库的时候,也是通过这 3 个变量确定。例如,依赖 commons-logging,使用 <dependency> 声明一个依赖后,Maven 就会自动下载这个依赖包并把它放到 classpath 中。

    • <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
      </dependency>
      

依赖管理

问题:什么是项目依赖?

  • 我们需要用到 a.jar,而 a.jar 自己有引用了 b.jar,Maven 这个时候会为我们自动解析判断并引入好我们需要的两个 jar 包,实际中可能引入几十个包
  • 比如当我们声明一个 spring-boot-starter-web 依赖时,Maven 会自动解析并判断最终需要大概二三十个其他依赖,如果我们自己去手动管理这些依赖是非常费时费力的,而且出错的概率很大。
    • spring-boot-starter-web
        spring-boot-starter
          spring-boot
          sprint-boot-autoconfigure
          spring-boot-starter-logging
            logback-classic
              logback-core
              slf4j-api
            jcl-over-slf4j
              slf4j-api
            jul-to-slf4j
              slf4j-api
            log4j-over-slf4j
              slf4j-api
          spring-core
          snakeyaml
        spring-boot-starter-tomcat
          tomcat-embed-core
          tomcat-embed-el
          tomcat-embed-websocket
            tomcat-embed-core
        jackson-databind
        ...
      

依赖关系

  • Maven 定义了几种依赖关系,分别是 compiletestruntimeprovided

    • scope 说明 示例
      compile 编译时需要用到该 jar 包(默认) commons-logging
      test 编译 Test 时需要用到该 jar 包 junit
      runtime 编译时不需要,但运行时需要用到 mysql
      provided 编译时需要用到,但运行时由 JDK 或某个服务器提供 servlet-api
  • 其中,默认的 compile​ 是最常用的,Maven 会把这种类型的依赖直接放入 classpath

  • test 依赖表示仅在测试时使用,正常运行时并不需要。最常用的 test 依赖就是 JUnit:

    • <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
          <version>5.3.2</version>
          <scope>test</scope>
      </dependency>
      
  • runtime 依赖表示编译时不需要,但运行时需要。最典型的 runtime 依赖是 JDBC 驱动,例如 MySQL 驱动:

    • <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.48</version>
          <scope>runtime</scope>
      </dependency>
      
  • provided 依赖表示编译时需要,但运行时不需要。最典型的 provided 依赖是 Servlet API,编译的时候需要,但是运行时,Servlet 服务器内置了相关的 jar,所以运行期不需要

    • <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.0</version>
          <scope>provided</scope>
      </dependency>
      

Maven 仓库原理

Maven 从哪里下载 jar 包的?

  • 答案是 Maven 维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的 jar 以及相关信息上传至中央仓库,Maven 就可以从中央仓库把所需依赖下载到本地。Maven 并不会每次都从中央仓库下载 jar 包。一个 jar 包一旦被下载过,就会被 Maven 自动缓存在本地目录(用户主目录的 .m2 目录),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的 jar 包。

唯一 ID

  • 对于某个依赖,Maven 只需要 3 个变量即可唯一确定某个 jar 包:

    • groupId:属于组织的名称,类似 Java 的包名;
    • artifactId:该 jar 包自身的名称,类似 Java 的类名;
    • version:该 jar 包的版本。
  • 通过上述 3 个变量,即可唯一确定某个 jar 包。Maven 通过对 jar 包进行 PGP 签名确保任何一个 jar 包一经发布就无法修改。修改已发布 jar 包的唯一方法是发布一个新版本。因此,某个 jar 包一旦被 Maven 下载过,即可永久地安全缓存在本地。

  • 注:只有以 -SNAPSHOT 结尾的版本号会被 Maven 视为开发版本,开发版本每次都会重复下载,这种 SNAPSHOT 版本只能用于内部私有的 Maven repo,公开发布的版本不允许出现 SNAPSHOT。

Maven 镜像

  • 除了可以从 Maven 的中央仓库下载外,还可以从 Maven 的镜像仓库下载。如果访问 Maven 的中央仓库非常慢,我们可以选择一个速度较快的 Maven 的镜像仓库。Maven 镜像仓库定期从中央仓库同步:

    •            slow    ┌───────────────────┐
          ┌─────────────>│Maven Central Repo.│
          │              └───────────────────┘
          │                        │
          │                        │sync
          │                        ▼
      ┌───────┐  fast    ┌───────────────────┐
      │ User  │─────────>│Maven Mirror Repo. │
      └───────┘          └───────────────────┘
      
  • 中国区用户可以使用阿里云提供的 Maven 镜像仓库。使用 Maven 镜像仓库需要一个配置,在用户主目录下进入 .m2 目录,创建一个 settings.xml 配置文件,内容如下,配置镜像仓库后,Maven 的下载速度就会非常快。

    • <settings>
          <mirrors>
              <mirror>
                  <id>aliyun</id>
                  <name>aliyun</name>
                  <mirrorOf>central</mirrorOf>
                  <!-- 国内推荐阿里云的Maven镜像 -->
                  <url>https://maven.aliyun.com/repository/central</url>
              </mirror>
          </mirrors>
      </settings>
      

搜索第三方组件

  • 最后一个问题:如果我们要引用一个第三方组件,比如 okhttp,如何确切地获得它的 groupIdartifactIdversion?方法是通过 search.maven.org 搜索关键字,找到对应的组件后,直接复制:

    • image.png

命令行编译

  • 在命令中,进入到 pom.xml 所在目录,输入以下命令,如果一切顺利,即可在 target 目录下获得编译后自动打包的 jar。

    • $ mvn clean package
      

构建流程

  • Maven 不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等。

Lifecycle(生命周期) 和 Phase(阶段)

  • 使用 Maven 时,我们首先要了解什么是 Maven 的生命周期(lifecycle) 。Maven 的生命周期由一系列阶段(phase)构成,以内置的生命周期 default 为例,它包含以下 phase:

    • validate
    • initialize
    • generate-sources
    • process-sources
    • generate-resources
    • process-resources
    • compile
    • process-classes
    • generate-test-sources
    • process-test-sources
    • generate-test-resources
    • process-test-resources
    • test-compile
    • process-test-classes
    • test
    • prepare-package
    • package
    • pre-integration-test
    • integration-test
    • post-integration-test
    • verify
    • install
    • deploy
  • 如果我们运行 mvn package,Maven 就会执行 default 生命周期,它会从开始一直运行到 package 这个 phase 为止:

    • validate
    • ...
    • package
  • 如果我们运行 mvn compile,Maven 也会执行 default 生命周期,但这次它只会运行到 compile,即以下几个 phase:

    • validate
    • ...
    • compile
  • Maven 另一个常用的生命周期是 clean,它会执行 3 个 phase:

    • pre-clean
    • clean (注意这个 clean 不是 lifecycle 而是 phase
    • post-clean
  • 所以,我们使用 mvn 这个命令时,后面的参数是 phase(比如 mvn package),Maven 自动根据生命周期运行到指定的 phase。

  • 更复杂的例子是指定多个 phase,例如,运行 mvn clean package,Maven 先执行 clean 生命周期并运行到 clean 这个 phase,然后执行 default 生命周期并运行到 package 这个 phase,实际执行的 phase 如下:

    • pre-clean
    • clean (注意这个 clean 是 phase)
    • validate
    • ...
    • package
  • 在实际开发过程中,经常使用的命令有:

    • mvn clean:清理所有生成的 class 和 jar;
    • mvn clean compile:先清理,再执行到 compile
    • mvn clean test:先清理,再执行到 test,因为执行 test 前必须执行 compile,所以这里不必指定 compile
    • mvn clean package:先执行清理,再执行到 package
  • 大多数 phase 在执行过程中,因为我们通常没有在 pom.xml 中配置相关的设置(#TODO :# 需要做什么设置?),**所以这些 phase 什么事情都不做。**

  • 经常用到的 phase 其实只有几个:

    • clean:清理
    • compile:编译
    • test:运行测试
    • package:打包

Goal

  • 其实在 Maven 的世界中,生命周期只是一个抽象的模型,其本身并不会直接去做事情,真正帮我们完成事情的是 Maven 的插件。Maven 的插件也属于构件的一种,也是可以放到 Maven 仓库当中的。

  • 通常情况下,一个插件可以有 A、B、C 等等不止一个功能,但是我们又没有必要为每一个功能都做一个单独的插件。这种时候,我们一般会给这个插件绑定不同的目标,而这些目标则是对应其不同的功能。

  • image.png

  • 当我们使用一个插件的目标的时候,我们可以执行命令:mvn pluginName:goalName。例如当我们执行 dependency 插件的 list 目标的时候,我们可以执行命令:mvn dependency:list

  • 执行一个 phase 又会触发一个或多个 goal,goal 的命名总是 abc:xyz(插件名称:goal ) 这种形式。

    • 执行的 Phase 对应执行的 Goal
      mvn compile compiler:compile
      mvn test compiler:testCompile
      surefire:test
  • 看到这里,相信大家对 lifecycle、phase 和 goal 已经明白了吧?明白个鸡儿

  • 其实我们类比一下就明白了:lifecycle > phase > goal

    • lifecycle 相当于 Java 的 package,它包含一个或多个 phase,java 的 package 包含很多 class
    • phase 相当于 Java 的 class,它包含一个或多个 goal,java 的 class 包含很多 method
    • goal 相当于 class 的 method,它其实才是真正干活的,java 中的 methon 才是真正干活的
  • 大多数情况,我们只要指定 phase,就默认执行这些 phase 默认绑定的 goal,当然,我们也可以跳过 phase,直接使用 mvn goal名称 来执行,只有少数情况,我们可以直接指定运行一个 goal,例如,启动 Tomcat 服务器:

    • mvn tomcat:run
      

使用插件

标准插件

  • 我们在前面介绍了 Maven 的 lifecyclephase goal.使用 Maven 构建项目就是执行 lifecycle,执行到指定的 phase 为止。每个 phase 会执行自己默认的一个或多个 goal。 goal 是最小任务单元。
  • 我们以 compile 这个 phase 为例,如果执行:
    • mvn compile
      
  • Maven 将执行 compile 这个 phase,这个 phase 会调用 compiler 插件执行关联的 compiler:compile 这个 goal。
  • 实际上,执行每个 phase,都是通过某个插件(plugin)来执行的,Maven 本身其实并不知道如何执行 compile,它只是负责找到对应的 compiler 插件,然后执行默认的 compiler:compile 这个 goal 来完成编译。所以,使用 Maven,实际上就是配置好需要使用的插件,然后通过 phase 调用它们。
  • Maven 已经默认内置了一些常用的标准插件,不需要在 pom.xml 文件中的 plugins 标签中手动声明:
    • 插件名称 对应执行的 phase
      clean clean
      compiler compile
      surefire test
      jar package

自定义插件

  • 如果标准插件无法满足需求,我们还可以使用自定义插件。使用自定义插件的时候,需要声明(注意,Maven 自带的标准插件例如 compiler 是无需声明的,只有引入其它的插件才需要声明。)。例如,使用 maven-shade-plugin 可以创建一个可执行的 jar,要使用这个插件,需要在 pom.xml 中声明它:

    • <project>  
          ...  
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-shade-plugin</artifactId>
                      <version>3.2.1</version>
                      <executions>
                          <execution>
                              <phase>package</phase>
                              <goals>
                                  <goal>shade</goal>
                              </goals>
                              <configuration>                            ...						</configuration>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </build>
      </project>
      
  • 自定义插件往往需要一些配置,例如,maven-shade-plugin 需要指定 Java 程序的入口,它的配置是:

    • <configuration>
          <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>com.itranswarp.learnjava.Main</mainClass>
              </transformer>
          </transformers>
      </configuration>
      
  • 下面列举了一些常用的插件:

    • maven-shade-plugin:打包所有依赖包并生成可执行 jar;
    • cobertura-maven-plugin:生成单元测试覆盖率报告;
    • findbugs-maven-plugin:对 Java 源码进行静态分析以找出潜在问题。

模块管理

这里的步骤是不借助 ide 的方式手动创建一个多模块项目

  • 在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法:

    •                         ┌ ─ ─ ─ ─ ─ ─ ┐
                                ┌─────────┐
                              │ │Module A │ │
                                └─────────┘
      ┌──────────────┐ split  │ ┌─────────┐ │
      │Single Project│───────>  │Module B │
      └──────────────┘        │ └─────────┘ │
                                ┌─────────┐
                              │ │Module C │ │
                                └─────────┘
                              └ ─ ─ ─ ─ ─ ─ ┘
      
  • 对于 Maven 工程来说,原来是一个大项目:

    • single-project
      ├── pom.xml
      └── src
      
  • 现在可以分拆成 3 个模块:

    • mutiple-project
      ├── module-a
      │   ├── pom.xml
      │   └── src
      ├── module-b
      │   ├── pom.xml
      │   └── src
      └── module-c
          ├── pom.xml
          └── src
      
  • Maven 可以有效地管理多个模块,我们只需要把每个模块当作一个独立的 Maven 项目,它们有各自独立的 pom.xml。例如,模块 A 的 pom.xml

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
      &lt;groupId&gt;com.itranswarp.learnjava&lt;/groupId&gt;
      &lt;artifactId&gt;module-a&lt;/artifactId&gt;
      &lt;version&gt;1.0&lt;/version&gt;
      &lt;packaging&gt;jar&lt;/packaging&gt;
      
      &lt;name&gt;module-a&lt;/name&gt;
      
      &lt;properties&gt;
          &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
          &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt;
          &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
          &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
          &lt;java.version&gt;11&lt;/java.version&gt;
      &lt;/properties&gt;
      
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
              &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
              &lt;version&gt;1.7.28&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
              &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
              &lt;version&gt;1.2.3&lt;/version&gt;
              &lt;scope&gt;runtime&lt;/scope&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
              &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
              &lt;version&gt;5.5.2&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
      

      </project>

  • 模块 B 的 pom.xml

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
      &lt;groupId&gt;com.itranswarp.learnjava&lt;/groupId&gt;
      &lt;artifactId&gt;module-b&lt;/artifactId&gt;
      &lt;version&gt;1.0&lt;/version&gt;
      &lt;packaging&gt;jar&lt;/packaging&gt;
      
      &lt;name&gt;module-b&lt;/name&gt;
      
      &lt;properties&gt;
          &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
          &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt;
          &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
          &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
          &lt;java.version&gt;11&lt;/java.version&gt;
      &lt;/properties&gt;
      
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
              &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
              &lt;version&gt;1.7.28&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
              &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
              &lt;version&gt;1.2.3&lt;/version&gt;
              &lt;scope&gt;runtime&lt;/scope&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
              &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
              &lt;version&gt;5.5.2&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
      

      </project>

  • 可以看出来,模块 A 和模块 B 的 pom.xml 高度相似,因此,我们可以提取出共同部分作为模块 parent

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
      &lt;groupId&gt;com.itranswarp.learnjava&lt;/groupId&gt;
      &lt;artifactId&gt;parent&lt;/artifactId&gt;
      &lt;version&gt;1.0&lt;/version&gt;
      &lt;packaging&gt;pom&lt;/packaging&gt;
      
      &lt;name&gt;parent&lt;/name&gt;
      
      &lt;properties&gt;
          &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
          &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&gt;
          &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
          &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
          &lt;java.version&gt;11&lt;/java.version&gt;
      &lt;/properties&gt;
      
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
              &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
              &lt;version&gt;1.7.28&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
              &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
              &lt;version&gt;1.2.3&lt;/version&gt;
              &lt;scope&gt;runtime&lt;/scope&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
              &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
              &lt;version&gt;5.5.2&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
      

      </project>

  • 注意到 parent 的 <packaging> 是 pom 而不是 jar,因为 parent 本身不含任何 Java 代码。编写 parentpom.xml 只是为了在各个模块中减少重复的配置。现在我们的整个工程结构如下:

    • multiple-project
      ├── pom.xml--->之后解释为什么这里还有一个pom.xml文件(这里的作用是为了方便直接在multiple-project执行mvn命令时可以对下面四个模块统一处理)
      ├── parent
      │   └── pom.xml
      ├── module-a
      │   ├── pom.xml
      │   └── src
      ├── module-b
      │   ├── pom.xml
      │   └── src
      └── module-c
          ├── pom.xml
          └── src
      
  • 这样模块 A 就可以简化为:

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
      &lt;parent&gt;
          &lt;groupId&gt;com.itranswarp.learnjava&lt;/groupId&gt;
          &lt;artifactId&gt;parent&lt;/artifactId&gt;
          &lt;version&gt;1.0&lt;/version&gt;
          &lt;relativePath&gt;../parent/pom.xml&lt;/relativePath&gt;
      &lt;/parent&gt;
      
      &lt;artifactId&gt;module-a&lt;/artifactId&gt;
      &lt;packaging&gt;jar&lt;/packaging&gt;
      &lt;name&gt;module-a&lt;/name&gt;
      

      </project>

  • 模块 B、模块 C 都可以直接从 parent 继承,大幅简化了 pom.xml 的编写。

  • 如果模块 A 依赖模块 B,则模块 A 需要模块 B 的 jar 包才能正常编译,我们需要在模块 A 中引入模块 B:

    •     ...
          <dependencies>
              <dependency>
                  <groupId>com.itranswarp.learnjava</groupId>
                  <artifactId>module-b</artifactId>
                  <version>1.0</version>
              </dependency>
          </dependencies>
      
  • 最后,在编译的时候,需要在根目录创建一个 pom.xml​ 统一编译:

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      
      &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
      &lt;groupId&gt;com.itranswarp.learnjava&lt;/groupId&gt;
      &lt;artifactId&gt;build&lt;/artifactId&gt;
      &lt;version&gt;1.0&lt;/version&gt;
      &lt;packaging&gt;pom&lt;/packaging&gt;
      &lt;name&gt;build&lt;/name&gt;
      
      &lt;modules&gt;
          &lt;module&gt;parent&lt;/module&gt;
          &lt;module&gt;module-a&lt;/module&gt;
          &lt;module&gt;module-b&lt;/module&gt;
          &lt;module&gt;module-c&lt;/module&gt;
      &lt;/modules&gt;
      

      </project>

  • 这样,在根目录执行 mvn clean package​ 时,Maven 根据根目录的 pom.xml​ 找到包括 parent​ 在内的共 4 个 <module>​,一次性全部编译。

  • 这里跟京东实习项目内容平台采用的模块化管理并不一致,二者各有各的优缺点

依赖冲突:最短路径原则

Maven的“最近优先”原则意味着在解析依赖冲突时,将选择依赖路径中最近定义的版本。这里通过一个例子来说明这个原则是如何工作的。

假设有这样的项目结构和依赖关系:

  • 项目A 依赖于 库X v1.0项目B
  • 项目B 依赖于 库X v2.0

项目A的pom.xml​可能包含类似以下定义的依赖:

<dependencies>
    <!-- 依赖库X v1.0 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>libX</artifactId>
        <version>1.0</version>
    </dependency>
    <!-- 依赖项目B -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>projectB</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

项目B的pom.xml​可能包含类似以下定义的依赖:

<dependencies>
    <!-- 依赖库X v2.0 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>libX</artifactId>
        <version>2.0</version>
    </dependency>
</dependencies>

在这个例子中,当Maven解析项目A的依赖时,它会发现两个不同版本的库X(v1.0和v2.0)。根据“最近优先”原则,Maven会选择依赖路径中最近的版本。因为项目A直接依赖于库X v1.0,而库X v2.0是通过项目B间接依赖的,所以Maven会选择库X v1.0作为解决方案。

这个原则有助于减少因依赖传递而造成的版本冲突,但有时也可能导致不是最新版本的库被使用。如果需要使用特定版本的库,可以在项目A的pom.xml中显式指定所需版本,或者使用<dependencyManagement>元素在父POM中统一管理依赖版本。

单元测试

在我们平时开发的过程中,测试环节是永远不能避免的。那我们如何能够快速的进行单元测试呢,如何更方便的看到测试结果呢?在这个过程中,Maven 也能够为我们提供帮助,那我们来看看 Maven 如何在测试环节来辅助我们的。

1. 使用 Maven 进行单元测试

1.1 添加测试代码

这里面我们在 mall-order 模块中增加生成订单的方法,这个方法调用后会生成一个 OrderEntity 对象,里面包括订单编号(orderNuM)和订单所有者(orderOwner)两个字段。(这里我们仅仅是用来模拟单元测试,对业务的具体逻辑不做过多纠结)

首先,我们在 mall-order 模块的 pom.xml 文件中添加需要的依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
</dependency>
12345678

添加依赖后,我们在项目中创建 service 目录和 entity 目录,分别用于存放项目的服务层代码和实体类。

在项目中添加对应的 service 和 entity

并且在对应的测试目录中,增加该服务层代码的测试类 OrderServiceTest

完成后,我们可以执行该测试用例,来调试 OrderService 中的 generateOrder 方法。

从调试结果来看,我们的方法被成功调用,并且没有异常。

1.2 借助 Maven 进行单元测试

后来,随着我们项目的不断进行,我们开发的功能也随之不断增多,相应的,不同功能的测试用例也在不断的增多。这个时候,如果单纯的靠开发人员手工去点击每一个测试用例,这显然是不合理的。

那么我们就可以借助 Maven 来帮助我们做这件事情,来进行自动化的单元测试。

例如在 mall-order 模块下, 我们想要执行所有的单元测试用例,那么我们只需要进入到该模块的根目录下,执行 mvn test 命令即可。

[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mic.tech:mall-order >-----------------------
[INFO] Building mall-order 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mall-order ---
[INFO] Surefire report directory: D:\code\mall-aggregate\mall-order\target\surefire-reports

T E S T S

Running com.mic.tech.OrderServiceTest
...
Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.333 s
[INFO] Finished at: 2020-05-20T23:37:40+08:00
[INFO] ------------------------------------------------------------------------

12345678910111213141516171819202122232425

从执行结果,我们可以看出,一共执行了三个测试用例,没有失败,也没有报错的情况出现。

2. 跳过测试

2.1 指定测试用例进行测试

其实每一项新的操作一般都会伴随一些问题产生。例如,我们在实际的开发过程中,有些时候只是改动了一处代码,但是如果直接执行 mvn test 命令的话,会将整个项目的测试用例全部都执行一遍,这对于我们来说,是有些得不偿失的,没必要因为一处改动,而去测试其他几十个或者几百个测试用例。

那我们应该怎么办呢? 这里我们为了演示,写了两个测试类,OrderServiceTestOrderService2Test,其中第一个类中,有两个测试用例,第二个类中,只有一个测试用例。

这时候,我们修改了第二个类中测试用例对应的方法,需要重新进行单元测试。我们可以直接执行命令:mvn test -Dtest=OrderService2Test

[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mic.tech:mall-order >-----------------------
[INFO] Building mall-order 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mall-order ---
[INFO] Surefire report directory: D:\code\mall-aggregate\mall-order\target\surefire-reports

T E S T S

Running com.mic.tech.OrderService2Test
...
Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.261 s
[INFO] Finished at: 2020-05-21T22:17:47+08:00
[INFO] ------------------------------------------------------------------------

12345678910111213141516171819202122232425

从结构来看,我们这里只执行了第二个测试类中的测试用例。

2.2 跳过测试

换到另外一个场景,构建项目的时候。在平时的开发过程中,我们经常会使用 mvn package 构建项目,但是如果这个项目比较庞大,测试用例会非常多,那么执行测试用例的过程就会非常耗时。那怎么办呢,test 阶段在 package 阶段之前,如果直接执行 package 阶段,test 阶段势必会被执行到。

这个时候我们可以跳过测试来构建项目。(当然,这样的做法是不被建议的)

在执行构建命令的时候,添加参数来指定跳过测试即可,mvn package -DskipTests 或者 mvn package -Dmaven.test.skip=true

这两个命令虽然都能够在构建项目的时候跳过测试,但还是有些区别的。

  • -DskipTests: 会编译测试类;
  • -Dmaven.test.skip=true: 不会编译测试类。

3. 测试报告

Maven 的默认配置中,会在 target\surefire-reports 目录下生成测试报告。

我们执行 mvn clean test,就可以观察到该目录生成。

我们可以在 txt 格式的文档中看到生成的测试报告。这里的测试报告基本上和控制台输出的内容是类似的。

大家可能也注意到了,我们在执行测试用例的时候,同时生成了两种类型的文件,一种是 txt 格式,另一个则是 XML 格式。

  • txt 格式: 为了让执行测试用例的开发者更加直观的看到测试用例的运行结果;
  • XML 格式: 更多的是为了支持其他工具的解析。

4. maven-surefire-plugin

说了这么多,其实 Maven 之所以可以帮助我们自动执行测试用例,还是依靠 maven-surefire-plugin 插件来实现的。在学过 Maven 的生命周期之后,我们知道一个插件的功能都是通过目标来实现的,而不同的目标则会和生命周期中的不同阶段进行绑定。这里,生命周期中的 test 阶段就是和 maven-surefire-plugin 插件的 test 目标进行绑定的。

对于 Maven 来说,我们可以不指定或者显示的声明该插件。

在显示声明的时候,我可以通过添加 configuration 的方式来实现刚刚执行命令的效果。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.17</version>
            <configuration>
            	<skipTests>true</skipTests>
            </configuration>
        </plugin>
    </plugins>
</build>
123456789101112

例如我们在 configuration 中添加 skipTests 节点,则可以默认跳过测试。当我们再次执行 mvn package 命令构建项目的时候,test 阶段是不会被执行的。

当然,我们也可以在 configuration 中添加 include 节点和 exclude 节点,来控制执行的测试类。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.17</version>
    <configuration>
        <includes>
        	<include>**/OrderServiceTest.java</include>
        </includes>
        <excludes>
        	<exclude>**/OrderService2Test.java</exclude>
        </excludes>
    </configuration>
</plugin>
12345678910111213

此时,我们再次执行 mvn test 的时候会发现只有 OrderServiceTest.java 类中的测试用例被执行了。

5. 小结

在本节的学习中,我们简单介绍了如何编写测试用例,Maven 如何自动的执行测试用例,以及在执行测试用例的过程中的一些技巧,比如跳过测试。最后我们还介绍了 maven-surefire-plugin 的简单使用。通过本节的学习,我们可以使用 Maven 轻松的进行自动化测试。

Profile 构建

上一节,我们讲到了如何使用 Maven 自动化的进行单元测试,那测试通过之后,我们就要进行构建,并且将构建好的程序包,放到对应的服务器上去运行。这个时候,问题就出现了,对于不同环境,我们需要不同的配置文件,那我们构建的之前,就需要将配置文件改为对应环境的配置文件,之后才能进行构建。总而言之,很麻烦,而且,万一忘记改配置,那么构建出来的包就是错的。

Profile 则让不同环境之间可移植性的构建成为可能。它使我们在构建项目的时候,可以很轻松的在不同环境中进行构建,也可以称之为“开箱即用”。

1. 可移植性

首先,我们来介绍一下可移植性。所谓的可移植性,指的是,在构建的过程中,改动配置文件的次数和范围越小,则可移植性越强,反之,则可移植性越弱。根据可移植性的不同程度,我们可以将其划分为如下几类:

  • 不可移植: 指的是,项目只能在某个特定环境或者条件下才能构建。这种时候,构建是不可移植的,当然,我们在开发的过程中,肯定是不想看到这种情况的发生。
  • 环境可移植: 指的是,对于不同环境添加不同的配置,一次构建都能够完成,那么这个构建就具备环境可移植了。即:无需对不同环境做过多改动,即可完成相应构建。
  • 全局可移植: 指的是,无论在何种环境下,开发者都不需要做任何配置,即可完成对项目的构建工作。这个特性对于优秀的开源软件来说,尤其重要。因为这种类型的软件,往往会由来自各地的开发者来共同开发。

在大多数情况下,我们平时开发的项目只需要做到环境可移植就可以了。因为通常的公司往往会有三套环境,开发环境,测试环境,生产环境,针对不同的开发阶段,往往需要将项目构建到不同的环境中去,但是由于这些项目通常部署在公司的内网环境中,所以,我们并不需要考虑做到全局可移植性。

2. Maven Profile

在了解了什么是可移植性之后,那我们来看看 Maven 是如何实现可移植性的。这里就需要介绍 Maven 的一组配置 Profile 。通过使用 Profile 我们就可以实现针对不同环境自定义进行构建。通常情况下,Profile 被定义在 pom.xml 文件中,但是定义的方式也可以有很多种。

<profiles>
   <profile>
        <id>dev</id>
        <properties>
            <database.driver>com.mysql.cj.jdbc.Driver</database.driver>
            <database.url>jdbc:mysql://localhost:3306/dev</database.url>
            <database.username>Mic</database.username>
            <database.password>Abc123</database.password>
        </properties>
    </profile>
    <profile>
        <id>test</id>
        <properties>
            <database.driver>com.mysql.cj.jdbc.Driver</database.driver>
            <database.url>jdbc:mysql://localhost:3306/test</database.url>
            <database.username>Mic</database.username>
            <database.password>Abc123</database.password>
        </properties>
     </profile>
</profiles>

这里我们以数据库连接串为例,可以将不同环境中的数据库连接串配置到对应的 profile 节点中,并为每个 profile 节点定义单独的 id 。配置好之后,我们我们在进行构建项目的时候,可以在执行命令的时候,加上参数 -Pdev 来指定对应的配置 :mvn clean package -Pdev

但是问题来了,通常情况下,我们不会把配置信息放到 pom.xml 文件中,这样对于我们管理配置,并不方便。那我们如何在构建的时候,指定使用对应的配置文件来进行构建呢?我们可以对 pom.xml 文件进行简单的修改。

<!-- build决定打包时打包哪些配置文件 -->
<build> 
    <resources>
        <resource>
            <directory>src/main/resources/</directory>
            <!-- 打包时,将对应配置文件先排除 -->
            <excludes>
            	<exclude>**/*.yml</exclude>
            </excludes>
            <includes>
            <!--如果有其他定义通用文件,需要包含进来-->
            </includes>
        </resource>
        <resource>
            <!-- 通过自定义的节点来激活指定的配置文件,profile.activ取值dev和test -->
            <directory>src/main/resources/${profile.active}</directory>
        </resource>
    </resources>
</build>

<!-- profiles配置了各种运行环境 -->
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 自定义节点profile.active-->
<profile.active>dev</profile.active>
</properties>
<!--activation用来指定激活方式,可以根据jdk环境,环境变量,文件的存在或缺失-->
<activation>
<!-- 表示默认激活-->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>

首先,我们定义 <resource> 节点用于指定配置文件的目录,并将最后一级目录定义成可配置的(如 src/main/resources/${profile.active})。然后,跟刚刚类似,我们开始定义 <profiles> 节点,只不过这次,我们只是在 properties 节点中自定义了一个节点(即 <profile.active>dev</profile.active>),与 resource 中的自定义路径相呼应(自定义路径分别是 devtest)。相应的,我们需要在 resources 目录下,创建两个目录,分别为 dev prod 目录,用于存放不同的配置文件。

都配置好之后,我们再使用命令 mvn clean package -Pdev 来构建项目。构建完成后,我们查看目录 target\classes 会发现,application-dev.yml 配置文件被编译进来,而另一个配置文件并没有被编译进来。这时候,我们的目标就基本上就达成了。

image.png

注意 :由于我们这里配置的 dev Profile 是默认激活状态的,所以执行 mvn clean packagemvn clean package -Pdev 两个命令的结果是相同的。

当然,通过配置 profile ,我们不仅仅可以指定激活不同的配置文件,也可以指定构建使用的 JDK 版本,以及某些操作系统的参数。

3. 小结

本节中,我们主要介绍了 Maven 的一个属性,叫做 Profile 。通过配置 Profile 我们可以实现构建的环境可移植性,从而大大简化我们在构建过程中的便捷程度,让我们从重复的修改配置的过程中解脱出来。

Maven 属性与资源过滤

在之前的章节中,我们已经介绍了 Maven 如何使用 Profile 来进行构建。类似的,对于 Maven 来说,还有很多其他的资源或者属性,而这些资源或者属性也都是可以进行过滤的,这一小节中,我们就重点介绍一下在什么情况下,需要过滤这些,并且要如何进行操作。

1. 属性

首先,我们来介绍一下 Maven 的属性特性。其实,在我们之前的章节中,一直都有在使用 Maven 的属性。例如我们在引入 Spring 框架的时候,将 Spring 框架的版本号信息抽象出来,放到 properties 节点中去,在使用这个版本号的时候,可以通过 ${} 来引用。

<properties>
    <spring.version>4.0.2.RELEASE</spring.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

我们都知道抽象了 spring.version 属性之后,可以减少我们很多的工作量,而且也便于我们对 pom.xml 文件的管理。在 Maven 的世界中,这只是其中一种属性。那我们来介绍一下 Maven 的属性的种类。

  • 内置属性: Maven 的内置属性主要有两个,一个是 ${basedir} 用来表示项目的根目录,另一个是 ${version} 用来表示项目的版本号;
  • POM 属性: 用来引用 pom.xml 文件中对应元素的值。一般来说,可以用 ${project.*} 来表示,例如:${project.groupId} 就是用来表示项目的 groupId 信息;
  • 自定义属性: 这个比较容易理解,就像我们上面例子中的 ${spring.version} 就属于自定义属性的范围;
  • Settings 属性: 与 POM 属性类似。通常使用 ${settings.*} 来表示,Settings 属性用来指向 settings.xml 文件中的属性,例如:${settings.localrepository} 可以用来表示本地仓库的地址;
  • Java 系统属性: 所有 Java 的系统属性都可以通过 Maven 属性来引用。我们在使用之前可以通过 mvn help:system 命令来查看对应的属性;
  • 环境变量属性: 所有的环境变量属性都可以通过 Maven 属性来引用。通常用 ${env.*} 来表示。

我们在很多地方都可以用到 Maven 属性,例如我们的示例项目中,多模块直接互相引用的时候,我们会用到 ${project.groupId}${project.version},来管理项目内部依赖。会用到 ${project.basedir} 来指定项目配置文件的路径。

2. Profile

这里就是之前章节中讲到的 Profile ,所以在这里,我们就不做过多的介绍,可以移步到Profile 构建一节进行学习。

3. 资源过滤

我们使用 Maven 资源过滤的目的和使用 Profile 的目的很大程度上是类似的,就是为了增强项目构建的可移植性。之前我们在 Profile 的章节中,讲到了在构建项目的时候,激活对应的 Profile 来使用对应的配置,当时我们把配置都放在了配置文件中,因此,如果有多套环境的话,那么配置文件就相应的需要多套。

也就是之前Profile 构建的方式配置多个环境需要多个不同的配置文件,build 只会激活多个配置文件中的一个;使用资源过滤的方式只需要一个配置文件,通过变量的形式,根据不同的环境给 resource/ 目录下的配置文件设置不同的值

这里,我们换一种方式来进行资源过滤。在讲 Profile 的章节中,我们使用 mall-order 模块来演示,这次我们换为 mall-delivery 模块来演示。

首先在 src\main\resources 目录下添加 application.yml 文件,用作配置文件。文件中的内容如下:

spring:
  datasource:
    driver-class-name: ${database.driverClass}
    username: ${database.username}
    password: ${database.password}
    url: ${database.url}
    type: com.alibaba.druid.pool.DruidDataSource

这里,可以看到,我们使用了 ${} 的方式来引用属性,这个属性可以定义在 pom.xml 文件中。

接下来,我们就在 pom.xml 文件中配置对应的属性。

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

<profiles>
<profile>
<id>dev</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3306/dev</database.url>
<database.username>userNameDev</database.username>
<database.password>passwordDev</database.password>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3307/test</database.url>
<database.username>userNameTest</database.username>
<database.password>passwordTest</database.password>
</properties>
</profile>
</profiles>

通常情况下,资源过滤是关闭的,如果需要开启,则需要我们手动设置 <filtering>true</filtering>。默认关闭也是为了防止在构建的过程中发生一些不必要的过滤情况。

这里,我们分别配置了开发环境的数据库信息和测试环境的数据库信息(其实也是使用的 Profile 的方式)。其中 properties 节点配置了我们自定义的属性,其与我们刚刚在 application.yml 文件中配置的占位符是一样的。

接下来,我们进行项目构建可以指定对应的 Profile,执行 Maven 命令 mvn clean package -Pdev。构建完成后,我们可以查看 target\classes 目录下的构建结果,会发现配置文件中的确是用的开发环境的 Profile,目的达成。

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: userNameDev
    password: passwordDev
    url: jdbc:mysql://localhost:3306/dev
    type: com.alibaba.druid.pool.DruidDataSource

以上的配置,还可以进行一下修改。由于我们平时更多的是在用开发环境的配置,因此,我们可以把开发环境的配置单独放置出来。

<build>
	...
</build>
<properties>
    <database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
    <database.url>jdbc:mysql://localhost:3306/dev</database.url>
    <database.username>userNameDev</database.username>
    <database.password>passwordDev</database.password>
</properties>

<profiles>
<profile>
<id>test</id>
<properties>
<database.driverClass>com.mysql.cj.jdbc.Driver</database.driverClass>
<database.url>jdbc:mysql://localhost:3307/test</database.url>
<database.username>userNameTest</database.username>
<database.password>passwordTest</database.password>
</properties>
</profile>
</profiles>

这样修改之后,我们在进行开发环境构建的时候,不需要添加额外的参数,直接执行命令 mvn clean package 即可正常构建。而当我们需要构建其他环境程序包的时候,则在命令后面添加对应的参数。

4. 小结

本文,我们承接《Maven 使用 Profile 构建》一节,继续介绍 Maven 的属性和资源过滤,学了这两节之后,能够更方便,更明晰的管理 pom.xml 文件中的依赖,也让多环境构建变得更加简单。

使用 mvnw

  • 我们使用 Maven 时,基本上只会用到 mvn 这一个命令。有些童鞋可能听说过 mvnw,这个是啥?
  • mvnwMaven Wrapper 的缩写。因为我们安装 Maven 时,默认情况下,系统所有项目都会使用全局安装的这个 Maven 版本。但是,对于某些项目来说,它可能必须使用某个特定Maven 版本,这个时候,就可以使用 Maven Wrapper,它可以负责给这个特定的项目安装指定版本的 Maven,而其他项目不受影响。
  • 简单地说,Maven Wrapper 就是给一个项目提供一个独立的,指定版本的 Maven 给它使用。

安装 Maven Wrapper

  • 安装 Maven Wrapper 最简单的方式是在项目的根目录(即 pom.xml 所在的目录)下运行安装命令,它会自动使用最新版本的 Maven。注意 0.7.6 是 Maven Wrapper 的版本。最新的 Maven Wrapper 版本可以去官方网站查看。

    • mvn -N io.takari:maven:0.7.6:wrapper
      
  • 如果要指定使用的 Maven 版本,使用下面的安装命令指定版本,例如 3.3.3

    • mvn -N io.takari:maven:0.7.6:wrapper -Dmaven=3.3.3
      
  • 安装后,查看项目结构:

    • my-project
      ├── .mvn
      │   └── wrapper
      │       ├── MavenWrapperDownloader.java
      │       ├── maven-wrapper.jar
      │       └── maven-wrapper.properties
      ├── mvnw
      ├── mvnw.cmd
      ├── pom.xml
      └── src
          ├── main
          │   ├── java
          │   └── resources
          └── test
              ├── java
              └── resources
      
  • 发现多了 mvnw 文件、mvnw.cmd 文件 和 .mvn 目录,我们只需要把 mvn 命令改成 mvnw可以使用跟项目关联的 Maven。例如:

    • mvnw clean package
      
  • 在 Linux 或 macOS 下运行时需要加上 ./

    • ./mvnw clean package
      
  • Maven Wrapper 的另一个作用是把项目的 mvnwmvnw.cmd.mvn 提交到版本库中,可以使所有开发人员使用统一的 Maven 版本。

发布 Artifact

  • 当我们使用 commons-logging 这些第三方开源库的时候,我们实际上是通过 Maven 自动下载它的 jar 包,并根据其 pom.xml 解析依赖(compile,test,runtime,provided),自动把相关依赖包都下载后加入到 classpath。
  • 那么问题来了:当我们自己写了一个牛逼的开源库时,非常希望别人也能使用,总不能直接放个 jar 包的链接让别人下载吧?
  • 如果我们把自己的开源库放到 Maven 的 repo 中,那么,别人只需按标准引用 groupId:artifactId:version,即可自动下载 jar 包以及相关依赖。因此,本节我们介绍如何发布一个库到 Maven 的 repo 中。
  • 把自己的库发布到 Maven 的 repo 中有好几种方法,我们介绍 3 种最常用的方法。

第一种方法:以静态文件发布

仓库目录结构分析

  • 如果我们观察一个中央仓库的 Artifact 结构,例如 Commons Math,它的 groupId 是 org.apache.commons,artifactId 是 commons-math3,以版本 3.6.1 为例,发布在中央仓库的文件夹路径就是 https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/,在此文件夹下,commons-math3-3.6.1.jar 就是发布的 jar 包,commons-math3-3.6.1.pom 就是它的 pom.xml 描述文件,commons-math3-3.6.1-sources.jar 是源代码,commons-math3-3.6.1-javadoc.jar 是文档。其它以 .asc.md5.sha1 结尾的文件分别是 GPG 签名、MD5 摘要和 SHA-1 摘要。我们只要按照这种目录结构组织文件,它就是一个有效的 Maven 仓库。

    • image.png

以静态文件发布到 repo 举例

  • 我们以广受好评的开源项目 how-to-become-rich 为例,先创建 Maven 工程目录结构如下:

    • how-to-become-rich
      ├── maven-repo        <-- Maven本地文件仓库
      ├── pom.xml           <-- 项目文件
      ├── src
      │   ├── main
      │   │   ├── java      <-- 源码目录
      │   │   └── resources <-- 资源目录
      │   └── test
      │       ├── java      <-- 测试源码目录
      │       └── resources <-- 测试资源目录
      └── target            <-- 编译输出目录
      
  • pom.xml 中添加如下内容:

    • <project ...>
          ...
          <distributionManagement>
              <repository>
                  <id>local-repo-release</id>
                  <name>GitHub Release</name>
                  <url>file://${project.basedir}/maven-repo</url>
              </repository>
          </distributionManagement>
      
      &lt;build&gt;
          &lt;plugins&gt;
              &lt;plugin&gt;
                  &lt;artifactId&gt;maven-source-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;id&gt;attach-sources&lt;/id&gt;
                          &lt;phase&gt;package&lt;/phase&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;jar-no-fork&lt;/goal&gt;
                          &lt;/goals&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
              &lt;plugin&gt;
                  &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;id&gt;attach-javadocs&lt;/id&gt;
                          &lt;phase&gt;package&lt;/phase&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;jar&lt;/goal&gt;
                          &lt;/goals&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
          &lt;/plugins&gt;
      &lt;/build&gt;
      

      </project>

  • 注意到 <distributionManagement>,它指示了发布的软件包的位置,这里的 <url> 是项目根目录下的 maven-repo 目录,在 <build> 中定义的两个插件 maven-source-pluginmaven-javadoc-plugin 分别用来创建源码和 javadoc,如果不想发布源码,可以把对应的插件去掉。

  • 我们直接在项目根目录下运行 Maven 命令 mvn clean package deploy,如果一切顺利,我们就可以在 maven-repo 目录下找到部署后的所有文件如下:

    • maven-repo
      └── com
          └── itranswarp
              └── rich
                  └── how-to-become-rich
                      ├── 1.0.0
                      │   ├── how-to-become-rich-1.0.0-javadoc.jar
                      │   ├── how-to-become-rich-1.0.0-javadoc.jar.md5
                      │   ├── how-to-become-rich-1.0.0-javadoc.jar.sha1
                      │   ├── how-to-become-rich-1.0.0-sources.jar
                      │   ├── how-to-become-rich-1.0.0-sources.jar.md5
                      │   ├── how-to-become-rich-1.0.0-sources.jar.sha1
                      │   ├── how-to-become-rich-1.0.0.jar
                      │   ├── how-to-become-rich-1.0.0.jar.md5
                      │   ├── how-to-become-rich-1.0.0.jar.sha1
                      │   ├── how-to-become-rich-1.0.0.pom
                      │   ├── how-to-become-rich-1.0.0.pom.md5
                      │   └── how-to-become-rich-1.0.0.pom.sha1
                      ├── maven-metadata.xml
                      ├── maven-metadata.xml.md5
                      └── maven-metadata.xml.sha1
      
  • 最后一步,是把这个工程推到 GitHub 上,并选择 Settings-GitHub Pages,选择 master branch 启用 Pages 服务:

    • image.png
  • 这样,把全部内容推送至 GitHub 后,即可作为静态网站访问 Maven 的 repo,它的地址是 https://michaelliao.github.io/how-to-become-rich/maven-repo/。版本 1.0.0 对应的 jar 包地址是:

    • https://michaelliao.github.io/how-to-become-rich/maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar
      
  • 现在,如果其他人希望引用这个 Maven 包,我们可以告知如下依赖即可:

    • <dependency>
          <groupId>com.itranswarp.rich</groupId>
          <artifactId>how-to-become-rich</artifactId>
          <version>1.0.0</version>
      </dependency>
      
  • 但是,除了正常导入依赖外,对方还需要再添加一个 <repository> 的声明,即使用方完整的 pom.xml 如下:

    • <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
      &lt;groupId&gt;example&lt;/groupId&gt;
      &lt;artifactId&gt;how-to-become-rich-usage&lt;/artifactId&gt;
      &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
      &lt;packaging&gt;jar&lt;/packaging&gt;
      
      &lt;properties&gt;
          &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
          &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
          &lt;java.version&gt;11&lt;/java.version&gt;
      &lt;/properties&gt;
      
      &lt;repositories&gt;
          &lt;repository&gt;
              &lt;id&gt;github-rich-repo&lt;/id&gt;
              &lt;name&gt;The Maven Repository on Github&lt;/name&gt;
              &lt;url&gt;https://michaelliao.github.io/how-to-become-rich/maven-repo/&lt;/url&gt;
          &lt;/repository&gt;
      &lt;/repositories&gt;
      
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;com.itranswarp.rich&lt;/groupId&gt;
              &lt;artifactId&gt;how-to-become-rich&lt;/artifactId&gt;
              &lt;version&gt;1.0.0&lt;/version&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
      

      </project>

  • <repository> 中,我们必须声明发布的 Maven 的 repo 地址,其中 <id><name> 可以任意填写,<url> 填入 GitHub Pages 提供的地址 +/maven-repo/ 后缀。现在,即可正常引用这个库并编写代码如下:

    • Millionaire millionaire = new Millionaire();
      System.out.println(millionaire.howToBecomeRich());
      
  • 有的童鞋会问,为什么使用 commons-logging 等第三方库时,并不需要声明 repo 地址?这是因为这些库都是发布到 Maven 中央仓库的,发布到中央仓库后,不需要告诉 Maven 仓库地址,因为它知道中央仓库的地址默认是 https://repo1.maven.org/maven2/,也可以通过 ~/.m2/settings.xml 指定一个代理仓库地址以替代中央仓库来提高速度(参考依赖管理的 Maven 镜像)。

  • 因为 GitHub Pages 并不会把我们发布的 Maven 包同步到中央仓库,所以自然使用方必须手动添加一个我们提供的仓库地址。

  • 此外,通过 GitHub Pages 发布 Maven repo 时需要注意一点,即不要改动已发布的版本。因为 Maven 的仓库是不允许修改任何版本的,对一个库进行修改的唯一方法是发布一个新版本。但是通过静态文件的方式发布 repo,实际上我们是可以修改 jar 文件的,但最好遵守规范,不要修改已发布版本。

第二种方法:通过 Nexus 发布到中央仓库

  • 有的童鞋会问,能不能把自己的开源库发布到 Maven 的中央仓库,这样用户就不需要声明 repo 地址,可以直接引用,显得更专业。

  • 当然可以,但我们不能直接发布到 Maven 中央仓库,而是通过曲线救国的方式,发布到 central.sonatype.org,它会定期自动同步到 Maven 的中央仓库。Nexus 是一个支持 Maven 仓库的软件(就像是 maven repository 网站一样,Nexus 可以部署一个本地的 maven repository 网站),由 Sonatype 开发,有免费版和专业版两个版本,很多大公司内部都使用 Nexus 作为自己的私有 Maven 仓库,而这个 central.sonatype.org 相当于面向开源的一个 Nexus 公共服务。

  • 所以,第一步是在 central.sonatype.org 上注册一个账号,注册链接非常隐蔽,可以自己先找找,找半小时没找到点这里查看攻略。

  • 如果注册顺利并审核通过,会得到一个登录账号,然后,通过这个页面一步一步操作就可以成功地将自己的 Artifact 发布到 Nexus 上,再耐心等待几个小时后,你的 Artifact 就会出现在 Maven 的中央仓库中。

  • 这里简单提一下发布重点与难点:

    • 必须正确创建 GPG 签名,Linux 和 Mac 下推荐使用 gnupg2;
    • 必须在 ~/.m2/settings.xml 中配置好登录用户名和口令,以及 GPG 口令:
      • <settings ...>
            ...
            <servers>
                <server>
                    <id>ossrh</id>
                    <username>OSSRH-USERNAME</username>
                    <password>OSSRH-PASSWORD</password>
                </server>
            </servers>
            <profiles>
                <profile>
                    <id>ossrh</id>
                    <activation>
                        <activeByDefault>true</activeByDefault>
                    </activation>
                    <properties>
                        <gpg.executable>gpg2</gpg.executable>
                        <gpg.passphrase>GPG-PASSWORD</gpg.passphrase>
                    </properties>
                </profile>
            </profiles>
        </settings>
        

  • 在待发布的 Artifact 的 pom.xml 中添加 OSS 的 Maven repo 地址,以及 maven-jar-pluginmaven-source-pluginmaven-javadoc-pluginmaven-gpg-pluginnexus-staging-maven-plugin

    • <project ...>
          ...
          <distributionManagement>
              <snapshotRepository>
                  <id>ossrh</id>
                  <url>https://oss.sonatype.org/content/repositories/snapshots</url>
              </snapshotRepository>
      
          &lt;repository&gt;
              &lt;id&gt;ossrh&lt;/id&gt;
              &lt;name&gt;Nexus Release Repository&lt;/name&gt;
              &lt;url&gt;http://oss.sonatype.org/service/local/staging/deploy/maven2/&lt;/url&gt;
          &lt;/repository&gt;
      &lt;/distributionManagement&gt;
      
      &lt;build&gt;
          &lt;plugins&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-jar-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;jar&lt;/goal&gt;
                              &lt;goal&gt;test-jar&lt;/goal&gt;
                          &lt;/goals&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-source-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;id&gt;attach-sources&lt;/id&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;jar-no-fork&lt;/goal&gt;
                          &lt;/goals&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;id&gt;attach-javadocs&lt;/id&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;jar&lt;/goal&gt;
                          &lt;/goals&gt;
                          &lt;configuration&gt;
                              &lt;additionalOption&gt;
                                  &lt;additionalOption&gt;-Xdoclint:none&lt;/additionalOption&gt;
                              &lt;/additionalOption&gt;
                          &lt;/configuration&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-gpg-plugin&lt;/artifactId&gt;
                  &lt;executions&gt;
                      &lt;execution&gt;
                          &lt;id&gt;sign-artifacts&lt;/id&gt;
                          &lt;phase&gt;verify&lt;/phase&gt;
                          &lt;goals&gt;
                              &lt;goal&gt;sign&lt;/goal&gt;
                          &lt;/goals&gt;
                      &lt;/execution&gt;
                  &lt;/executions&gt;
              &lt;/plugin&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.sonatype.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;nexus-staging-maven-plugin&lt;/artifactId&gt;
                  &lt;version&gt;1.6.3&lt;/version&gt;
                  &lt;extensions&gt;true&lt;/extensions&gt;
                  &lt;configuration&gt;
                      &lt;serverId&gt;ossrh&lt;/serverId&gt;
                      &lt;nexusUrl&gt;https://oss.sonatype.org/&lt;/nexusUrl&gt;
                      &lt;autoReleaseAfterClose&gt;true&lt;/autoReleaseAfterClose&gt;
                  &lt;/configuration&gt;
              &lt;/plugin&gt;
          &lt;/plugins&gt;
      &lt;/build&gt;
      

      </project>

  • 最后执行命令 mvn clean package deploy 即可发布至 central.sonatype.org

  • 此方法前期需要复杂的申请账号和项目的流程,后期需要安装调试 GPG,但只要跑通流程,后续发布都只需要一行命令。

第四种方法:发布到私有仓库

通过 nexus-staging-maven-plugin 除了可以发布到 central.sonatype.org 外,也可以发布到私有仓库,例如,公司内部自己搭建的 Nexus 服务器。

如果没有私有 Nexus 服务器,还可以发布到 GitHub Packages。GitHub Packages 是 GitHub 提供的仓库服务,支持 Maven、NPM、Docker 等。使用 GitHub Packages 时,无论是发布 Artifact,还是引用已发布的 Artifact,都需要明确的授权 Token,因此,GitHub Packages 只能作为私有仓库使用。

在发布前,我们必须首先登录后在用户的 Settings-Developer settings-Personal access tokens 中创建两个 Token,一个用于发布,一个用于使用。发布 Artifact 的 Token 必须有 repowrite:packagesread:packages 权限:

token-scopes

使用 Artifact 的 Token 只需要 read:packages 权限。

在发布端,把 GitHub 的用户名和发布 Token 写入 ~/.m2/settings.xml 配置中:

<settings ...>
    ...
    <servers>
        <server>
            <id>github-release</id>
            <username>GITHUB-USERNAME</username>
            <password>f052...c21f</password>
        </server>
    </servers>
</settings>

然后,在需要发布的 Artifact 的 pom.xml 中,添加一个 <repository> 声明:

<project ...>
    ...
    <distributionManagement>
        <repository>
            <id>github-release</id>
            <name>GitHub Release</name>
            <url>https://maven.pkg.github.com/michaelliao/complex</url>
        </repository>
    </distributionManagement>
</project>

注意到 <id>~/.m2/settings.xml 配置中的 <id> 要保持一致,因为发布时 Maven 根据 id 找到用于登录的用户名和 Token,才能成功上传文件到 GitHub。我们直接通过命令 mvn clean package deploy 部署,成功后,在 GitHub 用户页面可以看到该 Artifact:

github-packages

完整的配置请参考 complex 项目,这是一个非常简单的支持复数运算的库。

使用该 Artifact 时,因为 GitHub 的 Package 只能作为私有仓库使用,所以除了在使用方的 pom.xml 中声明 <repository> 外:

<project ...>
    ...
    <repositories>
        <repository>
            <id>github-release</id>
            <name>GitHub Release</name>
            <url>https://maven.pkg.github.com/michaelliao/complex</url>
        </repository>
    </repositories>
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.itranswarp&lt;/groupId&gt;
        &lt;artifactId&gt;complex&lt;/artifactId&gt;
        &lt;version&gt;1.0.0&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
...

</project>

还需要把有读权限的 Token 配置到 ~/.m2/settings.xml 文件中。

pom.xml 文件中的 标签

前言

在阅读详细文档之前我们先来谈谈我自己对 maven 的一些个人理解,以助于从整体大局上了解 maven。

  • maven 是什么,用通俗的话来将,maven 能帮你构建工程,管理 jar 包,编译代码,还能帮你自动运行单元测试,打包,生成报表,甚至能帮你部署项目

  • 使用 maven 构建的项目均可以直接使用 maven build 标签完成项目的编译测试打包,无需额外配置

  • Maven 是通过 pom.xml 来执行任务的,其中的 build 标签 描述了如何来编译及打包项目,而具体的编译和打包工作是通过 build 中配置的 plugin 来完成。当然 plugin 配置不是必须的,默认情况下,Maven 会绑定以下几个插件来完成基本操作。

    • 即在没有配置的情况下,执行 mvn clean install 时,maven 会调用默认的 plugin 来完成编译打包操作,具体来讲,执行 mvn clean install 时会执行

    • maven-clean-plugin:2.5:clean (default-clean)

    • maven-resources-plugin:2.6:resources (default-resources)

    • maven-compiler-plugin:3.1:compile (default-compile)

    • maven-resources-plugin:2.6:testResources (default-testResources)

    • maven-compiler-plugin:3.1:testCompile (default-testCompile)

    • maven-surefire-plugin:2.12.4:test (default-test)

    • maven-jar-plugin:2.4:jar (default-jar)

    • maven-install-plugin:2.4:install (default-install)

    • 等 plugin

  • 4.如果有需要可以针对各个 plugin 进行特殊配置,需要在 pom.xml 中的 <build> / <plugins> 标签中显示指定 plugin 和 属性配置,如下配置了 maven-compiler-plugin 的版本和编译时使用的 jdk 版本

POM.XML 的 build 标签

说明

在 Maven 的 pom.xml 文件中,Build 相关配置包含两个部分,一个是 <build>,另一个是 <reporting>,这里我们只介绍 <build>

1.pom.xml 中的两种 build

在 Maven 的 pom.xml 文件中,存在如下两种 <build>

说明:

  1. 一种 <build> 被称为 ==Project Build==(针对整个项目的所有情况都有效),即是 <project> 的直接子元素。
  2. 另一种 <build> 被称为 ==Profile Build==(针对不同的 profile 配置),即是 <profile> 的直接子元素。

Profile Build 包含了基本的 build 元素,而 Project Build 还包含两个特殊的元素,即<...Directory> 和 <extensions>。

2.Profile Build 和 Project Build 的共有标签

共有标签:基础标签

示例如下:

说明:

  • defaultGoal,执行构建时默认的 goal 或 phase,如 jar:jar 或者 package 等
  • directory,构建的结果所在的路径,默认为 ${basedir}/target 目录
  • finalName,构建的最终结果的名字,该名字可能在其他 plugin 中被改变

共有标签: 标签

资源文件的构建

资源往往不是代码,无需编译,资源文件往往是一些 properties XML 配置文件,构建过程中会往往会将资源文件从源路径复制到指定的目标路径。

标签作用

<resources> 指出了各个资源在 Maven 源码项目(不是 target/)中的具体路径。示例如下:

说明:

maven中filter和filtering的使用方法

  • filters,给出对资源文件进行过滤的属性文件的路径,默认位于 ${basedir}/src/main/filters/ 目录下。属性文件中定义若干键值对。在构建过程中,对于资源文件中出现的变量(键),将使用属性文件中该键对应的值替换。(资源文件指的是resource目录下的配置文件,属性文件指的是pom.xml定义的变量,属性)

  • resources, maven build 过程中涉及的资源文件

  • targetPath,资源文件的目标路径,通常被打包在 jar 中的 resources 的目标路径是 META-INF

  • filtering,构建过程中是否对资源进行过滤,默认 false

  • directory,资源文件的路径,默认位于 ${basedir}/src/main/resources/ 目录下

  • includes,一组文件名的匹配模式,被匹配的资源文件将被构建过程处理

  • excludes,一组文件名的匹配模式,被匹配的资源文件将被构建过程忽略。同时被 includes 和 excludes 匹配的资源文件,将被忽略。

  • testResources,test 过程中涉及的资源文件,默认位于 ${basedir}/src/test/resources/ 目录下。这里的资源文件不会被构建到目标构件中

  • 示例(除了 maven 默认的 src/main/resources/*.yml 配置文件需要打包之外,还需要打包 src/main/java/**/*.xml 文件)

    • image.png

共有标签: 标签

<plugins> 给出构建过程中所用到的插件。

说明:

  • groupId:插件组织名称
  • artifactId:插件项目名称
  • version:插件项目版本号
  • extensions,是否加载该插件的扩展,默认 false
  • inherited,该插件的 configuration 中的配置是否可以被(继承该 POM 的其他 Maven 项目)继承,默认 true
  • configuration,该插件所需要的特殊配置,在父子项目之间可以覆盖或合并
  • dependencies,该插件所特有的依赖类库
  • executions,该插件的某个 goal(一个插件中可能包含多个 goal)的执行方式。一个 execution 有如下设置:
    • id,唯一标识
    • goals,要执行的插件的 goal(可以有多个),如 run
    • phase,插件的 goal 要嵌入到 Maven 的 phase 中执行,如 verify
    • inherited,该 execution 是否可被子项目继承
    • configuration,该 execution 的其他配置参数

共有标签: 标签

<build> 中,<pluginManagement><plugins> 并列,两者之间的关系类似于 <dependencyManagement><dependencies> 之间的关系。<pluginManagement> 中也配置 <plugin>,其配置参数与 <plugins> 中的 <plugin> 完全一致。只是,<pluginManagement> 往往出现在父项目中,其中配置的 <plugin> 往往通用于子项目。子项目中只要在 <plugins> 中以 <plugin> 声明该插件,该插件的具体配置参数则继承自父项目中 <pluginManagement> 对该插件的配置,从而避免在子项目中进行重复配置。

Project Build 特有的 <...Directory>

往往配置在父项目中,供所有父子项目使用。示例如下:

目录可以使用绝对路径,如示例所示。如果使用相对路径,则所有的相对路径都是在 ${basedir}目录下。

Project Build 特有的

是执行构建过程中可能用到的其他工具,在执行构建的过程中被加入到 classpath 中。也可以通过 激活构建插件,从而改变构建的过程。通常,通过 给出通用插件的一个具体实现,用于构建过程。

的使用示例如下:

maven 默认标准的输入输出目录

构建 Maven 项目的时候,如果没有进行特殊的配置,Maven 会按照标准的目录结构查找和处理各种类型文件。

普通 maven 项目(非 web 项目)

src/main/java src/test/java

这两个目录中的所有 *.java 文件会分别在 comile 和 test-comiple 阶段被编译,编译结果分别放到了 target/classestarge/test-classes 目录下,但是这两个目录中的其他扩展名的文件都会被忽略掉。 如果在这俩目录中放置其他配置文件 mapper.xml,dev.properties,就会被忽略掉.

src/main/resouces src/test/resources

这两个目录中的文件也会分别被复制到 target/classestarget/test-classes 目录下(与源码同文件夹级别)。

web 项目

当是 web 项目时,会在 target 下生成 myproject 目录,myproject 是你的项目名

src/main/webapps

src/main/webapps 这个目录中的文件会被复制到 target/myProject 目录中

target/classes

默认会把这个目录中的所有内容复制到 target/myProject/WEB-INF/classes 目录中

Dependency(项目依赖)

默认会将项目的依赖复制到 target/myProject/WEB-INF/lib

maven 仓库

image.png

  • 无论哪种仓库,都是需要下载到本地磁盘才可以被项目引用.

    • image.png

中央仓库

其实我们使用的大多数第三方模块都是这个用法,例如,我们使用 commons logging、log4j 这些第三方模块,就是第三方模块的开发者自己把编译好的 jar 包发布到 Maven 的中央仓库中。

私有仓库

私有仓库是指公司内部如果不希望把源码和 jar 包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的 ~/.m2/settings.xml 中配置好,使用方式和中央仓位没有任何区别。

本地仓库

本地仓库是指把本地开发的项目“发布”在本地,这样其他项目可以通过本地仓库引用它。但是我们不推荐把自己的模块安装到 Maven 的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。更好的方法是使用模块化编译,在编译的时候,告诉 Maven 几个模块之间存在依赖关系,需要一块编译,Maven 就会自动按依赖顺序编译这些模块。

posted @ 2025-03-11 22:09  红豆绿豆abc  阅读(27)  评论(0)    收藏  举报