从0到0.1学习 maven(二:坐标、依赖和仓库)

该文章为maven系列学习的第二篇,共三篇
第一篇快速入口:从0到0.1学习 maven(一:概述及简单入门)
第三篇快速入口:从0到0.1学习 maven(三:声明周期、插件、聚合与继承)

坐标

上一篇说到,作为项目依赖管理工具的maven,坐标是重要的基础。
maven的坐标系统包括 groupId, artifactId, version, packaging, classifier。每个构建都有并且必须有一个坐标,这也意味着开发自己项目的时候,也需要为其定义坐标。下面介绍每个坐标的意义:

  • groupId:定义当前maven项目隶属的实际项目
  • artifactId: 定义一个maven模块。一般的,maven生成的构建会用artifactId作为开头。此外,通常会将 项目的实际名称作为前缀。例如实际项目名为nexus,模块有core,service,web,那artifactid可定义为nexus-core,nexus-service, nexus-web.
  • version: 定义maven项目当前所处的版本。
  • packaging:定义maven的打包方式,默认为jar
  • classifier:用于帮助定义构架输出的一些附属构件。
    其中,前三个都是必须的,packaging是可选的,classifier不能直接定义。

依赖

首先,依赖声明可以包含如下元素

        <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <type>...</type>
            <scope>...</scope>
            <exclusions>
                <exclusion>
                    <groupId>...</groupId>
                    <artifactId>...</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

以下会介绍这些标签的使用。

依赖范围

首先,maven在编译项目主代码的时候需要使用一套classpath,编译和运行会使用另外两套classpath。这主要是由于在不同场景下的依赖不同,比如在测试场景下要使用JUnit,在编译和运行场景下却无需使用。classpath共有三种:编译classpath,测试classpath,运行classpath。
依赖范围就是用来控制与三种classpath之间的关系,共有以下六种依赖范围,默认为compile

  • compile:编译依赖范围。对三种classpath都有效
  • test: 测试依赖范围,只对测试classpath有效
  • provided:已提供依赖范围,对编译和测试classpath有效。
  • runtime:运行时以来范围,对测试和运行classpath有效
  • system:系统依赖范围,这种依赖的依赖范围与provided一致,但需要通过systemPath显式的指定依赖文件的路径。因此这种方法对本地是有强依赖性的,需要谨慎使用。
  • import:导入依赖范围。

传递性依赖

maven的依赖是可传递的,也就是,不需要再引入依赖的依赖。A对B有依赖,B依赖着C,maven会解析直接依赖的pom,将必要的间接依赖使用传递性依赖的形式引入到当前的项目中。这里A对于C就是传递性的依赖。
此外,传递范围也会对依赖性传递产生影响,依然使用上面的例子,A对于B是第一直接依赖,B对于C是第二直接依赖,依赖的传递范围与传递性影响如下表,左侧列表示第一依赖范围,上方行表示第二依赖范围。

compiletestprovidedruntime
compilecompile--runtime
testtest--test
providedprovided-providedprovided
runtimeruntime--runtime

依赖调解

传递性依赖可以帮助我们省略很多依赖声明,但是项目中可能面对的问题是,对于同一个包引入了不同的版本,哪个版本会被解析使用呢?Maven依赖调解的第一原则就是

路径近者优先

例如,项目中有两条依赖A→B→C(Version 1.0);A→C(Version 2.0)。根据路径近者优先的原则,会选择C(Version 2.0)。那针对相同路径长度的依赖,则采用第二原则

第一声明者优先

简单来讲,就是在路径长度一样的时候,谁先声明就使用谁。

可选依赖

若项目A中的依赖B,其拥有可选依赖C,则C不会被传递。需要在A中进行显式的依赖。举个例子,若B的作用是作为多种数据库的JDBC,它将mysql,oracle,等等都作为了可选依赖,但是在使用的时候只会用到一种依赖。那A在使用B时,就要显式地在A的pom中说明对mysql还是oracle等的依赖。

依赖排除

有时会由于某些原因,不希望自动导入某些传递性依赖。可以使用extensions进行显式的依赖排除。只需要groupId和artifactId就可以定位到该依赖包,而不用version,因为本身只会引入一个版本的依赖。
关于idea中如何筛选以及排除依赖,可以参考我之前写的一篇教程。
Java项目使用intellij-IDEA查看依赖包版本是否有冲突(方法及工具)附截图

归类依赖

用过spring的朋友们肯定知道,如果引入spring框架相关的包,会叽里呱啦引入很多,比如spring-beans, context, core, support,这些都是来自一个项目的不同模块,也需要用相同版本。另外在升级的时候,这些包也要一起升级,因此这可以使用变量的方式,来做归类。

<properties>
	<spring-version>4.1.1</spring-version>
</properties>
.......

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

优化依赖

经过以上的种种操作,最后得到的依赖叫可解析依赖。可以用如下命令查看当前项目中的依赖。

mvn dependency:list

此外,也可以通过以下命令来查看maven解析的路径

mvn dependency:tree

还有个实用的小工具

mvn dependency:analyze

它可以帮助我们找到已引入但是没有使用的依赖,和没有引入但是使用了的依赖。但是对于引入却没使用的依赖不要盲目的进行删除。因为这里分析的只是编译主代码和测试代码用到的依赖,而运行和执行测试的代码依赖没有被包含。

仓库

简单来讲,仓库就是根据前面说的坐标存放构件的地方。插件,依赖,或者项目模块构建的输出都可以是构件。

路径生成

仓库是一层一层的,根据groupId,artifactId,version生成类似于文件系统的路径。举个例子,假设groupId=org.sth.someorg, artifactId=lalala,version=9.9.9,classifier=jdk8, packaging=jar

  1. 首先根据groupId,将.用/替换 即变为org/sth/someorg
  2. 再把artifactId加入刚刚的路径里org/sth/someorg/lalala
  3. 加入版本信息 org/sth/someorg/lalala/9.9.9
  4. 依次加入artifactId,连接符,与version:org/sth/someorg/lalala/9.9.9/lalala-9.9.9
  5. 如果有classifier也一起加入:org/sth/someorg/lalala/9.9.9/lalala-9.9.9-jdk8
  6. 最后加入拓展名:org/sth/someorg/lalala/9.9.9/lalala-9.9.9.jar

仓库分类

maven的仓库分成两类,本地仓库和远程仓库。会根据坐标现在本地仓库里找,如果本地仓库有该构件则直接使用。如果没有那就会去远程仓库找,找到后再放到本地仓库。如果本地和远程都没有找到,则报错。

本地仓库

默认情况下,用户在自己的用户目录下会有.m2/repository的仓库目录。可以通过编辑.m2目录下的settings.xml文件,设置localRepository元素值来定义想要的仓库地址。但是默认这个文件是不存在的,需要用户在$M2_HOME/conf/settings.xml复制过来。

另外,构建除了从远程仓库下来到本地,还可以将本地项目的构建安装到maven仓库中。我们使用的install就是在将构建结果放入本地仓库。

远程仓库

远程仓库可以分成,中央仓库,私服和其他公共库

  • 中央仓库是默认的远程仓库,也就是 https://repo.maven.apache.org/maven2

在这里插入图片描述

  • 私服是架设在局域网的仓库,在下载构建时,maven会先从私服请求,如果私服没有则从外部远程下载,缓存在私服后再为maven下载提供请求。

  • 可以在settings.xml中的repositories元素下增加repository标签来声明远程仓库。

快照

首先,先看一下带有快照版本号的版本号长什么样子

快照版:2.1-SNAPSHOT
时间戳版:2.1-20230131.170912-3

快照版实际上是未完成开发的非正式不稳定版本。在将版本号设定成x.x-SNAPSHOT,在发布到私服时,maven会为其自动打上时间戳,这样在之后就可以快速的找到当前的最新版本。以上面的时间戳版本号为例,2.1-20230131.170912-3代表着2.1版本下2023年1月31日17点09分12秒的第三个构件。如果项目中配置了snapshot的依赖,在项目构建时则会从仓库中检查最新的构件,有更新则下载。默认每天检查一次更新,可以通过仓库配置的updatePolicy设置更新频率,也可以使用-U参数强制更新mvn clean install -U

部署至远程仓库

上面提到了,快照需要发布到私服保证能获取最新版本,那怎么将构建发布至远程仓库呢?

  1. 编辑项目的pom.xml,配置distributionManagement。
    子标签repository表示发布版本的构建仓库,snapshotRepository表示快照版本的仓库。其中id,name,url都是必选的。id表示远程仓库的唯一标识,name方便人阅读,url表示该仓库的地址。
<project>    
  ...    
  <distributionManagement>    
    <repository>    
      <id>nexus-releases</id>    
      <name>Nexus Release Repository</name>    
      <url>http://127.0.0.1:8080/nexus/content/repositories/releases/</url>    
    </repository>    
    <snapshotRepository>    
      <id>nexus-snapshots</id>    
      <name>Nexus Snapshot Repository</name>    
      <url>http://127.0.0.1:8080/nexus/content/repositories/snapshots/</url>    
    </snapshotRepository>    
  </distributionManagement>    
  ...    
</project>    
  1. 认证
    在pom.xml中添加server标签配置仓库认证信息
<servers>
                
		<server>
			<id>nexus-releases</id>
			<username>ptyp</username>
			<password>yourpassword</password>
        </server>
</servers>

这里的server id要和前面repository的id保持一致。

  1. 运行mvn clean deploy。
    maven就会将构件部署到对应的远程仓库上。

从仓库解析依赖的机制

  1. 当依赖范围为system时,直接从本地解析构建
  2. 解析好路径后,如果在本地仓库发现构件则解析成功
  3. 若本地无该构建,若版本为x.x这种显式的结构,则从远程仓库下载并完成解析
  4. 若版本为RELEASE或LASTEST,则根据更新策略读取所有远程仓库的groupId/artifactid/maven-metadata.xml,将其与本地仓库的元数据合并后得到对应的版本,再重复步骤2和3
  5. 若版本为SNAPSHOT,则根据更新策略读取所有远程仓库的groupId/artifactid/version/maven-metadata.xml,将其与本地仓库的元数据合并后得到对应的版本,再重复步骤2和3
  6. 若解析后版本号为时间戳格式,则复制该构件并将其重命名例如SNAPSHOT,并使用新复制出来的构件。(简单来说,就是最后使用的构件名不会包含时间戳)

此外,有几点需要额外注意:

  • 如果使用非x.x这样显式的版本号,即以上的4和5,需要在settings.xml中的仓库配置中打开对应开关(<releases><enabled>)
	<profiles>
	
		<profile>
			<id>dev</id>
			<repositories>
				<repository>
					<id>mvn-repo</id>
					<url>xxx</url>
					<releases>
						<enabled>true</enabled>
					</releases>
					<snapshots>
						<enabled>true</enabled>
						<updatePolicy>always</updatePolicy>
					</snapshots>
				</repository>
			</repositories>
		</profile>
	</profiles>
  • updatePolicy配置了检查更新的频率如每日,从不,永远检查等。但若用户在命令行中使用了-U,maven则会忽略updatePolicy的配置。
  • RELEASE表示最新发布版本,LATEST表示的是最新版本(包含SNAPSHOT),这两个都是基于groupId/artifactid/maven-metadata.xml文件得到的
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>5.2.10.BUILD-SNAPSHOT</version>
  <versioning>
    <lastest>1.1.1-SNAPSHOT<lastest>
    <release>1.1.0<release>
    <versions>
		<version>0.8.0</version>
		<version>0.9.0</version>
		<version>1.1.0</version>
		<version>1.1.1-SNAPSHOT</version>
	</versions>
    <lastUpdated>20201023123408</lastUpdated>
  </versioning>
</metadata>

可以看到lastest指向了最新的版本,而versions里面列着全部的版本。
但是需要说明的是由于release和lastest这样的声明获取到的版本可能随时都会发生改变,因此可能会存在潜在的问题。从maven3开始已经不支持配置latest和release。但如果不配置插件版本,默认会为release。

  • SNAPSHOT表示的是快照版本,是基于groupId/artifactid/version/maven-metadata.xml文件得到的
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>5.2.10.BUILD-SNAPSHOT</version>
  <versioning>
    <snapshot>
      <timestamp>20201023.122514</timestamp>
      <buildNumber>33</buildNumber>
    </snapshot>
    <lastUpdated>20201023123408</lastUpdated>
    <snapshotVersions>
      <snapshotVersion>
        <classifier>javadoc</classifier>
        <extension>jar</extension>
        <value>5.2.10.BUILD-20201023.122514-33</value>
        <updated>20201023122514</updated>
      </snapshotVersion>
      <snapshotVersion>
        <classifier>sources</classifier>
        <extension>jar</extension>
        <value>5.2.10.BUILD-20201023.122514-33</value>
        <updated>20201023122514</updated>
      </snapshotVersion>
      <snapshotVersion>
        <extension>jar</extension>
        <value>5.2.10.BUILD-20201023.122514-33</value>
        <updated>20201023122514</updated>
      </snapshotVersion>
    </snapshotVersions>
  </versioning>
</metadata>

这里比上一部分多了timestamp和buildNumber两个子元素,分别代表了时间戳和构建号,将这两个值拼起来则会得到仓库中该快照的实际版本号。

  • maven-metadata.xml 不一定永远都是正确的,如果出现构建解析错误可能是元数据出了问题,那就需要人工修复。

镜像

如果A的内容都能从B获取,则将B称为A的镜像。镜像一般都会比原仓库的获取速度更快,因此可以使用镜像来替代中央仓库。

<!-- 阿里云镜像 -->
<mirror> 
	<id>alimaven</id> 
	<name>aliyun maven</name> 
	<url>http://maven.aliyun.com/nexus/content/repositories/central/</url> 
	<mirrorOf>central</mirrorOf> 
</mirror>

mirrorOf指的是,所有对于central的请求都会转到该镜像。

另外,镜像也可以结合私服。因为私服可以代理包括中央仓库在内的任何外部仓库,因此使用私服地址就相当于使用了所有需要的外部仓库,简单讲就是私服是所有仓库的镜像,由此简化配置。

<mirror> 
	<id>some-internal-repo</id> 
	<name>Internal Repository Name</name> 
	<url>http://-----------</url> 
	<mirrorOf>*</mirrorOf> 
</mirror>

这里的mirrorOf是*表示,所有仓库的镜像都会被转到该url上。

mirrorOf中如果配置多个仓库可以使用逗号进行分割,也可以使用!进行排除,如<mirrorOf>,!repo/mirrorOf>代表匹配除了repo之外的全部远程仓库;
<mirrorOf>external:
</mirrorOf> : 表示匹配除了不在本机之外(文件系统或localhost)的远程仓库

镜像会完全拦截mirrorOf中配置的请求,因此就算镜像地址失效,也不会有请求打到被镜像的地址上。

第二节完

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值