Maven入门
为什么学Maven
Maven 作为依赖管理工具
随着我们使用越来越多的框架,或者框架封装程度越来越高,项目中使用的jar包也越来越多。项目中,一个模块里面用到上百个jar包是非常正常的。比如下面的例子,我们只用到 SpringBoot、SpringCloud 框架中的三个功能:
- Nacos 服务注册发现
- Web 框架环境
- 图模板技术 Thymeleaf
最终却导入了 106 个 jar 包:
而如果使用 Maven 来引入这些 jar 包只需要配置三个『依赖』:
1 | <!-- Nacos 服务注册发现启动器 --> |
jar 包的来源
- 这个jar包所属技术的官网。官网通常是英文界面,网站的结构又不尽相同,甚至找到下载链接还发现需要通过特殊的工具下载。
- 第三方网站提供下载。问题是不规范,在使用过程中会出现各种问题。
- jar包的名称
- jar包的版本
- jar包内的具体细节
- 而使用 Maven 后,依赖对应的 jar 包能够自动下载,方便、快捷又规范。
jar 包之间的依赖关系
框架中使用的 jar 包,不仅数量庞大,而且彼此之间存在错综复杂的依赖关系。依赖关系的复杂程度,已经上升到了完全不能靠人力手动解决的程度。另外,jar 包之间有可能产生冲突。进一步增加了我们在 jar 包使用过程中的难度。
下面是前面例子中 jar 包之间的依赖关系
而实际上 jar 包之间的依赖关系是普遍存在的,如果要由程序员手动梳理无疑会增加极高的学习成本,而这些工作又对实现业务功能毫无帮助。
而使用 Maven 则几乎不需要管理这些关系,极个别的地方调整一下即可,极大的减轻了我们的工作量。
Maven 作为构建管理工具
你没有注意过的构建
你可以不使用 Maven,但是构建必须要做。当我们使用 IDEA 进行开发时,构建是 IDEA 替我们做的。
脱离 IDE 环境仍需构建
结论
- 管理规模庞大的 jar 包,需要专门工具。
- 脱离 IDE 环境执行构建操作,需要专门工具。
什么是 Maven?
Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。
构建
Java 项目开发过程中,构建指的是使用『原材料生产产品』的过程。
原材料
Java 源代码
基于 HTML 的 Thymeleaf 文件
图片
配置文件
#……
产品
- 一个可以在服务器上运行的项目
构建过程包含的主要的环节:
- 清理:删除上一次构建的结果,为下一次构建做好准备
- 编译:Java 源程序编译成 *.class 字节码文件
- 测试:运行提前准备好的测试程序
- 报告:针对刚才测试的结果生成一个全面的信息
- 打包
- Java工程:jar包
- Web工程:war包
- 安装:把一个 Maven 工程经过打包操作生成的 jar 包或 war 包存入 Maven 仓库
- 部署
- 部署 jar 包:把一个 jar 包部署到 Nexus 私服服务器上
- 部署 war 包:借助相关 Maven 插件(例如 cargo),将 war 包部署到 Tomcat 服务器上
依赖
如果 A 工程里面用到了 B 工程的类、接口、配置文件等等这样的资源,那么我们就可以说 A 依赖 B。例如:
- junit-4.12 依赖 hamcrest-core-1.3
- thymeleaf-3.0.12.RELEASE 依赖 ognl-3.1.26
- ognl-3.1.26 依赖 javassist-3.20.0-GA
- thymeleaf-3.0.12.RELEASE 依赖 attoparser-2.0.5.RELEASE
- thymeleaf-3.0.12.RELEASE 依赖 unbescape-1.1.6.RELEASE
- thymeleaf-3.0.12.RELEASE 依赖 slf4j-api-1.7.26
依赖管理中要解决的具体问题:
- jar 包的下载:使用 Maven 之后,jar 包会从规范的远程仓库下载到本地
- jar 包之间的依赖:通过依赖的传递性自动完成
- jar 包之间的冲突:通过对依赖的配置进行调整,让某些jar包不会被导入
Maven 的工作机制
Maven下载和配置
下载
官网地址:
下载地址,对应3.8.5版本
https://dlcdn.apache.org/maven/maven-3/3.8.5/binaries/apache-maven-3.8.5-bin.zip
解压
下载后需要将文件解压到没有中文的目录下
其中核心配置文件是:conf/settingsxml
配置本地仓库目录
打开核心配置文件 conf/settingsxml,添加本地仓库地址
1 | <localRepository>D:\Install\rundir\Maven\maven-reposit</localRepository> |
配置阿里云镜像地址
在配置文件 conf/settingsxml 中作出如下修改,添加一个 mirror 配置
1 | <mirror> |
配置jdk版本
在配置文件 conf/settingsxml 中添加一个 profile 标签
1 | <profile> |
配置maven环境变量
首先配置之前要保证 java 的环境变量正常运行
配置环境变量的规律:
XXX_HOME 通常指向的是 bin 目录的上一级
PATH 指向的是 bin 目录
验证一下,在 cmd 窗口输入 mvn -v 查看版本
出现上图表示配置成功
Maven 坐标概念
在数据中可以根据坐标确定一个点,在 maven 中可以根据坐标确定一个具体的 jar 包
向量
使用三个『向量』在『Maven的仓库』中唯一的定位到一个『jar』包。
- groupId:公司或组织的 id
- artifactId:一个项目或者是项目中的一个模块的 id
- version:版本号
三个向量的取值方式
- groupId:公司或组织域名的倒序,通常也会加上项目名称
- 例如:com.atguigu.maven
- artifactId:模块的名称,将来作为 Maven 工程的工程名
- version:模块的版本号,根据自己的需要设定
- 例如:SNAPSHOT 表示快照版本,正在迭代过程中,不稳定的版本
- 例如:RELEASE 表示正式版本
举例:
- groupId:com.atguigu.maven
- artifactId:pro01-atguigu-maven
- version:1.0-SNAPSHOT
坐标和仓库中 jar 包的存储路径之间的对应关系
坐标:
1 | <groupId>javax.servlet</groupId> |
上面坐标对应的 jar 包在 Maven 本地仓库中的位置:
1 | Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar |
创建 Maven 版的 Java工程
在需要创建项目的目录上运行
运行创建命令
1 | mvn archetype:generate |
运行中输入项目名称
1 | Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7:【直接回车,使用默认值】 |
创建成功提示
自动生成的 pom.xml 解读
1 | <!-- 当前Maven工程的坐标 --> |
POM
含义
POM:Project Object Model,项目对象模型。和 POM 类似的是:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。
模型化思想
POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。
对应的配置文件
POM 理念集中体现在 Maven 工程根目录下 pom.xml 这个配置文件中。所以这个 pom.xml 配置文件就是 Maven 工程的核心配置文件。其实学习 Maven 就是学这个文件怎么配置,各个配置有什么用
约定的目录结构
各个目录的作用
另外还有一个 target 目录专门存放构建操作输出的结果。
约定目录结构的意义
Maven 为了让构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如:Maven 执行编译操作,必须先去 Java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放在 target 目录。
约定大于配置
Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的位置进行详细的配置,那肯定非常麻烦。
目前开发领域的技术发展趋势就是:约定大于配置,配置大于编码
Maven 的构建命令
mvn -v 命令和构建操作无关,只要正确配置了 PATH,在任何目录下执行都可以。
而构建相关的命令要在 pom.xml 所在目录下运行——操作哪个工程,就进入这个工程的 pom.xml 目录。
清理操作
mvn clean
效果:删除 target 目录
编译操作
主程序编译:mvn compile
测试程序编译:mvn test-compile
主体程序编译结果存放的目录:target/classes
测试程序编译结果存放的目录:target/test-classes
测试操作
mvn test
测试的报告存放的目录:target/surefire-reports
查看引用的依赖
1 | mvn dependency:list 普通结构查看 |
打包操作
mvn package
打包的结果——jar 包,存放的目录:target
安装操作
mvn install
1 | [INFO] Installing D:\maven-workspace\space201026\pro01-maven-java\target\pro01-maven-java-1.0-SNAPSHOT.jar to D:\maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.jar |
安装的效果是将本地构建过程中生成的 jar 包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据它的坐标生成的。
坐标信息如下:
1 | <groupId>com.atguigu.maven</groupId> |
在 Maven 仓库中生成的路径如下:
1 | D:\maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.jar |
另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始的 pom.xml 文件时,查看对应 XXX.pom 文件即可,它们是名字发生了改变,本质上是同一个文件。
创建 Maven 版的 Web 工程
不能在一个非 pom 的工程下再创建其他工程,所以在创建 web 工程的时候不能继续在刚刚创建 com.szx.maven 文件下继续创建,而是应该在他的平级目录下创建 web 工程
输入命令
1 | mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4 |
Web工程的目录结构
webapp 目录下有 index.jsp
WEB-INF 目录下有 web.xm
创建 Servlet
- 在 main 目录下创建 java 目录
- 在 java 目录下创建 Servlet 类所在的包的目录
- 在包下创建 Servlet 类
1 | package com.atguigu.maven; |
- 在 web.xml 中注册 Servlet
1 | <servlet> |
- 在 index.jsp 页面编写超链接
1 | <html> |
JSP全称是 Java Server Page,和 Thymeleaf 一样,是服务器端页面渲染技术。这里我们不必关心 JSP 语法细节,编写一个超链接标签即可。
编译
此时直接执行 mvn compile 命令出错:
1 | 程序包 javax.servlet.http 不存在 |
上面的错误信息说明:我们的 Web 工程用到了 HttpServlet 这个类,而 HttpServlet 这个类属于 servlet-api.jar 这个 jar 包。此时我们说,Web 工程需要依赖 servlet-api.jar 包。
配置对 servlet-api.jar 包的依赖
对于不知道详细信息的依赖可以到https://mvnrepository.com/网站查询。使用关键词搜索,然后在搜索结果列表中选择适合的使用。
比如,我们找到的 servlet-api 的依赖信息:
1 | <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> |
这样就可以把上面的信息加入 pom.xml。重新执行 mvn compile 命令
打包生成war包
输入 mvn package
war 包存放地址
部署war包到 Tomcat
首先将 war 的解压包复制到 tomcat 的 webapps 文件中
重命名粘贴到 tomcat 的 webapps 文件中
然后打开 tomcat 的 bin 目录,双击运行 startup.bat 文件
这里如果出现闪退的情况,需要配置一下 JRE_HOME 环境变量
服务运行中
打开浏览器查看页面
让 Web 工程依赖 Java 工程
在 pro02-maven-web 工程的 pom.xml 中,找到 dependencies 标签,在 dependencies 标签中做如下配置:
1 | <!-- 配置对Java工程pro01-maven-java的依赖 --> |
在 Web 工程中,编写测试代码
补充测试目录 pro02-maven-web\src\test\java\com\szx\maven
把 Java 工程的 AppTest.java 类复制到 pro02-maven-wb\src\test\java\com\szx\maven 目录下
执行maven命令
再此基础前要确保 java 工程已经执行过 mvn package 操作和 mvn install 操作。这是保证 java 工程的 jar 包在本地项目中
测试命令
1 | mvn test |
运行效果
打包命令
1 | mvn package |
运行效果
通过查看 war 包内的结构,我们看到被 Web 工程依赖的 Java 工程确实是会变成 Web 工程的 WEB-INF/lib 目录下的 jar 包。
查看当前 Web 工程所依赖的 jar 包的列表
1 | mvn dependency:list 普通结构查看 |
运行效果
依赖的范围
标签的位置:dependencies/dependency/scope
标签的可选值:compile/test/provided/system/runtime/import
compile 和 test 对比
main目录(空间) | test目录(空间) | 开发过程(时间) | 部署到服务器(时间) | |
---|---|---|---|---|
compile | 有效 | 有效 | 有效 | 有效 |
test | 无效 | 有效 | 有效 | 无效 |
compile 和 provided 对比
main目录(空间) | test目录(空间) | 开发过程(时间) | 部署到服务器(时间) | |
---|---|---|---|---|
compile | 有效 | 有效 | 有效 | 有效 |
provided | 有效 | 有效 | 有效 | 无效 |
结论
compile:通常使用的第三方框架的 jar 包这样在项目实际运行时真正要用到的 jar 包都是以 compile 范围进行依赖的。比如 SSM 框架所需jar包。
test:测试过程中使用的 jar 包,以 test 范围依赖进来。比如 junit。
provided:在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围依赖进来。比如 servlet-api、jsp-api。而这个范围的 jar 包之所以不参与部署、不放进 war 包,就是避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。说白了就是:服务器上已经有了,你就别带啦!
依赖的传递性
首先在pro01项目中引入
1 | <dependency> |
然后编译 mvn compile,然后查看依赖如下
然后将pro01包install安装到本地仓库 mvn install
之后在 pro02 项目中引入 pro01,查看 pro02 的项目依赖
scope 为 test 的依赖的不能被传递
依赖的排除
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。
所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
配置方式
1 | <dependency> |
继承
概念
Maven工程之间,A 工程继承 B 工程
- B 工程:父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。
作用
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
- 对一个比较大型的项目进行了模块拆分。
- 一个 project 下面,创建了很多个 module。
- 每一个 module 都需要配置自己的依赖信息。
它背后的需求是:
- 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
- 使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
- 使用框架时所需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力
操作
首先创建一个父工程:pro03-maven-java
输入创建命令
1 | mvn archetype:generate |
创建完成后打开 pom.xml 配置文件
修改打包方式为 pom
然后再父工程下新建子项目
- pro04-maven-java
- pro05-maven-java
创建完成后的目录结构
此时再打开父项目的 pom.xml,在 modules 标签中声明了父项目下面的子项目
然后打开 pro04-mavne-java 子项目
在父工程中配置依赖的统一管理
在父工程中加入依赖
1 | <!-- 使用dependencyManagement标签配置对依赖的管理 --> |
放置位置如下
然后在子项目中依然需要声明要依赖的jar包
子项目中引用父项目依赖的几个情况
- 没有声明版本号,默认使用和父项目一样的版本号
- 声明了版本号
- 版本号和父项目一样,则就是一样的
- 版本号不一样,则会覆盖父项目的版本号
此时我们查看一下 pro04-maven-java 这个子项目的依赖
输入命令:mvn dependency:tree
配置自定义属性
1 | <!-- |
在声明版本的地方可以引用自定义的属性
实际意义
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。
如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。
聚合
好处
一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。
以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。
配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
聚合的配置
在总工程中配置 modules 即可:
1 | <modules> |
依赖循环问题
如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
1 | [ERROR] [ERROR] The projects in the reactor contain a cyclic reference: |
这个错误的含义是:循环引用。
IDEA中创建Maven项目
新建项目。选择maven创建
创建项目名称
创建完成后如下
创建完成后修改配置(根据需要修改)
创建子项目
在父项目上右键,新建 module,选择创建 Maven 项目即可
创建完成后,打开父项目的 pom.xml
IDEA中执行Maven命令
方式一:
方式二: