看完本篇文章后相信你对 Maven 的理解能更进一步
通常 Web 项目
开发只会创建一个工程,然后所有的 jar 包都会存放到 WEB-INF/lib
目录下,如下图所示:
上面的目录结构存在以下问题
一个项目就是一个 web 工程
。如果项目比较庞大,那么利用包名 package 来划分模块,显然容易造成混淆而且不利于分工合作;
项目中需要的 jar 包必须手动复制,粘贴到 WEB-INF/lib 目录下。这会导致每创建一个新的工程就需要将 jar 包重复复制到 lib 目录下,从而造成工作区存在大量重复的文件;
jar 需要我们手动去官网上或者其他途径下载;
一个 jar 包依赖的其他 jar 包,需要自己手动加入到项目中,而且很有可能我们漏掉了某个依赖关系,导致项目运行报错。
那么如何解决这些问题呢?我们的主角 Maven 应运而生了。
Maven 读音是 [ˈmevən],也就是霉文
,而不是读马文
。它是一个项目管理和综合工具,Maven 使用标准的目录结构和默认构建生命周期。提供了开发人员构建一个完整的生命周期框架,开发团队可以自动完成该项目的基础设施建设。相信如果对 Maven 没有任何了解的,看了这段话等于没看,不过没关系,后面我们将会逐渐揭开 Maven 的神秘面纱。什么是 Maven,你只需要知道这玩意能简化和标准化项目建设过程。
Maven的 最初设计,以简化Jakarta Turbine 项目的建设进程。有几个项目,每个项目包含了稍微不同的 Ant 构建文件。JAR 中检查到 CVS。Apache 组织开发的 Maven 可以建立多个项目,发布项目信息,项目部署。
Maven主要目标是提供开发人员
项目是可重复使用,易维护,更容易理解的一个综合模型。
插件或交互的工具,这种声明性的模式。
❝Maven 项目的结构和内容是在一个 XML 文件中声明,pom.xml 的项目对象模型(POM),这是整个 Maven 系统的基本单元。
❞
Maven 的理念是 「约定优于配置!!!」
开发人员不需要创建构建过程本身,不必知道提到的每一个配置的详细信息。Maven 提供了合理的默认行为的项目。创建一个 Maven 项目时,Maven 创建默认的项目结构。开发者只需要把相应的文件和她需要在 pom.xml 中定义即可。
官网下载地址:http://maven.apache.org/download.cgi
将下载的 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 插件的位置 : Window->Preferences->Maven->Installations
指定 conf/settings.xml 的位置,进而指定 Maven 本地仓库的位置
修改 settings.xml 如下标签的位置即可:自定义路径
上面我们配置并安装好了 Maven,那么现在我们就来介绍一下如何用 eclipse 创建一个 Maven 工程,然后介绍 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 工程:
对每个目录结构的解析如下:
为什么 maven 工程的目录结构要这样呢?
Maven 要负责项目的自动化构建,以编译为例,Maven 要想自动进行编译,那么它必须知道 Java 的源文件保存在哪里,这样约定之后,不用我们手动指定位置,Maven 能知道位置,从而帮我们完成自动编译。
遵循 约定>>>配置>>>编码。即能进行配置的不要去编码指定,能事先约定规则的不要去进行配置。这样既减轻了劳动力,也能防止出错。
「pom.xml 文件」
Project Object Model 项目对象模型,Maven 的核心配置文件,pom.xml,与构建过程相关的一切设置都在这个文件中进行配置。
❝这个文件非常重要,我们后面会详细讲解。
❞
在 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 文件如下:
4.0.0
com.ys
hellomaven
0.0.1-SNAPSHOT
junit
junit
4.0
test
为什么要这样写,我们后面会详细讲解。
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
如果测试类 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 pakage 打包,动态 web工程打 war包,Java工程打 jar 包。
第一步:选择 pom.xml 文件,右键--->Run As ---->2 Maven build...,然后在弹出框中输入 package
mvn install 将项目生成 jar 包放在仓库中,以便别的模块调用
我们知道 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
什么是依赖
?相信有过一定开发经验的人知道,每当我们需要使用某个框架时,比如 SpringMVC,那么我们需要导入相应的 jar 包,但是手动导入包的时候,往往会漏掉几个 jar 包,那么在使用该框架的时候系统就会报错。那么我们就说导入的包与未导入的包存在依赖关系。而使用 Maven,我们只需要在 pom.xml 文件中进行相应的配置,它就会帮助我们自动管理 jar 包之间的依赖关系。那么它是怎么管理的呢,接下来我们会详细讲解。
我们以 Junit 为例,在 pom.xml 文件中进行详细而完整的配置。
junit
junit
3.8.1
...
...
...
...
...
dependencies:一个 pom.xml 文件中只能存在一个这样的标签。用来管理依赖的总标签。
dependency: 包含在 dependencies 标签中,可以有无数个,每一个表示一个依赖
groupId、artifactId 和 version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根 据坐标才能找到需要的依赖。
type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。
scope:依赖的范围,默认值是 compile。后面会进行详解。
optional:标记依赖是否可选。
exclusions:用来排除传递性依赖,后面会进行详细介绍。
先放一张图
一般情况下,我们对前面三个依赖用的比较多。下面的主程序表示 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
比如我们创建三个 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 的核心程序中定义了抽象的生命周期,生命周期中各个阶段的具体任务是由插件来完成的。有三套相互独立的生命周期,各个构建环节执行顺序不能打乱,必须按照既定的正确顺序来执行。
「Clean Lifecycle : 在进行真正的构建之前进行一些清理工作」
「Default Lifecycle:构建的核心部分,编译、测试、打包、安装、部署等等」。
「Site Lifecycle:生成项目报告,站点,发布站点」。
这三个都是相互独立的。你可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。当然,也可以直接运行 mvn claen install site 运行所有这三套生命周期。下面我们分别来谈谈这三个生命周期。
pre-clean 执行一些需要在clean之前完成的工作
clean 移除所有上一次构建生成的文件
post-clean 执行一些需要在clean之后立刻完成的工作
我们前面讲的执行命令 mvn -clean,也就等同于 Clean 生命周期中的第一个阶段 mvn pre-clean clean。注意有 Clean 声明周期,而这个声明周期中又有 clean 阶段。
只要执行后面的命令,那么前面的命令都会执行,不需要再重新去输入命令
validate
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 接受编译好的代码,打包成可发布的格式,如 JAR 。
pre-integration-test
integration-test
post-integration-test
verify
install 将包安装至本地仓库,以让其它项目依赖。
deploy 将最终的包复制到远程的仓库,以让其它开发人员与项目共享。
这里我们强调一下:「在maven中,只要在同一个生命周期,你执行后面的阶段,那么前面的阶段也会被执行,而且不需要额外去输入前面的阶段」。
我们举个例子:执行 mven compile 命令,根据上面的声明周期,它会默认执行前面五个个步骤也就是
validate
generate-sources
process-sources
generate-resources
process-resources 复制并处理资源文件,至目标目录,准备打包。
compile 编译项目的源代码。
我们在 eclipse 中执行 mvn compile 命令
看到红色框的两部分,第一个 maven-compiler-plugin:2.6:resource 就是用来执行前面几个步骤的插件,第二个插件 maven-compiler-plugin:3.1:compile 则是用来执行 mvn compile 的插件。这里我们提一下,mvn 的各个生命周期步骤都是依赖插件来完成的,后面我们会详细讲解 maven 插件。
pre-site 执行一些需要在生成站点文档之前完成的工作
site 生成项目的站点文档
post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
site-deploy 将生成的站点文档部署到特定的服务器上
这里经常用到的是 site 阶段和 site-deploy 阶段,用来生成和发布 maven 站点,这是 Maven 比较强大的功能,文档及统计数据自动生成。由于现在的系统会有专门工具来生成文档或报表。所以这个功能也是比较鸡肋吧,不够简洁和美观,用的不太多。
上面我们讲了 Maven 的生命周期,我们知道 Maven 的核心是生命周期,生命周期指定了 Maven 命令执行的流程顺序。但是真正实现流程的工程是由插件来完成的。
我们也可以说 Maven 是一个执行插件的框架,每一个任务实际上都是有插件来完成。进一步说每个任务对应了一个插件目标(goal)
,每个插件会有一个或者多个目标,例如 maven-compiler-plugin 的 compile 目标用来编译位于 src/main/java/ 目录下的主源码,testCompile 目标用来编译位于src/test/java/目录下的测试源码。
一般我们创建一个 Maven 工程,就算指定了 JDK 的版本,但是你执行 update project 操作,一般 Maven 工程会自动恢复到默认的 JDK 版本,有可能是1.4,有可能是1.5(和 Maven 版本有关)。
那么我们如何指定其 JDK 版本呢?在 pom.xml 中添加如下代码:
org.apache.maven.plugins
maven-compiler-plugin
1.7
1.7
UTF-8
下面我们来添加一个 tomcat 插件,首先我们要知道如何创建 Maven Web 工程。
第一步:New maven project,注意打包方式为 war
第二步:右击项目名,选择 properties,选择Project Facets
第三步:将 Dynamic Web Module 取消,点击 Apply
第四部:将 Dynamic Web Module 重新勾选,点击 下方生成的超链接
第五步:点击超链接,修改目录结构,然后点击 OK,创建 Maven Web 工程完成
创建的 Web 工程目录结构如下:
我们在上面创建的 web 工程,可以输入 tomcat:run 来使用默认的 tomcat 插件去启动 web 工程,但是默认的插件版本有点低,我们可以手动添加插件。
org.apache.tomcat.maven
tomcat7-maven-plugin
8080
/
执行命令是输入:tomcat7:run
有三个 Maven 工程,每个工程都依赖某个 jar 包,比如 Junit,由于 test 范围的依赖不能传递,它必然会分散在每个工程中,而且每个工程的jar 包版本可能不一致。那么如何管理各个工程中对于某个 jar 包的版本呢?
「解决办法:」
将那个 jar 包版本统一提取到 父
工程中,在子工程中声明依赖时不指定版本,以父工程中统一设定的为准,同时也便于修改。
操作步骤:
创建父工程
在子工程中声明对父工程的引用
com.ys.maven
Parent
0.0.1-SNAPSHOT
../Parent/pom.xml
将子工程的坐标中与父工程坐标重复的内容删除(不删除也可以,为了简洁)
junit
junit
4.8
test
dependencyManagement标签管理的依赖,其实没有真正依赖,它只是管理依赖的版本。
在子工程中删除 Junit 的版本号
父工程通过 properties 统一管理版本号
我们可以通过
自定义标签,然后在标签里面填写常量,这种方法不仅可以用来管理版本号,还可以用来管理比如设置某种编码等等。
需求场景:
在真实项目中,一个项目有表现层、业务层、持久层等。我们在用Maven 管理项目的时候,通常为创建多个 Maven 工程,也就是一个项目的多个模块。但是这样分成多个模块了,当我们进行项目打包发布的时候,那么要每一个模块都执行打包操作吗?这种重复的操作我们怎么才能避免呢?
解决办法:
创建一个聚合工程,将其他的各个模块都由这个聚合工程来管理,那么我们在进行项目发布的时候,只需要打包这个聚合工程就可以了。
第一步:创建聚合工程(注意聚合工程的打包方式也必须为 pom,通常由 上面所讲的父工程来充当聚合工程)
第二步:创建子工程:业务层
选择 Maven Module
填写子工程模块名,打包方式选择 jar(子工程除了 web 层我们打包方式选择 war ,其余的都选择 jar)
第三步:创建子工程:表现层和持久层
创建步骤和前面一样,注意表现层打包方式我们要选择 war,因为要发布到 tomcat 容器运行。
第四步:在聚合工程中添加子工程的引用
Service
Controller
Mapper
❝注意:这里虽然各个模块有依赖关系,但是可以不让依赖顺序添加,maven会自动识别依赖关系进行编译打包。
这里总的聚合工程随便哪个工程都可以,但是通常用 Parent 工程来完成。
❞
文章经由作者原创授权 https://www.cnblogs.com/ysocean/
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼
如有收获,点个在看,诚挚感谢