活久见!64 张图带你 Maven 实战通关
Posted 里奥ii
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了活久见!64 张图带你 Maven 实战通关相关的知识,希望对你有一定的参考价值。
Maven 概述
看完本篇文章后相信你对 Maven 的理解能更进一步
常规项目开发存在的问题
通常 Web 项目
开发只会创建一个工程,然后所有的 jar 包都会存放到 WEB-INF/lib
目录下,如下图所示:
上面的目录结构存在以下问题
- 一个项目就是一个
web 工程
。如果项目比较庞大,那么利用包名 package 来划分模块,显然容易造成混淆而且不利于分工合作; - 项目中需要的 jar 包必须手动复制,粘贴到 WEB-INF/lib 目录下。这会导致每创建一个新的工程就需要将 jar 包重复复制到 lib 目录下,从而造成工作区存在大量重复的文件;
- jar 需要我们手动去官网上或者其他途径下载;
- 一个 jar 包依赖的其他 jar 包,需要自己手动加入到项目中,而且很有可能我们漏掉了某个依赖关系,导致项目运行报错。
那么如何解决这些问题呢?我们的主角 Maven 应运而生了。
什么是 Maven
Maven 读音是 [ˈmevən],也就是霉文
,而不是读马文
。它是一个项目管理和综合工具,Maven 使用标准的目录结构和默认构建生命周期。提供了开发人员构建一个完整的生命周期框架,开发团队可以自动完成该项目的基础设施建设。相信如果对 Maven 没有任何了解的,看了这段话等于没看,不过没关系,后面我们将会逐渐揭开 Maven 的神秘面纱。什么是 Maven,你只需要知道这玩意能简化和标准化项目建设过程。
Maven 的历史
Maven的 最初设计,以简化Jakarta Turbine 项目的建设进程。有几个项目,每个项目包含了稍微不同的 Ant 构建文件。JAR 中检查到 CVS。Apache 组织开发的 Maven 可以建立多个项目,发布项目信息,项目部署。
Maven 的目标
Maven主要目标是提供开发人员
- 项目是可重复使用,易维护,更容易理解的一个综合模型。
- 插件或交互的工具,这种声明性的模式。
❝Maven 项目的结构和内容是在一个 XML 文件中声明,pom.xml 的项目对象模型(POM),这是整个 Maven 系统的基本单元。
❞
Maven 的理念是 「约定优于配置!!!」
开发人员不需要创建构建过程本身,不必知道提到的每一个配置的详细信息。Maven 提供了合理的默认行为的项目。创建一个 Maven 项目时,Maven 创建默认的项目结构。开发者只需要把相应的文件和她需要在 pom.xml 中定义即可。
Maven 的安装和配置
Maven 下载
官网下载地址:http://maven.apache.org/download.cgi
配置 Maven 环境变量
将下载的 maven 压缩包解压到电脑的某个盘符,我这里是 D:\\JavaTools\\apache-maven-3.3.9
「右键---计算机属性----高级系统设置---高级---环境变量---系统变量----新建」
变量名:MAVEN_HOME
变量值:D:\\JavaTools\\apache-maven-3.3.9
❝这个值是 maven 压缩包解压的位置
❞
「将 MAVEN_HOME 添加到 path 目录下」
选择 path---编辑---新建---将 %MAVEN_HOME%\\bin 添加进去,然后点击确定就可以了
查看环境变量是否配置成功
打开命令提示符,快捷键 windows + R.输入 mvn -v
如果出现如下 maven 的版本信息,那么就配置成功
在 eclipse 中集成 Maven 插件
在 eclipse 指定 Maven 插件的位置 : Window->Preferences->Maven->Installations
指定 conf/settings.xml 的位置,进而指定 Maven 本地仓库的位置
修改 settings.xml 如下标签的位置即可:自定义路径
Maven 工程目录介绍
上面我们配置并安装好了 Maven,那么现在我们就来介绍一下如何用 eclipse 创建一个 Maven 工程,然后介绍 Maven 工程的目录结构。
eclipse 创建 Maven 工程
「第一步:File-->New--->Maven Project」
「第二步:勾上 Create a simple project ,然后点击 next」
「第三步:填写 Group Id 和 Artifact Id」
groupid
和 artifactId
被统称为坐标
是为了保证项目唯一性而提出的,如果你要把你项目弄到 maven 本地仓库去,你想要找到你的项目就必须根据这两个 id
去查找。
groupId 一般分为多个段,这里只说两段
第一段为域,第二段为公司名称。域又分为org、com、cn等等许多,其中org为非营利组织,com为商业组织。举个apache公司的tomcat项目例子:这个项目的groupId是org.apache,它的域是org(因为tomcat是非营利项目),公司名称是apache,artigactId是tomcat。
ArtifactID 就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称。比如我创建一个项目,我一般会将 groupId 设置为 com.ys,com 表示域,ys 是我个人姓名缩写,「Artifact Id」设置为 hellomaven,表示你这个项目的名称是 hellomaven,依照这个设置,你的包结构最好是 com.ys.hellomaven 打头的,如果有个StudentDao,它的全路径就是 com.ys.hellomaven.dao.StudentDao
Maven Java 工程目录结构
我们根据上面的步骤,创建出如下的 maven 工程:
对每个目录结构的解析如下:
为什么 maven 工程的目录结构要这样呢?
- Maven 要负责项目的自动化构建,以编译为例,Maven 要想自动进行编译,那么它必须知道 Java 的源文件保存在哪里,这样约定之后,不用我们手动指定位置,Maven 能知道位置,从而帮我们完成自动编译。
- 遵循 约定>>>配置>>>编码。即能进行配置的不要去编码指定,能事先约定规则的不要去进行配置。这样既减轻了劳动力,也能防止出错。
「pom.xml 文件」
Project Object Model 项目对象模型,Maven 的核心配置文件,pom.xml,与构建过程相关的一切设置都在这个文件中进行配置。
❝这个文件非常重要,我们后面会详细讲解。
❞
常用的 Maven 命令
创建 Maven 工程
在 src/main/java 新建包 com.ys.maven,然后在这个包中创建类 HelloMaven.java
package com.ys.maven;
public class HelloMaven
//传入一个字符串并返回
public String Hello(String name)
return name;
在 src/test/java 新建包 com.ys.maven,然后在这个包中创建类 HelloTest.java
package com.ys.maven;
import junit.framework.Assert;
import org.junit.Test;
public class HelloTest
@Test
public void testHello()
HelloMaven he = new HelloMaven();
String name = he.Hello("Tom");
//判断 Hello 传入的参数是否是 "maven"
Assert.assertEquals("maven", name);
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>
<groupId>com.ys</groupId>
<artifactId>hellomaven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
为什么要这样写,我们后面会详细讲解。
Maven 的常用命令
mvn compile 编译,将Java 源程序编译成 class 字节码文件。
mvn test 测试,并生成测试报告
mvn clean 将以前编译得到的旧的 class 字节码文件删除
mvn pakage 打包,动态 web工程打 war包,Java工程打 jar 包。
mvn install 将项目生成 jar 包放在仓库中,以便别的模块调用
compile:将Java 源程序编译成 class 字节码文件。
第一步:选择 pom.xml 文件,右键--->Run As ---->2 Maven build...
第二步:在第一步执行完后弹出来的对话框中,输入 compile,然后点击 Run 按钮
第三步:查看控制台
第四步:在 target 目录下,我们会发现编译生成的 class 文件
test:测试,并生成测试报告
- 第一步:选择 pom.xml 文件,右键--->Run As ---->2 Maven build...,然后在弹出框中输入 test,或者选择 pom.xml 文件,右键--->Run As------>6 Maven test
- 第二步:查看控制台,分析测试程序,我们传入的参数是Tom,而我们希望的是maven,很显然是不相等的,那么测试失败
如果测试类 HelloTest.java改为如下:
重新执行 mvn test 命令,控制台如下:
生成的测试报告可以在如下目录查看:target/surefire-reports
mvn clean 将以前编译得到的旧的 class 字节码文件删除
- 第一步:选择 pom.xml 文件,右键--->Run As ---->2 Maven build...,然后在弹出框中输入 clean
或者选择 pom.xml 文件,右键--->Run As------>3 Maven clean,如下图
- 第二步:查看控制台
- 第三步:发现 mvn compile 编译好的文件这时已经清除了
mvn pakage 打包,动态 web工程打 war包,Java工程打 jar 包。
- 第一步:选择 pom.xml 文件,右键--->Run As ---->2 Maven build...,然后在弹出框中输入 package
- 第二步:查看控制台
- 第三步:进入到 target 目录,会发现打出来的 jar 包
mvn install 将项目生成 jar 包放在仓库中,以便别的模块调用
Maven 坐标的概念和依赖管理
我们知道 maven 能帮我们管理jar包,那么它是怎么管理的呢?我们下面就来讲解一下
什么是坐标
「数学中的坐标」
在平面上,使用 X 、Y 两个向量可以唯一的定位平面中的任何一个点
在空间中,使用 X、Y、Z 三个向量可以唯一的定位空间中的任意一个点
「Maven 中的坐标」
俗称 gav:使用下面三个向量子仓库中唯一定位一个 Maven 工程
在项目中的 pom.xml 文件中,我们可以看到下面 gav 的定义
- groupid:公司或组织域名倒序 :com.ys.maven
- artifactid:模块名,也是实际项目的名称:Maven_05
- version:当前项目的版本:0.0.1-SNAPSHOT
「Maven 坐标和仓库,jar 包的关系」
什么是仓库,后面我们会详细讲解,现在你只需要知道是 Maven 用来存放 jar 包的地方。
那么依照上面定义的 gav,我们执行 mvn -install 命令,会出现什么情况呢?
首先进入到我们上面讲过的 settings.xml 文件配置的仓库目录。
将我们上面配置的 gav 向量组合起来就是目录:
com/ys/maven/Maven_05/0.0.1-SNAPSHOT/Maven_05-0.0.1-SNAPSHOT.jar
其次,我们观察打出来的 jar 包:Maven_05-0.0.1-SNAPSHOT.jar,也就是 artifactid-version.jar
Maven 依赖
什么是依赖
?相信有过一定开发经验的人知道,每当我们需要使用某个框架时,比如 SpringMVC,那么我们需要导入相应的 jar 包,但是手动导入包的时候,往往会漏掉几个 jar 包,那么在使用该框架的时候系统就会报错。那么我们就说导入的包与未导入的包存在依赖关系。而使用 Maven,我们只需要在 pom.xml 文件中进行相应的配置,它就会帮助我们自动管理 jar 包之间的依赖关系。那么它是怎么管理的呢,接下来我们会详细讲解。
依赖的详细配置
我们以 Junit 为例,在 pom.xml 文件中进行详细而完整的配置。
<project>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
- dependencies:一个 pom.xml 文件中只能存在一个这样的标签。用来管理依赖的总标签。
- dependency: 包含在 dependencies 标签中,可以有无数个,每一个表示一个依赖
- groupId、artifactId 和 version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根 据坐标才能找到需要的依赖。
- type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。
- scope:依赖的范围,默认值是 compile。后面会进行详解。
- optional:标记依赖是否可选。
- exclusions:用来排除传递性依赖,后面会进行详细介绍。
依赖的范围 scope
先放一张图
一般情况下,我们对前面三个依赖用的比较多。下面的主程序表示 maven 目录结构 src/main/java.测试程序目录结构为:src/test/java
「compile 范围依赖」
对主程序是否有效:有效
对测试程序是否有效:有效
是否参与打包:参与
是否参与部署:参与
典型例子:log4j
「test 范围依赖」
对主程序是否有效:无效
对测试程序是否有效:有效
是否参与打包:不参与
是否参与部署:不参与
典型例子:Junit
「provided 范围依赖」
对主程序是否有效:有效
对测试程序是否有效:有效
是否参与打包:不参与
是否参与部署:不参与
典型例子:servlet-api.jar,一般在发布到 服务器中,比如 tomcat,服务器会自带 servlet-api.jar 包,所以provided 范围依赖只在编译测试有效。
「runtime 范围依赖」:在测试、运行的时候依赖,在编译的时候不依赖。例如:JDBC 驱动,项目代码只需要 jdk 提供的 jdbc 接口,只有在执行测试和运行项目的时候才需要实现 jdbc 的功能。
接下来我们举几个例子在工程中实际去理解:
「test 依赖和 compile 依赖的区别:」
首先我们在 pom.xml 文件中配置,Junit 的 test 依赖
我们在主程序中去导入 Junit 的包,然后进行 mvn -compile 编译,很明显,test 范围的在主程序中无效,故编译会报错。
我们在 src/main/java 包下新建 MavenTest.java,并导入 Junit 包
然后执行 mvn -compile 操作,如下图报错信息:
我们将 Junit 的依赖范围改为 compile,然后执行 mvn -compile
发现 mvn -compile 没有报错了。
依赖的传递
比如我们创建三个 Maven 工程,maven-first,maven-second 以及 maven-third,而 third 依赖于 second,second 又依赖于 first,那么我们说 second 是 third 的第一直接依赖,first 是 second 的第二直接依赖。而 first 是 third 的间接依赖。
依赖之间的传递如下图:「第一列表示第一直接依赖,第一行表示第二直接依赖」
总结
当第二依赖的范围是 compile
的时候,传递性依赖的范围与第一直接依赖的范围一致。
当第二直接依赖的范围是 test
的时候,依赖不会得以传递。
当第二依赖的范围是 provided
的时候,只传递第一直接依赖范围也为provided
的依赖,且传递性依赖的范围同样为 provided;
当第二直接依赖的范围是 runtime
的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile
例外,此时传递的依赖范围为 runtime;
我们这里举个例子来看:
第二依赖范围是 test
Maven_first 的pom.xml 文件如下:
然后 Maven_second 依赖 Maven_fisrt,Maven_third 依赖 Maven-second,其pom.xml 文件如下
Maven_second的 pom.xml
Maven_third 的 pom.xml
我们发现在 Maven_third 和 Maven_second 都没有 Maven_first 引入的 Junit 包,正好符合上面总结的第二点:当第二直接依赖的范围是test的时候,依赖不会得以传递。
第二依赖范围是 compile
如果我们将 Maven_first 的Junit 改为 compile,那么将会符合上面总结的第一点:当第二依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致。
依赖的排除
如果我们在当前工程中引入了一个依赖是 A,而 A 又依赖了 B,那么 Maven 会自动将 A 依赖的 B 引入当前工程,但是个别情况下 B 有可能是一个不稳定版,或对当前工程有不良影响。这时我们可以在引入 A 的时候将 B 排除。
比如:我们在Maven_first 中添加 spring-core,maven 会自动将 commons-logging添加进来,那么由于 Maven_second 是依赖 Maven_first 的,那么 Maven_second 中将存在 spring-core(自带了commons-logging),这时候我们不想要 commons-logging,那该怎么办呢?我们上面第二大点提到了:
❝exclusions:用来排除传递性依赖
❞
Maven_first 的 pom.xml 文件
由于 Maven_second 依赖 Maven_second,故 Maven_second 存在 spring-core 包
如何排除呢?我们在 Maven_second 的 pom.xml 文件中添加如下代码:
再次查看工程:Maven_second 的 commons-logging 已经移除了
依赖的冲突
在 maven 中存在两种冲突方式:一种是跨 pom 文件的冲突,一致是同一个 pom 文件中的冲突。
「跨 pom 文件,路径最短者优先」。
比如我们在 Maven_first 中的 Junit 是4.9版本的,Maven_second 中的 Junit 是4.8版本的,那么Maven_third 中的 Junit 将会是那个版本呢?
由上图我们可以看出,由于 Maven_second 是 Maven_third 的直接依赖,明显相比于 Maven_first 路径要短,所以 Maven_third 的 Junit 版本与 Maven_second 保持一致
「同一个pom.xml 文件,先申明者优先」。
「看 Maven_second」
可选依赖
Optional 标签标示该依赖是否可选,默认是 false。可以理解为,如果为 true,则表示该依赖不会传递下去,如果为false,则会传递下去。
我们是在 Maven_second 的 pom 文件中设定 Junit 不可传递,那么Maven_third 工程中将不会有来自 Maven_second 的 Junit 的传递。
Maven 生命周期
什么是生命周期
Maven 强大的原因是有一个十分完善的生命周期,生命周期可以理解为项目构建步骤的集合,它定义了各个构建环节的执行顺序,有了这个顺序,Maven 就可以自动化的执行构建命令。
Maven 的核心程序中定义了抽象的生命周期,生命周期中各个阶段的具体任务是由插件来完成的。有三套相互独立的生命周期,各个构建环节执行顺序不能打乱,必须按照既定的正确顺序来执行。
- 「Clean Lifecycle : 在进行真正的构建之前进行一些清理工作」
- 「Default
以上是关于活久见!64 张图带你 Maven 实战通关的主要内容,如果未能解决你的问题,请参考以下文章
深入浅出!十三张图带你从源码了解SpringBoot启动流程!实战篇