Maven 是目前最流行的自动化构建工具,对于生产环境下多框架、多模块整合开发有重要作用。Maven 是一款在大型项目开发过程中不可或缺的重要工具。(自己粗略写了一个SSM项目之后,回顾头来看这个,会发现更有感觉)
Maven——Maven介绍——史上最烂系列
文章目录
1:Maven核心概念介绍
Maven 能够实现自动化构建是和它的内部原理分不开的,这里我们从 Maven 的九个核心概念入手, 看看 Maven 是如何实现自动化构建的
- POM
- 约定的目录结构
- 坐标
- 依赖管理
- 仓库管理
- 生命周期
- 插件和目标
- 继承
- 聚合
Maven 的核心程序中仅仅定义了抽象的生命周期,而具体的操作则是由 Maven 的插件来完成的。可是 Maven 的插件并不包含在 Maven 的核心程序中,在首次使用时需要联网下载。
小知识:Maven为什么要配置镜像仓库
- 下载得到的插件会被保存到本地仓库中。本地仓库默认的位置是:~.m2\repository。
- 如果不能联网可以使用我们提供的 RepMaven.zip 解压得到。如下
几个常用maven命令
mvn compile 编译
mvn clean 清理
mvn test-compile 测试
mvn package 打包
注意:运行Maven命令时一定要进入pom.xml文件所在的目录!
举例:在如下路径下,创建了Hello的Maven工程,运行maven命令,
目录结构如下(写上测试类进行测试)
Hello
|—src
|—|---main
|—|---|—java
|—|---|—resources
|—|---test
|—|---|—java
|—|---|—resources
|—pom.xml
执行mvn compile命令
执行mvn test-compile命令
执行mvn package命令
执行mvn clean命令
1.1 约定的目录结构
约定的目录结构对于 Maven 实现自动化构建而言是必不可少的一环,就拿自动编译来说,Maven 必须 能找到 Java 源文件,下一步才能编译,而编译之后也必须有一个准确的位置保持编译得到的字节码文件。
我们在开发中如果需要让第三方工具或框架知道我们自己创建的资源在哪,那么基本上就是两种方式:
- 通过配置的形式明确告诉它
- 基于第三方工具或框架的约定
Maven 对工程目录结构的要求就属于基于第三方工具或框架的约定
现在 JavaEE 开发领域普遍认同一个观点:约定>配置>编码。意思就是能用配置解决的问题就不编码, 能基于约定的就不进行配置。而 Maven 正是因为指定了特定文件保存的目录才能够对我们的 Java 工程进行 自动化构建。
1.2 POM
Project Object Model:项目对象模型。将 Java 工程的相关信息封装为对象作为便于操作和管理的模型。 Maven 工程的核心配置。可以说学习 Maven 就是学习 pom.xml 文件中的配置。(相当于web.xml对于动态web工程)
1.3 坐标
-
数学中的坐标
[1]在一个平面中使用 x、y 两个向量可以唯一的确定平面中的一个点。
[2]在空间中使用 x、y、z 三个向量可以唯一的确定空间中的一个点。 -
Maven坐标
使用如下三个向量在 Maven 的仓库中唯一的确定一个 Maven 工程。
[1]groupid:公司或组织的域名倒序+当前项目名称
[2]artifactId:当前项目的模块名称
[3]version:当前模块的版本
#大到小
<groupId>com.atguigu.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
- 如何通过坐标到仓库中查找 jar 包?
[1]将 gav 三个向量连起来
com.atguigu.maven+Hello+0.0.1-SNAPSHOT
[2]以连起来的字符串作为目录结构到仓库中查找
com/atguigu/maven/Hello/0.0.1-SNAPSHOT/Hello-0.0.1-SNAPSHOT.jar
1.4 仓库
- 仓库分类
[1]本地仓库:为当前本机电脑上的所有 Maven 工程服务。
(默认: C:\Users\86180.m2\repository\)
[2]远程仓库
(1)私服:架设在当前局域网环境下,为当前局域网范围内的所有 Maven 工程服务。
(2)中央仓库:架设在 Internet 上,为全世界所有 Maven 工程服务。
(3)中央仓库的镜像:架设在各个大洲,为中央仓库分担流量。减轻中央仓库的压力,同时更快的响应用户请求。 - 仓库中的文件
[1]Maven 的插件
[2]我们自己开发的项目的模块
[3]第三方框架或工具的 jar 包 (第一方:JDK)
不管是什么样的 jar 包,在仓库中都是按照坐标生成目录结构,所以可以通过统一的方式查询或依赖。
1.5 依赖
Maven 中最关键的部分,我们使用 Maven 最主要的就是使用它的依赖管理功能。要理解和掌握 Maven 的依赖管理,我们只需要解决一下几个问题(以子目录形式出现):
1.5.1 依赖的目的是什么 ?
当 A jar 包用到了 B jar 包中的某些类时,A 就对 B 产生了依赖,这是概念上的描述。那么如何在项目 中以依赖的方式引入一个我们需要的 jar 包呢? 答案非常简单,就是使用 dependency 标签指定被依赖 jar 包的坐标就可以了。
对我们自己开发的Maven工程(比如上述的Hello工程),使用mvn install命令安装后就会进入仓库
比如这是我创建了一个HelloFriend的Maven工程,pom.xml中依赖了Hello工程
<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.atstudying.maven</groupId>
<artifactId>HelloFriend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HelloFriend</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atstudying.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
这时在HelloFriend的工程中使用mvn compile命令会报无法找到Hello工程的错误,我们要在Hello工程中使用mvn install命令,把该工程放入到(本地仓库)默认仓库,Maven解析依赖信息时会到本地仓库查找依赖的jar包,这时在调用mvn compile命令就会成功,如下图
1.5.2 依赖的范围
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atstudying.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
大家注意到上面的依赖信息中除了目标 jar 包的坐标还有一个 scope 设置,这是依赖的范围。依赖的范 围有几个可选值,我们用得到的是:compile、test、provided 三个。
- 从项目结构角度理解 compile 和 test 的区别
结合具体例子:对于 HelloFriend 来说,Hello 就是服务于主程序的,junit 是服务于测试程序的。 HelloFriend 主程序需要 Hello 是非常明显的,测试程序由于要调用主程序所以也需要 Hello,所以 compile 范围依赖对主程序和测试程序都应该有效。 HelloFriend 的测试程序部分需要 junit 也是非常明显的,而主程序是不需要的,所以 test 范围依赖 仅仅对于主程序( HelloFriend)有效。 - 从开发和运行这两个不同阶段理解 compile 和 provided 的区别
总结
compile | test | provided | |
---|---|---|---|
主程序 | 可以 | 不可以 | 可以 |
测试程序 | 可以 | 可以 | 可以 |
参与部署 | 可以 | 不可以 | 不可以 |
1.5.3 依赖的传递性
- A 依赖 B,B 依赖 C,A 能否使用 C 呢?那要看 B 依赖 C 的范围是不是 compile,如果是则可用,否则不 可用。
- 依赖的传递性好处:可以传递的依赖不必再每个模块工程中都重复声明,最‘下面的‘’工程(父工程)中依赖一次即可(非compile范围的依赖不可以传递,所以如果有需要就得重复声明依赖)
1.5.4 依赖的排除
- 如果我们在当前工程中引入了一个依赖是 A,而 A 又依赖了 B,那么 Maven 会自动将 A 依赖的 B 引入当 前工程,但是个别情况下 B 有可能是一个不稳定版,或对当前工程有不良影响。这时我们可以在引入 A 的时 候将 B 排除。
未排除前
<dependency>
<groupId>com.atguigu.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
<!--排除了Hello工程传递过来的commons-logging依赖-->
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
排除后
1.5.5 统一管理所依赖 jar 包的版本
对同一个框架的一组 jar 包最好使用相同的版本。为了方便升级框架,可以将 jar 包的版本信息统一提 取出来
步骤如下
统一声明版本号
<!---其中 atstudying.spring.version 部分是自定义标签。 ->
<properties>
<atstudying.spring.version>4.1.1.RELEASE</atguigu.spring.version>
</properties>
引用前面声明的版本号
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${atguigu.spring.version}</version>
</dependency>
……
</dependencies>
1.5.6 依赖的原则:解决 jar 包冲突
- 路径最短者优先
- 路径相同时先声明者优先
1.6 生命周期
什么是 Maven 的生命周期?
-
Maven 生命周期定义了各个构建环节的执行顺序,有 了这个清单,Maven 就可以自动化的执行构建命 令了。
-
Maven 有三套相互独立的生命周期,分别是:
1:Clean Lifecycle 在进行真正的构建之前进行一些清理工作。
2:Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等。
3:Site Lifecycle 生成项目报告,站点,发布站点。
它们是相互独立的,你可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。当然你也可以 直接运行 mvn clean/install/site 运行所有这三套生命周期。
-
每套生命周期都由一组阶段(Phase)组成,我们平时在命令行输入的命令总会对应于一个特定的阶段。比 如,运行 mvn clean,这个 clean 是 Clean 生命周期的一个阶段。有 Clean 生命周期,也有 clean 阶段。 如下
Clean 生命周期:
Clean 生命周期一共包含了三个阶段:
1:pre-clean 执行一些需要在 clean 之前完成的工作
2:clean 移除所有上一次构建生成的文件
3:post-clean 执行一些需要在 clean 之后立刻完成的工作
Site 生命周期
Site 生命周期一共包含了四个阶段:
1:pre-site 执行一些需要在生成站点文档之前完成的工作
2:site 生成项目的站点文档
3:post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
4:site-deploy 将生成的站点文档部署到特定的服务器上这里经常用到的是 site 阶段和 site-deploy 阶段,用以生成和发布 Maven 站点,这可是 Maven 相当强大 的功能,Manager 比较喜欢,文档及统计数据自动生成,很好看。
Default 生命周期
Default 生命周期是 Maven 生命周期中最重要的一个,绝大部分工作都发生在这个生命周期中。这里, 只解释一些比较重要和常用的阶段,如下:
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 将最终的包复制到远程的仓库,以让其它开发人员与项目共享或部署到服务器上运行。
- 生命周期与自动化构建 运行任何一个阶段的时候,它前面的所有阶段都会被运行
例如我们运行 mvn install 的时候 ,代码会 被编译,测试,打包。这就是 Maven 为什么能够自动执行构建过程的各个环节的原因。此外,Maven 的插 件机制是完全依赖 Maven 的生命周期的,因此理解生命周期至关重要。 (从上面就可以理解了)
1.7 插件和目标
- Maven 的核心仅仅定义了抽象的生命周期,具体的任务都是交由插件完成的。
- 每个插件都能实现多个功能,每个功能就是一个插件目标。
- Maven 的生命周期与插件目标相互绑定,以完成某个具体的构建任务。
例如:compile 就是插件 maven-compiler-plugin 的一个目标;
pre-clean 是插件 maven-clean-plugin 的一个目标。
1.8 继承
- 为什么需要继承机制?
由于非 compile 范围的依赖信息是不能在“依赖链”中传递的,所以有需要的工程只能单独配置,例如我们上面讲的例子
Hello | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <scope>test</scope> </dependency> |
---|---|
HelloFriend | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version><scope>test</scope> </dependency> |
MakeFriend | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <scope>test</scope> </dependency> |
此时如果项目需要将各个模块的junit版本统一为4.9,那么到各个工程中手动修改无疑是非常不可取的。 使用继承机制就可以将这样的依赖信息统一提取到父工程模块中进行统一管理。
步骤如下:
1: 创建父工程,在父工程中管理依赖
<!-- 配置依赖的管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
2:在子工程中引用父工程
<!-- 子工程中声明父工程 -->
<parent>
<groupId>com.atguigu.maven</groupId>
<artifactId>Parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 以当前文件为基准的父工程pom.xml文件的相对路径 -->
<relativePath>../Parent/pom.xml</relativePath>
</parent>
3:在子项目中重新指定需要的依赖,删除范围和版本号
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.9 聚合
- 为什么要使用聚合?
将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进 行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。
- 如何配置聚合?
在总的聚合工程中使用 modules/module 标签组合,指定模块工程的相对路径即可
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
2:在线maven仓库
我们可以到 maven仓库 搜索需要的 jar 包的依赖信息。