写点什么

五分钟快速掌握 Maven 的核心概念

用户头像
田维常
关注
发布于: 2021 年 01 月 11 日

关注“Java 后端技术全栈”


回复“面试”获取全套面试资料


前两天在一个技术群,有人还在问 maven 中 groupId、artifactId、version 这些关键字的含义是什么,于是,我觉得还是很有必要来聊聊 Maven 中的这些核心概念。


成功不是将来才有的,而是从决定去做的那一刻起,持续累积而成。


今天我们来学习 Maven 中的核心概念。了解了这些核心概念后,我们后面就可以更深层次的学习和使用 Maven。


图片


坐标


坐标的概念


来自百度百科


能够确定一个点在空间的位置的一个或一组数,叫做这个点的坐标。通常由这个点到垂直相交的若干条固定的直线的距离来表示 。这些直线叫做坐标轴。坐标轴的数目在平面上为 2(x,y),在空间里为 3(x,y,z)。


图片


其实就是可以标识平面中或空间里唯一的一个点。


Maven 中的坐标


Maven 其中一个核心的作用就是管理项目的依赖,引入我们所需的各种 jar 包等。为了能自动化的解析任何一个 Java 构件,Maven 必须将这些 Jar 包或者其他资源进行唯一标识,这是管理项目的依赖的基础,也就是我们要说的坐标。包括我们自己开发的项目,也是要通过坐标进行唯一标识的,这样才能才其它项目中进行依赖引用。


案例


依赖时候:比如下面我们依赖 junit 的 jar 包。


<!-- pom.xml中 --><dependency>  <groupId>junit</groupId>  <artifactId>junit</artifactId>  <version>3.8.1</version>  <scope>test</scope></dependency>
复制代码


项目中定义我们的项目将打成 jar 或者 war 包。


<?xml version="1.0" encoding="UTF-8"?><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>        <groupId>com.tian</groupId>        <artifactId>maven-demo</artifactId>        <version>1.0-SNAPSHOT</version>        <!-- 默认是jar -->        <packaging>jar</packaging></project>
复制代码


最后打出来的 jar 或 war 的形式的形式:


artifactid-version.jarartifactid-version.war
复制代码


packaging 标签默认是 jar,所以通常我们在没有指定打成 jar 包还是 war 的时候,最终打成的就是 jar 包。


Maven 坐标的组成


「groupId」组织标识(包名)。定义当前 Maven 项目隶属的实际项目。首先,Maven 项目和实际项目不一定是一对一的关系。比如 SpringFrameWork 这一实际项目,其对应的 Maven 项目会有很多,如 spring-core,spring-context 等。这是由于 Maven 中模块的概念,因此,一个实际项目往往会被划分成很多模块。其次,groupId 不应该对应项目隶属的组织或公司。原因很简单,一个组织下会有很多实际项目,如果 groupId 只定义到组织级别,而后面我们会看到,artifactId 只能定义 Maven 项目(模块),那么实际项目这个层次将难以定义。最后,groupId 的表示方式与 Java 包名的表达方式类似,通常与域名反向一一对应。上例中,groupId 为 junit,是不是感觉很特殊,这样也是可以的,因为全世界就这么个 junit,它也没有很多分支。


「artifactId」项目名称。该元素定义当前实际项目中的一个 Maven 项目(模块),推荐的做法是使用实际项目名称作为 artifactId 的前缀。比如上例中的 junit,junit 就是实际的项目名称,方便而且直观。在默认情况下,maven 生成的构件,会以 artifactId 作为文件头,如 junit-3.8.1.jar,使用实际项目名称作为前缀,就能方便的从本地仓库找到某个项目的构件。


「version」项目的当前版本或者我们要依赖 jar 的版本。该元素定义了使用构件的版本,如上例中 junit 的版本是 3.8.1,你也可以改为 4.0 表示使用 4.0 版本的 junit。


「packaging」项目的打包方式,最为常见的 jar 和 war 两种,默认是 jar。定义 Maven 项目打包的方式,使用构件的什么包。首先,打包方式通常与所生成构件的文件扩展名对应,如上例中没有 packaging,则默认为 jar 包,最终的文件名为 junit-3.8.1.jar。也可以打包成 war 等。


「classifier」 该元素用来帮助定义构建输出的一些附件。附属构件与主构件对应,如上例中的主构件为 junit-3.8.1.jar,该项目可能还会通过一些插件生成如 junit-3.8.1-javadoc.jar,junit-3.8.1-sources.jar, 这样附属构件也就拥有了自己唯一的坐标。


上述 5 个元素中,groupId、artifactId、version 是必须定义的,packaging 是可选的(默认为 jar),而 classfier 是不能直接定义的,需要结合插件使用。


Maven 为什么使用坐标呢?


  • Maven 世界里拥有大量构建,我们需要找一个用来唯一标识一个构建的统一规范。

  • 拥有了统一规范,就可以把查找工作交给机器。


maven 依赖管理


依赖


依赖通常表现为:我需要你的东西,就像情侣之间相互依赖,夫妻之间相互依赖,人依赖于水,人依赖于粮食等。


在 Maven 中则表现为:项目中用到 b.jar 包的每个类,此时的项目就依赖 b.jar。


复杂点关系就是多层依赖:a.jar 包依赖 b.jar 包,还有可能 b.jar 包依赖 c.jar。这种现象也可以称之为依赖传递性。


图片


我们的项目间接性的依赖了 b.jar。


依赖配置


Maven 中依赖配置案例如下:


<!--添加依赖配置--><dependencies>  <!--项目要使用到junit的jar包,所以在这里添加junit的jar包的依赖-->  <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.9</version>      <scope>test</scope>  </dependency>  <!--项目要使用到Hello的jar包,所以在这里添加Hello的jar包的依赖-->  <dependency>      <groupId>com.tian.maven</groupId>      <artifactId>user-service</artifactId>      <version>0.0.1-SNAPSHOT</version>      <scope>compile</scope><!-- 依赖范围-->  </dependency></dependencies>
复制代码


依赖范围


所谓的依赖范围就是指我们在什么需要依赖的 jar。有的是在编译的时候就需要,有的是测试的时候需要等。


依赖范围 scope 有以下 6 种:


「compile」 默认编译依赖范围。对于编译,测试,运行三种 classpath 都有效。即在编译、测试和运行的时候都要使用该依赖 jar 包;


「test」测试依赖范围。只对于测试 classpath 有效。而在编译和运行项目时无法使用此类依赖,典型的是 JUnit,它只用于编译测试代码和运行测试代码的时候才需要;


「provided」已提供依赖范围。对于编译,测试的 classpath 都有效,但对于运行无效。因为由容器已经提供,例如 servlet-api.jar,这个在编译和测试的时候需要用到,但是在运行的时候,web 容器已经提供了,就不需要 maven 帮忙引入了。


「runtime」运行时依赖范围,使用此依赖范围的 maven 依赖,对于编译测试、运行测试和运行项目的 classpath 有效,但在编译主代码时无效,比如 jdbc 驱动实现,运行的时候才需要具体的 jdbc 驱动实现。


「system」系统依赖范围,使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径,不依赖 Maven 仓库解析,所以可能会造成建构的不可移植(即就是在你的电脑上可能没问题,但是到别人电脑上那就说不清楚了),有点类似 provided ,注意这个 system 谨慎使用。


<systemPath>${java.home}/lib/rt.jar</systemPath>
复制代码


「import」仅 pom 在本节中的类型依赖项上支持此作用域。它指示依赖关系将被指定的 pom 部分中的有效依赖关系列表替换。由于已替换它们,因此范围为的依赖项 import 实际上不会参与限制依赖项的可传递性,在 springboot 和 springcloud 中用到的比较多。


以上六种范围中,常用的有 compile、test、runtime、provided 。


依赖范围不仅可以控制与三种 classpath 的关系,还对传递性依赖产生影响,依赖关系图如下:


图片


「注意」预期这应该是运行时范围,因此必须明确列出所有编译依赖项。但是,如果您依赖的库从另一个库扩展了一个类,则两者都必须在编译时可用。因此,即使编译时间相关性是可传递的,它们仍保留为编译范围。


Maven 仓库管理


Maven 仓库


用来统一存储所有 Maven 共享构建的位置,说白了就是用来存放 jar 包的,我们本地每次编译的时候没有对应 jar 包是编译通不过的,我们一个项目中是需要很多 jar 的依赖的,这时候就知道仓库的重要性了。


Maven 仓库布局


根据 Maven 坐标定义每个构建在仓库中唯一存储路径,大致为:


groupId/artifactId/version/artifactId-version.packaging


本地仓库


在上一篇文章中,每个用户只有一个本地仓库,默认是在~/.m2/repository/,~代表的是用户目录 。为了便于管理,一般都会自己搞一目录,专门用来存储本地仓库内容。这样我们开发的时候,依赖那个 jar 就直接去我们的本地仓库 repository 中去查找,如果没有,我们会从中央仓库中拉取。


中央仓库


基本上保存了对外开发的所有 jar 包,Maven 默认的远程仓库,(外国网站)URL 地址:http://search.maven.org/ 。还有比如阿里的仓库,我们在开发的时候,由于网络原因,很多人都喜欢使用阿里的这个仓库:http://maven.aliyun.com 。


这时候我们本地仓库和中央仓库的关系:


图片


私服


大部分公司都会搭建私服,私服就是一种特殊的远程仓库,它是架设在局域网内的仓库 。比如公司搭建局域网,公司也搞个仓库,然后开发人员就直接使用公司搭建的私服就行了,这样大大减少了网络开销以及开发成本(有时候外网访问很慢,会浪费大家开发时间的)。


这样开发人员每次需要每个 jar 包就直接从公司的私服里拉取,不需要使用外网去中央仓库里拉取了。总之节约时间和节约网络开始。并且有些企业还是不给外网的,这时候你就知道这个私服的重要性了。


增加了私服后,本地仓库+私服+中央仓库的关系图:


图片


面试中也频繁被问:本地仓库、私服以及中央仓库是什么关系?


Maven 生命周期


Maven 的 生命周期:从我们的项目构建,一直到项目发布的这个过程。


图片


每个阶段的说明:


图片


为了完成 default 生命周期,这些阶段(包括其他未在上面罗列的生命周期阶段)将被按顺序地执行。


Maven 有以下三个标准的生命周期:


  • Clean Lifecycle 在进行真正的构建之前进行一些清理工作。

  • Default Lifecycle 构建的核心部分,编译,测试,打包,部署等等。

  • Site Lifecycle 生成项目报告,站点,发布站点。


这三个标准它们是相互独立的,你可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。当然你也可以直接运行 mvn clean install site 运行所有这三套生命周期。


运行任何一个阶段的时候,它前面的所有阶段都会被运行,这也就是为什么我们运行 mvn install 的时候,代码会被编译,测试,打包。此外,Maven 的插件机制是完全依赖 Maven 的生命周期的,因此理解生命周期至关重要。


Maven 插件


Maven 是不做具体事情的,只是规定了生命周期的各个阶段和步骤,由集成到 Maven 中的插件完成。


  1. Maven 的核心仅仅定义了抽象的生命周期,具体的任务都是交由插件完成的。

  2. 每个插件都能实现多个功能,每个功能就是一个插件目标。

  3. Maven 的生命周期与插件目标相互绑定,以完成某个具体的构建任务, 例如 compile 就是插件 maven-compiler-plugin 的一个插件目标。


关于插件,这里就说个大概,后续会出一篇文章专门来说 Maven 插件。


排除不需要依赖


<dependency>    <groupId>com.tian.maven</groupId>    <artifactId>my-maven</artifactId>    <version>1.0.0</version>    <exclusions>        <exclusion>            <groupId>com.tian.maven</groupId>            <artifactId>your-maven</artifactId>        </exclusion>    </exclusions></dependency>
复制代码


上面使用使用 exclusions 元素排除了 my-maven->your-maven 依赖的传递,也就是 my-maven->your-maven 不会被传递到当前项目中。


exclusions 中可以有多个 exclusion 元素,可以排除一个或者多个依赖的传递,声明 exclusion 时只需要写上 groupId、artifactId 就可以了,version 可以省略。


总结


本文讲述 Maven 坐标,Maven 依赖管理、Maven 仓库管理、Maven 生命周期以及简单介绍了 Maven 插件。有了这些概念作为铺垫,我们就可以更深层次去体会,为什么我们在工作室这么用的。


「只要路是对的,就不怕路远。」


推荐阅读


面试官系列:说说Integer缓存范围


面试官:说说你对序列化的理解


搞定这24道JVM面试题,要价30k都有底气~


发布于: 2021 年 01 月 11 日阅读数: 271
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
五分钟快速掌握Maven的核心概念