廖神maven笔记

02廖神maven笔记

  • https://liaoxuefeng.com/books/java/maven/basic/index.html


  • Maven 是一个 Java 项目管理和构建工具,它可以定义项目结构项目依赖,并使用统一的方式进行自动化构建,是 Java 项目不可缺少的工具。

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-webspring-boot-starterspring-bootsprint-boot-autoconfigurespring-boot-starter-logginglogback-classiclogback-coreslf4j-apijcl-over-slf4jslf4j-apijul-to-slf4jslf4j-apilog4j-over-slf4jslf4j-apispring-coresnakeyamlspring-boot-starter-tomcattomcat-embed-coretomcat-embed-eltomcat-embed-websockettomcat-embed-corejackson-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.Driverusername: userNameDevpassword: passwordDevurl: jdbc:mysql://localhost:3306/devtype: 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 就会自动按依赖顺序编译这些模块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/897437.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

已知p部分高位部分低位攻击

题目: from Crypto.Util.number import getPrime, bytes_to_long from secret import flagp = getPrime(1024) q = getPrime(1024) n = p * q e = 65537 hint1 = p >> 724 hint2 = q % (2 ** 265) ct = pow(bytes_to_long(flag), e, n) print(hint1) print(hint2) print(…

机动车占用通道监控报警摄像机

机动车占用通道监控摄像机,是创新行业智能监督管理方式、完善监管部门动态监控及预警预报体系的信息化手段,是实现平台远程监控由“人为监控”向“智能监控”转变的必要手段。产品致力于设备的智能化建设,有效实现对机动车堵塞通道智能检测的实时预警,包括视频监管、事件预…

Stereotyped messages attack(刻板消息攻击)

攻击阐述 我们用b\x00替换消息中的x这样就有了(m+x)^e mod n=cm知道一部分 x是b\x00\x00******未知的 (e,n)是公钥,c是密文问题变为如何找到x Coppersmith可以解决了这个问题 (这种题本质上就是Coppersmith的变体) 攻击成功条件e = 3 x < N<sup>1/e</sup>题目1:…

反素数(emirp数)

反素数,英文称作emirp(prime(素数)的左右颠倒拼写),是素数的一种,把一个素数的阿拉伯字数字序列(十进制)变成由低位向高位反写出来,得到的另一个数还是素数,例如素数<font style="color:rgb(32, 33, 34);">13</font>,反写就是<font style="color…

ai烟雾检测智能摄像机

ai烟雾烟头识别摄像头作为现代火灾预防技术的重要组成部分,特别是在大范围要求高的林业、农田等,具有独特的优势,有效降低了火灾发生。AI烟雾烟头识别摄像头厂家也逐渐多了起来。AI烟雾烟火识别摄像头能准确捕捉到火焰的形状、大小、颜色等特征,还能结合环境背景、光线变化…

24-关于华夏ERP的搭建、审计、扫描

1、使用IDEA和PHPStudy搭建华夏erp 需要先下载 jshERP3.3-最新包,JSH_ERP-v3.3源码前端部署在网站根目录下创建一个名为ERP的文件夹,将前端压缩包 前端包/dist.zip 解压到该目录下 使用小皮创建 huaxi.com网站,连接到刚刚的ERP目录访问http://huaxi.com,出现如下页面说明前…

AMM算法进阶

题目: from Crypto.Util.number import * import os from gmpy2 import *def getMyPrime1(nbits):while True:n = 2*1009*7*getPrime(nbits//2)*getPrime(nbits//2)if is_prime(n+1):return n+1def getMyPrime2(nbits):while True:n = 2*1009*getPrime(nbits//2)*getPrime(nbit…

AMM算法

题目: from Crypto.Util.number import * import os from gmpy2 import *def getMyPrime(nbits):while True:n = 2*1009*getPrime(nbits//2)*getPrime(nbits//2)if is_prime(n+1):return n+1p = getMyPrime(700) q = getMyPrime(700) n = p*qe = 1009flag = bNSSCTF{******} + …

[PaperReading] Scaling Vision Transformers to 22 Billion Parameters

目录名称TL;DRMethodCode && ImplementationExperiment实现细节Linear Eval on ImageNetZero-shot Eval on ImageNetDense PredictionRelated works中值得深挖的工作 名称 Scaling Vision Transformers to 22 Billion Parameters 论文链接 时间:2023.02 作者与单位:G…

【CF VP记录】Codeforces Round 1008 (Div. 2)

比赛链接 本文原文发布于博客园,如您在其他平台刷到此文,请前往博客园获得更好的阅读体验。 跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18766146 开题 + 补题情况 坠机场,要是赛时打了的话就又回青了,前两题很快开出来了,第三题脑残了,一开始觉得只需要构…

32位系统上的Linux的highmem

什么是highmem? Linux内存管理 highmem的出现与Linux的内存管理相关。众所周知,linux内核一般将处理器的虚拟地址空间分为两个部分。底部较大的部分用于用户进程,而顶部的较小部分用于内核。这个划分的比例通常是1:3(在编译内核时可以通过特殊的配置选项修改这个比例)。所…

halcon 深度学习教程(一)分类检测 (工业里如何使用halcon深度学习去检测分类产品)

原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/18766108深度学习教程目录如下,还在继续更新完善中 深度学习系列教程目录 本篇主要是入门halcon的深度学习篇,参考halcon实例classify_fruit_deep_learning.hdev,不过去实例的话会比较复杂一些,不便于理解,这…