Maven学习笔记
前言
近些日子,笔者在学习Java开发,不得不说,Java的生态圈做的有够大的。前几天Java核心学完了以后,本来以为已经学了挺多内容,可以喘口气,没想到Java core只能算是个入门。后续还有一系列的框架,如MyBatis、Spring、SpringMVC、SpringBoot、SpringCloud等等,数不胜数,磨刀霍霍在等着我。
而在学这些东西时,Maven则始终贯穿其中,虽然说Maven使用上还是挺简单的,但作为一个底层基础工具,本人觉得还是有必要稍微进行一些系统学习,这样才能更好的理解其具体运行过程。这篇博文则是本人对于Maven的学习记录。
Maven概述
Maven是什么,为什么有必要学习它:
Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。
在我们利用Java语言做开发时,秉承着不重复造轮子的原则(当然更多时候是没能力重复造轮子),必定会涉及到第三方类库的导入,而这些被导入的类,就成为了我们开发工程的“依赖”。显然,这些依赖文件在我们开发乃至部署时,都要先保存在我们写的类的可见范围内。
在简单的开发中,我们只需自行下载所需类库的jar文件,将其移动到指定目录即可。然而,随着时间的推移,开发逐渐转向了“面向框架”的模式,而每个框架又是前人基于一系列的依赖所构建起来的,这就导致了Java工程动辄都有成百上千的依赖,而这些依赖之间也存在着互相依赖的情况。

显然,人类是无法管理这样量级的依赖的,而Maven就是大佬们为了解决这个问题所开发出来的工具(相似的工具还有Gradle,未来有需要再学了)。通过Maven的依赖配置,我们只需指定我们想要的框架或工程,Maven会自动将其所需的依赖进行下载,编译时也无需我们过多关注,全部都可以交由Maven来自动管理。
除了作为依赖管理外,Maven还是一个构建工具。构建是指:在我们开发完成后,将源码打包为可以直接部署的文件。当我们在利用如IDEA这类的IDE开发时,IDE会接管构建过程,以方便我们调试和开发,然而,最终在开发完毕,脱离IDE时,构建过程则是需要利用Maven完成。
简言之,作为依赖管理+工程构建工具,对于学习Java开发来说,Maven不是一个可选项,而是一个必须掌握的基础技能。
Maven部署及基础配置
核心程序
下载
在这里可以下载到最新版本的Maven:
下载完毕后直接解压到任意非中文、没有空格的目录。
Maven的核心配置文件为:
conf/settings.xml
指定本地仓库
默认的Maven仓库在C盘,如果你的C盘空间不够大,那么可以将其修改为其他位置,如下:
1
2
3
4
5
6
7<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>D:\maven-repository</localRepository>当然,要把
标签取消注释。 配置基础 JDK 版本
Maven默认的JDK版本为1.5,而我们一般至少要使用1.8,修改方法为:将下面的 profile 标签整个复制到
settings.xml
文件的 profiles 标签内。1
2
3
4
5
6
7
8
9
10
11
12<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
环境变量配置
上一步下载的源码,要想在命令行中使用,则需要配置环境变量。
配置 MAVEN_HOME
新建一个系统变量,变量名为:
MAVEN_HOME
,变量值为解压的Maven根目录TIP
配置环境变量的规律:
XXX_HOME 通常指向的是 bin 目录的上一级
PATH 指向的是 bin 目录配置PATH
在PATH中,新建一个
%MAVEN_HOME%\bin
,将其指向Maven根目录中的bin文件夹
做完这些,在命令台中使用mvn -v
,即可看到Maven的版本信息输出。
Maven核心概念
坐标
Maven通过“坐标”的概念,来精准定位单个jar包文件。
Maven的坐标中有三个维度:
- groupId:公司或组织的 id
- artifactId:一个项目或者是项目中的一个模块的 id
- version:版本号
例如我们有这样一个坐标:
1 | <groupId>javax.servlet</groupId> |
其对应的jar包在本地仓库中的位置为:
1 | Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar |
pom.xml文件
Maven使用中,最核心的配置文件就是在工程根目录下的pom.xml
文件。
含义:
POM:Project Object Model,项目对象模型。和 POM 类似的是Web开发中的:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。
模型化思想:
POM表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。
笔者手中的Demo小项目用到的pom.xml长这样,也就是典型的一个配置文件的样子:
1 |
|
一般而言,我们最常配置的标签就是”dependencies”,只需在Maven官方仓库找到我们要添加的依赖,将其中的dependency标签复制过来即可。
约定的目录结构
现今的开发,有个规则叫“约定大于配置(convention over configuration)”,是一种软件设计范式。
Maven 对于目录结构,就采用这个模式:即没有采用配置的方式,而是基于约定。这样会让我们的开发更方便。
而Maven的约定工程目录如下:
1 | └───maven-project |
Maven 为了让构建过程能够尽可能自动化完成,约定了目录结构的作用。例如:Maven 执行编译操作,必须先去 Java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放在 target 目录。
Maven使用基础
构建/生命周期命令
清理操作
mvn clean
效果:删除 target 目录
编译操作
主程序编译:
mvn compile
测试程序编译:
mvn test-compile
主体程序编译结果存放的目录:target/classes
测试程序编译结果存放的目录:target/test-classes
测试操作
mvn test
测试的报告存放的目录:target/surefire-reports
打包操作
mvn package
打包的结果——jar 包或者war 包,存放的目录:target
安装操作
mvn install
安装的效果是将本地构建过程中生成的 jar 包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据它的坐标生成的。
另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始的 pom.xml 文件时,查看对应 XXX.pom 文件即可,它们是名字发生了改变,本质上是同一个文件。
除了这些生命周期命令外,还有构建工程用的archetype命令,这里不赘述了,用IDEA操作很简单。
依赖范围
标签的位置:dependencies/dependency/scope
标签的可选值:compile/test/provided/system/runtime/import
这里主要只关注:compile/test/provided
compile:通常实际运行时真正要用到的 jar 包都是以 compile 范围导入依赖。比如 SSM 框架所需jar包。
test:测试过程中使用的 jar 包,无需在正式环境中使用,就以 test 范围导入依赖。比如 junit。
provided:在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围导入依赖。比如 servlet-api、jsp-api。而这个范围的 jar 包不参与部署、不放进 war 包,以避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。
依赖传递性
概念
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?
如果可以,则体现了依赖的传递性。
传递原则
如果 B 依赖 C 时使用的是 compile 范围:可以传递
如果 B 依赖 C 时使用 test 或 provided 范围:不能传递,所以在 A 中需要 C 这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。
依赖排除
概念
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。
所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
配置方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<dependency>
<groupId>com.atguigu.maven</groupId>
<artifactId>pro01-maven-java</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
<!-- 使用excludes标签配置依赖的排除 -->
<exclusions>
<!-- 在exclude标签中配置一个具体的排除 -->
<exclusion>
<!-- 指定要排除的依赖的坐标(不需要写version) -->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
继承
概念
Maven工程之间,A 工程可以继承 B 工程
- B 工程:父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。
作用
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
对一个比较大型的项目进行了模块拆分。
一个 project 下面,创建了很多个 module。
每一个 module 都需要配置自己的依赖信息。
它背后的需求是:
在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
使用框架时所需要的 jar包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。
操作
① 创建父工程
工程名称:pro03-maven-parent
工程创建好之后,要修改它的打包方式:
1
2
3
4
5<groupId>com.atguigu.maven</groupId>
<artifactId>pro03-maven-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>② 创建模块工程
模块工程类似于 IDEA 中的 module,所以需要进入 pro03-maven-parent 工程的根目录,然后运行 mvn archetype:generate 命令来创建模块工程。
假设,我们创建三个模块工程。
③ 查看被添加新内容的父工程 pom.xml
下面 modules 和 module 标签是聚合功能的配置1
2
3
4
5<modules>
<module>pro04-maven-module</module>
<module>pro05-maven-module</module>
<module>pro06-maven-module</module>
</modules>④ 解读子工程的pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 使用parent标签指定当前工程的父工程 -->
<parent>
<!-- 父工程的坐标 -->
<groupId>com.atguigu.maven</groupId>
<artifactId>pro03-maven-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 子工程的坐标 -->
<!-- 如果子工程坐标中的groupId和version与父工程一致,那么可以省略 -->
<!-- <groupId>com.atguigu.maven</groupId> -->
<artifactId>pro04-maven-module</artifactId>
<!-- <version>1.0-SNAPSHOT</version> -->⑤ 在父工程中配置依赖的统一管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<!-- 使用dependencyManagement标签配置对依赖的管理 -->
<!-- 被管理的依赖并没有真正被引入到工程 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>⑥ 子工程中引用那些被父工程管理的依赖
关键点:省略版本号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 -->
<!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
<!-- 具体来说是由父工程的dependencyManagement来决定。 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
</dependencies>⑦ 在父工程中升级依赖信息的版本
1
2
3
4
5
6
7……
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
……⑧ 在父工程中声明自定义属性
1
2
3
4
5
6
7<!-- 通过自定义属性,统一指定Spring的版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 自定义标签,维护Spring版本数据 -->
<atguigu.spring.version>4.3.6.RELEASE</atguigu.spring.version>
</properties>在需要的地方使用${}的形式来引用自定义的属性名:
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${atguigu.spring.version}</version>
</dependency>
实际意义
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。
聚合
意义:
使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。
项目:整体
模块:部分TIP
概念的对应关系:
从继承关系角度来看:
- 父工程
- 子工程
从聚合关系角度来看:
- 总工程
- 模块工程
好处:
一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。
以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。
配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
聚合的配置:
在总工程中配置 modules 即可:
1
2
3
4
5<modules>
<module>pro04-maven-module</module>
<module>pro05-maven-module</module>
<module>pro06-maven-module</module>
</modules>
其他核心概念
生命周期
纯理论的内容,先不写了
插件和目标
Maven 的核心程序仅仅负责宏观调度,不做具体工作。具体工作都是由 Maven 插件完成的。例如:编译就是由 maven-compiler-plugin-3.1.jar 插件来执行的。
一个插件可以对应多个目标,而每一个目标都和生命周期中的某一个环节对应。
Default 生命周期中有 compile 和 test-compile 两个和编译相关的环节,这两个环节对应 compile 和 test-compile 两个目标,而这两个目标都是由 maven-compiler-plugin-3.1.jar 插件来执行的。
仓库
分为本地和远程仓库。