Maven基础知识(2)- Maven 坐标、Maven 外部依赖、Maven 仓库
1. Maven 坐标
在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称为构件(组件)。在 Maven 世界中存在着数十万甚至数百万构件,在引入坐标概念之前,当用户需要使用某个构件时,只能去对应的网站寻找,但各个网站的风格迥异,这使得用户将大量的时间浪费在搜索和寻找上,严重地影响了研发效率。为了解决这个问题,于是 Maven 引入了 Maven 坐标的概念。
Maven 坐标一套规则,它规定:世界上任何一个构件都可以使用 Maven 坐标并作为其唯一标识,Maven 坐标包括 groupId、artifactId、version、packaging 等元素,只要用户提供了正确的坐标元素,Maven 就能找到对应的构件。
任何一个构件都必须明确定义自己的坐标,这是 Maven 的强制要求,任何构件都不能例外。我们在开发 Maven 项目时,也需要为其定义合适的坐标,只有定义了坐标,其他项目才能引用该项目生成的构件。
以下是 MavenDemo01 项目的坐标定义。
1 <project> 2 <groupId>com.example</groupId> 3 <artifactId>MavenDemo01</artifactId> 4 <packaging>jar</packaging> 5 <version>1.0-SNAPSHOT</version> 6 </project>
Maven 坐标主要由以下元素组成:
groupId: 项目组 ID,定义当前 Maven 项目隶属的组织或公司,通常是唯一的。它的取值一般是项目所属公司或组织的网址或 URL 的反写,例如 com.example;
artifactId: 项目 ID,通常是项目的名称;
version:版本;
packaging:项目的打包方式,默认值为 jar;
以上 4 个元素中 groupId、artifactId 和 version 是必须定义的,packaging 是可选的。
2. Maven 外部依赖
Maven 是一款优秀的依赖管理工具,那么什么是外部依赖呢?
通俗的说,如果一个 Maven 构建所产生的构件(例如 Jar 文件)被其他项目引用,那么该构件就是其他项目的依赖。
1) 依赖声明
Maven 坐标是依赖的前提,所有 Maven 项目必须明确定义自己的坐标,只有这样,它们才可能成为其他项目的依赖。当一个项目的构件成为其他项目的依赖时,该项目的坐标才能体现出它的价值。
当 Maven 项目需要声明某一个依赖时,通常只需要在其 POM 中配置该依赖的坐标信息,Maven 会根据坐标自动将依赖下载到项目中。
例如,某个项目中使用 servlet-api 作为其依赖,其配置如下。
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 4 http://maven.apache.org/maven-v4_0_0.xsd"> 5 6 ... 7 8 <dependencies> 9 <dependency> 10 <groupId>javax.servlet</groupId> 11 <artifactId>servlet-api</artifactId> 12 <version>2.5</version> 13 <scope>provided</scope> 14 </dependency> 15 </dependencies> 16 </project>
dependencies 元素可以包含一个或者多个 dependency 子元素,用以声明一个或者多个项目依赖,每个依赖都可以包含以下元素:
(1) groupId、artifactId 和 version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven 根据坐标才能找到需要的依赖;
(2) type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar;
(3) scope:依赖的范围;
(4) optional:标记依赖是否可选;
(5) exclusions:用来排除传递性依赖;
大部分依赖声明只包含 groupId、artifactId 和 version 三个元素,至于 scope、optional 以及 exclusions 等元素,了解即可,在后续的学习中我们会陆续进行讲解。
2) 获取依赖坐标
通常情况下,绝大部分依赖的 Maven 坐标都能在 https://mvnrepository.com/ 中获取。
例如,当项目中需要引入 servlet-api 时, 只需要在首页搜索 servlet-api 即可。
选择合适的版本,在依赖详情页的最下方就是该版本依赖的 Maven 坐标,我们可以直接将其复制到项目的 pom.xml 中使用。
3) 导入本地 JAR 包
Maven 是通过仓库对依赖进行管理的,当 Maven 项目需要某个依赖时,只要其 POM 中声明了依赖的坐标信息,Maven 就会自动从仓库中去下载该构件使用。
在实际的开发过程中,经常会遇到一种情况:某一个项目需要依赖于存储在本地的某个 jar 包,该 jar 包无法从任何仓库中下载的,这种依赖被称为外部依赖或本地依赖。
下面通过一个实例来介绍如何导入本地 jar 包,示例:
(1) 创建 MavenDemo02 项目
打开 cmd 命令行窗口,进入 D:\Workshop\maven 目录,
D:\Workshop\maven>mvn archetype:generate -DgroupId=com.example -DartifactId=MavenDemo02 -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
(2) 修改 MavenDemo02 中 App 类的代码如下
1 package com.example; 2 3 import com.example.Common; 4 5 public class App { 6 public static void main(String[] args) { 7 Common.sayHello("MavenDemo02: say hello"); 8 } 9 }
MavenDemo02 中的 App 类需要使用 MavenDemo01 中的 Common 类,即 MavenDemo02 需要依赖于 MavenDemo01,需要导入 MavenDemo01-1.0-SNAPSHOT.jar。
MavenDemo01-1.0-SNAPSHOT.jar 的打包过程,请参考 “Maven基础知识(1)- Maven 简介、Maven 安装配置、创建 Quickstart 项目”。
(3) 修改 MavenDemo02 中 pom.xml 的配置如下
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 4 http://maven.apache.org/maven-v4_0_0.xsd"> 5 6 <modelVersion>4.0.0</modelVersion> 7 <groupId>com.example</groupId> 8 <artifactId>MavenDemo02</artifactId> 9 <packaging>jar</packaging> 10 <version>1.0-SNAPSHOT</version> 11 <name>MavenDemo02</name> 12 <url>http://maven.apache.org</url> 13 <dependencies> 14 <dependency> 15 <groupId>junit</groupId> 16 <artifactId>junit</artifactId> 17 <version>3.8.1</version> 18 <scope>test</scope> 19 </dependency> 20 21 <!--外部依赖--> 22 <dependency> 23 <groupId>com.example</groupId> 24 <artifactId>MavenDemo01</artifactId> 25 <version>1.0-SNAPSHOT</version> 26 <!-- 依赖范围 --> 27 <scope>system</scope> 28 <!-- 依赖所在位置 --> 29 <systemPath>D:\Workshop\maven\MavenDemo01\target\MavenDemo01-1.0-SNAPSHOT.jar</systemPath> 30 </dependency> 31 </dependencies> 32 </project>
在以上配置中,除了依赖的坐标信息外,外部依赖还使用了 scope 和 systemPath 两个元素。
(1) scope 表示依赖范围,这里取值必须是 system,即系统。
(2) systemPath 表示依赖的本地构件的位置。
(4) 编译运行
执行如下命令:
D:\Workshop\maven\MavenDemo02>mvn clean compile
在项目根目录中生成了一个名为 target 的目录,该目录包含以下文件:
target
|- classes
|- maven-status
执行如下命令:
D:\Workshop\maven\MavenDemo02>cd target\classes
D:\Workshop\maven\MavenDemo02\target\classes>java com.example.App
显示结果如下:
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/Common
at com.example.App.main(App.java:7)
Caused by: java.lang.ClassNotFoundException: com.example.Common
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
...
注:错误信息显示,没有发现 com.example.Common 类,也就是在运行环境里没有找到 MavenDemo01-1.0-SNAPSHOT.jar 包。
设置本地临时环境变量 classpath,再运行:
D:\Workshop\maven\MavenDemo02\target\classes>set classpath=%classpath%;D:\Workshop\maven\MavenDemo01\target\MavenDemo01-1.0-SNAPSHOT.jar
D:\Workshop\maven\MavenDemo02\target\classes>java com.example.App
MavenDemo02: say hello
注:导入本地 JAR 包的方式,编译时需要指定 <systemPath> 固定全路径,运行时需要把依赖包设置到本地环境变量 classpath。很显然这种硬编码路径的方式,在编译和运行过程中,为了找到 MavenDemo01-1.0-SNAPSHOT.jar 包,都需要做额外的配置,不是一个好的解决方案。
Maven 提供的解决方案是安装 JAR 包到本地 Maven 仓库。
4) 安装 JAR 包到本地 Maven 仓库
Maven 提供了 mvn install 命令来实现安装 JAR 包到本地 Maven 仓库,假设本地 Maven 仓库的路径是 C:\Applications\java\maven-repository 。
打开 cmd 命令行窗口,进入 D:\Workshop\maven\MavenDemo01\ 目录,运行如下命令:
D:\Workshop\maven\MavenDemo01> mvn install
在目录 C:\Applications\Java\maven-repository\com\example 里就可以看到被安装的 MavenDemo01 包。
修改上文 MavenDemo02 中 pom.xml 的配置如下:
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 4 http://maven.apache.org/maven-v4_0_0.xsd"> 5 ... 6 7 <!--外部依赖--> 8 <dependency> 9 <groupId>com.example</groupId> 10 <artifactId>MavenDemo01</artifactId> 11 <version>1.0-SNAPSHOT</version> 12 </dependency> 13 </dependencies> 14 </project>
执行编译命令:
D:\Workshop\maven\MavenDemo02>mvn clean compile
运行如下命令:
D:\Workshop\maven\MavenDemo02\target\classes>java com.example.App
MavenDemo02: say hello
3. Maven 仓库
在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。
Maven 在某个统一的位置存储所有项目的构件,这个统一的位置,我们就称之为仓库。换言之,仓库就是存放依赖和插件的地方。
任何的构件都有唯一的坐标,该坐标定义了构件在仓库中的唯一存储路径。当 Maven 项目需要某些构件时,只要其 POM 文件中声明了这些构件的坐标,Maven 就会根据这些坐标找自动到仓库中找到并使用它们。
项目构建完成生成的构件,也可以安装或者部署到仓库中,供其他项目使用。
Maven 仓库可以分为本地仓库和远程仓库,远程仓库还可以分为中央仓库、私服、其他公共仓库。
1) 本地仓库
Maven 本地仓库实际上就是本地计算机上的一个目录(文件夹),它会在第一次执行 Maven 命令时被创建。
Maven 本地仓库可以储存本地所有项目所需的构件。当 Maven 项目第一次进行构建时,会自动从远程仓库搜索依赖项,并将其下载到本地仓库中。当项目再进行构建时,会直接从本地仓库搜索依赖项并引用,而不会再次向远程仓库获取。
Maven 本地仓库默认地址为 C:\%USER_HOME%\.m2\repository ,但出于某些原因(例如 C 盘空间不够),我们通常会重新自定义本地仓库的位置。这时需要修改 %MAVEN_HOME%\conf 目录下的 settings.xml 文件,通过 localRepository 元素定义另一个本地仓库地址,例如:
1 <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 4 http://maven.apache.org/xsd/settings-1.0.0.xsd"> 5 6 <localRepository>D:/java/maven-repository</localRepository> 7 8 </settings>
构件只有储存在本地仓库中,才能被其他的 Maven 项目使用。构件想要进入本地仓库,除了从远程仓库下载到本地仓库外,还可以使用命令 mvn install 将本地项目的输出构件安装到本地仓库中。
2) 中央仓库
中央仓库是由 Maven 社区提供的一种特殊的远程仓库,它包含了绝大多数流行的开源构件。在默认情况下,当本地仓库没有 Maven 所需的构件时,会首先尝试从中央仓库下载。
中央仓库具有如下特点:
(1) 由 Maven 社区管理
(2) 不需要配置
(3) 需要通过网络才能访问
我们可以通过 Maven 社区提供的 URL:http://search.maven.org/#browse,浏览其中的构件。中央仓库包含了绝大多数流行的开源 Java 构件及其源码、作者信息和许可证信息等。一般来说,Maven 项目所依赖的构件都可以从中央仓库下载到。
虽然中央仓库属于远程仓库的范畴,但由于它的特殊性,一般会把它与其他远程仓库区分开。我们常说的远程仓库,一般不包括中央仓库。
3) 远程仓库
如果 Maven 在本地仓库和中央仓库中都找不到依赖的库文件,它就会停止构建过程并输出错误信息到控制台。为避免这种情况的发生,Maven 还提供了远程仓库的概念,它是一种由开发人员自己定制的仓库,其中包含了供其他项目使用的代码库或者构件。
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 4 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 6 <modelVersion>4.0.0</modelVersion> 7 <groupId>com.example</groupId> 8 <artifactId>MavenDemo01</artifactId> 9 <version>0.0.1-SNAPSHOT</version> 10 <dependencies> 11 <dependency> 12 <groupId>com.example.common-lib</groupId> 13 <artifactId>common-lib</artifactId> 14 <version>1.0.0</version> 15 </dependency> 16 </dependencies> 17 <repositories> 18 <repository> 19 <id>test.lib1</id> 20 <url>http://download.test.org/maven2/lib1</url> 21 </repository> 22 <repository> 23 <id>test.lib2</id> 24 <url>http://download.test.org/maven2/lib2</url> 25 </repository> 26 </repositories> 27 </project>
4) 依赖搜索顺序
(1) 从本地仓库查找构件,如果没有找到,跳到第 2 步,否则继续执行其他处理;
(2) 从中央仓库查找构件,如果没有找到,并且已经设置其他远程仓库,然后移动到第 4 步;如果找到,那么将构件下载到本地仓库中使用;
(3) 如果没有设置其他远程仓库,Maven 则会停止处理并抛出错误;
(4) 在远程仓库查找构件,如果找到,则会下载到本地仓库并使用,否则 Maven 停止处理并抛出错误。
5) Maven 仓库镜像和私服
如果一个仓库 A 可以提供另一个仓库 B 的所有内容,那么就可以认为仓库 A 是仓库 B 的一个镜像(Mirror)。
(1) 使用镜像代替中央仓库
国内开发人员由于网络原因,直接从中央仓库下载构件时,速度较慢或不稳定,通常会使用中央仓库的国内镜像站来解决该问题。
配置 Maven 镜像的方法也非常的简单,只需要在 Maven 安装 config 目录中 setting.xml 文件的 mirrors 节点中,使用 mirror 标签添加镜像的相关信息即可。
目前国内使用最多,最稳定的中央仓库镜像分别是由阿里云和华为云提供的,它们的地址配置如下。
阿里云镜像地址
1 <mirror> 2 <id>aliyun</id> 3 <mirrorOf>central</mirrorOf> 4 <name>aliyun</name> 5 <url>https://maven.aliyun.com/repository/public</url> 6 </mirror>
华为云镜像地址
1 <mirror> 2 <id>huaweicloud</id> 3 <name>mirror from maven huaweicloud</name> 4 <mirrorOf>central</mirrorOf> 5 <url>https://repo.huaweicloud.com/repository/maven/</url> 6 </mirror>
以上配置中,mirrorOf 的取值为 central,表示该配置为中央仓库的镜像,所有对于中央仓库的请求都会转到该镜像。当然,我们也可以使用以上方式配置其他仓库的镜像。另外三个元素 id、name 和 url 分别表示镜像的唯一标识、名称和地址。
(2) 镜像与 Maven 私服配合使用
镜像通常会和 Maven 私服配合使用,由于 Maven 私服可以代理所有外部的公共仓库(包括中央仓库),因此对于组织内部的用户来说,使用一个私服就相当于使用了所有需要的外部仓库,这样就可以将配置集中到私服中,简化 Maven 本身的配置。这种情况下,用户所有所需的构件都可以从私服中获取,此时私服就是所有仓库的镜像。
1 <settings> 2 ... 3 <mirrors> 4 <mirror> 5 <id>nexus</id> 6 <mirrorOf>*</mirrorOf> 7 <name>nexus</name> 8 <url>http://localhost:8082/nexus/content/groups/bianchengbang_repository_group/</url> 9 </mirror> 10 </mirrors> 11 ... 12 </settings>
以上配置中,mirrorOf 元素的取值为 * ,表示匹配所有远程仓库,所有对于远程仓库的请求都会被拦截,并跳转到 url 元素指定的地址。
为了满足一些较为复杂的需求,Maven 还支持一些更为高级的配置。
(1) <mirrorOf>*</mirrorOf>:匹配所有远程仓库。
(2) <mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用 localhost 和 file:// 协议的除外。即,匹配所有不在本机上的远程仓库。
(3) <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库 repo1 和 repo2,使用逗号分隔多个远程仓库。
(4) <mirrorOf>*,!repo1</miiroOf>:匹配所有远程仓库,repo1 除外,使用感叹号将仓库从匹配中排除。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务时,Maven 也无法访问被镜像仓库,因而将无法下载构件。
(3)Maven 私服
Maven 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的远程仓库(中央仓库、其他远程公共仓库)。
建立了 Maven 私服后,当局域网内的用户需要某个构件时,会按照如下顺序进行请求和下载。
(1) 请求本地仓库,若本地仓库不存在所需构件,则跳转到第 2 步;
(2) 请求 Maven 私服,将所需构件下载到本地仓库,若私服中不存在所需构件,则跳转到第 3 步。
(3) 请求外部的远程仓库,将所需构件下载并缓存到 Maven 私服,若外部远程仓库不存在所需构件,则 Maven 直接报错。
此外,一些无法从外部仓库下载到的构件,也能从本地上传到私服供其他人使用。
Maven 私服具有以下 5 点优势:
(1) 节省外网带宽: 大量对于外部远程仓库的重复请求,会消耗很大量的带宽,利用 Maven 私服代理外部仓库后,能够消除对外部仓库的大量重复请求,降低外网带宽压力。
(2) 下载速度更快: Maven 私服位于局域网内,从私服下载构建更快更稳定。
(3) 便于部署第三方构件: 有些构件是无法从任何一个远程仓库中获得的(例如,某公司或组织内部的私有构件、Oracle 的 JDBC 驱动等),建立私服之后,就可以将这些构件部署到私服中,供内部 Maven 项目使用。
(4) 提高项目的稳定性,增强对项目的控制: 建立私服后,即使外部网络状况不佳甚至中断,只要私服中已经缓存了所需的构件,Maven 也能够正常运行。此外,一些私服软件(如 Nexus)还提供了很多额外控制功能,例如,权限管理、RELEASE/SNAPSHOT 版本控制等,可以对仓库进行一些更加高级的控制。
(5) 降低中央仓库得负荷压力: 由于私服会缓存中央仓库得构件,避免了很多对中央仓库的重复下载,降低了中央仓库的负荷。
搭建 Maven 私服的仓库管理器(Repository Manager),主要有以下 3 种:
(1) Apache Archiva
(2) JFrog Artifactory
(3) Sonatype Nexus
其中,Sonatype Nexus 是当前最流行,使用最广泛的 Maven 仓库管理器。