maven教程(3)+https://developer.aliyun.com/article/1556681
4.4.1 操作
在 pro02-maven-web 工程的 pom.xml 中,找到 dependencies 标签,在 dependencies 标签中做如下配置:
<!-- 配置对Java工程pro01-maven-java的依赖 --> <!-- 具体的配置方式:在dependency标签内使用坐标实现依赖 --> <dependency> <groupId>com.atguigu.maven</groupId> <artifactId>pro01-maven-java</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
4.4.2 在 Web 工程中,编写测试代码
证明在Web工程中可以使用Java工程中创建的那个类:Calculator
💧 确认 Web 工程依赖了 junit
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
💧 创建测试类
把 Java 工程的 CalculatorTest.java 类复制到 pro02-maven-wb\src\test\java\com\atguigu\maven 目录下
4.4.3 执行Maven命令
💧 测试命令:mvn test
说明:测试操作中会提前自动执行编译操作,测试成功就说明编译也是成功的。
💧 打包命令:mvn package
通过查看 war 包内的结构,我们看到被 Web 工程依赖的 Java 工程确实是会变成 Web 工程的 WEB-INF/lib 目录下的 jar 包。
💧 查看当前 Web 工程所依赖的 jar 包的列表
mvn dependency:list
[INFO] The following files have been resolved: [INFO] org.hamcrest:hamcrest-core:jar:1.3:test [INFO] javax.servlet:javax.servlet-api:jar:3.1.0:provided [INFO] com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile [INFO] junit:junit:jar:4.12:test
说明:javax.servlet:javax.servlet-api:jar:3.1.0:provided 格式显示的是一个 jar 包的坐标信息。格式是:
groupId:artifactId:打包方式:version:依赖的范围
这样的格式虽然和我们 XML 配置文件中坐标的格式不同,但是本质上还是坐标信息,大家需要能够认识这样的格式,将来从 Maven 命令的日志或错误信息中看到这样格式的信息,就能够识别出来这是坐标。进而根据坐标到Maven 仓库找到对应的jar包,用这样的方式解决我们遇到的报错的情况。
💧 以树形结构查看当前 Web 工程的依赖信息
mvn dependency:tree
[INFO] com.atguigu.maven:pro02-maven-web:war:1.0-SNAPSHOT [INFO] +- junit:junit:jar:4.12:test [INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test [INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided [INFO] \- com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile
我们在 pom.xml 中并没有依赖 hamcrest-core,但是它却被加入了我们依赖的列表。原因是:junit 依赖了hamcrest-core,然后基于依赖的传递性,hamcrest-core 被传递到我们的工程了。
4.5 依赖范围
标签的位置:scope
标签的可选值:compile(默认值)、test、provided、system、runtime、import
💧 compile 和 test 对比
main目录(空间) | test目录(空间) | 开发过程(时间) | 部署到服务器(时间) | |
compile | 有效 | 有效 | 有效 | 有效 |
test | 无效 | 有效 | 有效 | 无效 |
💧 compile 和 provided 对比
main目录(空间) | test目录(空间) | 开发过程(时间) | 部署到服务器(时间) | |
compile | 有效 | 有效 | 有效 | 有效 |
provided | 有效 | 有效 | 有效 | 无效 |
provided 已提供的,项目部署到服务器,服务器中已经提供了的jar包,就不需要进行额外的打包到服务器
💧 结论
compile:通常使用的第三方框架的 jar 包这样在项目实际运行时真正要用到的 jar 包都是以 compile 范围进行依赖的。比如 SSM 框架所需jar包。
test:测试过程中使用的 jar 包,以 test 范围依赖进来。比如 junit。
provided:在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围依赖进来。比如 servlet-api、jsp-api。而这个范围的 jar 包之所以不参与部署、不放进 war 包,就是避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。说白了就是:“服务器上已经有了,你就别带啦!”
4.6 依赖的传递性
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?
💧 传递的原则
在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
- B 依赖 C 时使用 compile 范围:可以传递
- B 依赖 C 时使用 test 或 provided 范围:不能传递。
工程依赖的jar包是从Maven仓库中查找到相应的jar包然后进行导入的,所以需要引入自己创建的jar包时,需要及时把最新版本的jar更新到本地仓库中。
4.7 依赖的排除
💦 概念
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。
💦 配置方式
<dependency> <groupId>com.atguigu.maven</groupId> <artifactId>pro01-maven-java</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> <!-- 使用excludes标签配置依赖的排除 --> <!-- 排除com.atguigu.maven:pro01-maven-java:1.0-SNAPSHOT中不需要的依赖 --> <exclusions> <!-- 在exclude标签中配置一个具体的排除 --> <exclusion> <!-- 指定要排除的依赖的坐标(不需要写version) --> <!-- 不管哪个版本都会被排除 --> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
4.8 继承
Maven工程之间,A 工程继承 B 工程
- B 工程:父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。
💦 作用
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
- 对一个比较大型的项目进行了模块拆分。
- 一个 project 下面,创建了很多个 module。
- 每一个 module 都需要配置自己的依赖信息。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。
💦 操作
4.8.1 创建父工程
工程名称:pro03-maven-parent
<groupId>com.atguigu.maven</groupId> <artifactId>pro03-maven-parent</artifactId> <version>1.0-SNAPSHOT</version> <!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom --> <packaging>pom</packaging>
只有打包方式为 pom 的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程。
4.8.2 创建子工程
模块工程类似于 IDEA 中的 module,所以需要进入 pro03-maven-parent 工程的根目录,然后运行 mvn archetype:generate 命令来创建模块工程。
💧 查看被添加新内容的父工程 pom.xml
下面 modules 和 module 标签是聚合功能的配置,Maven自动配置
<modules> <module>pro04-maven-module</module> <module>pro05-maven-module</module> <module>pro06-maven-module</module> </modules>
4.8.3 解读子工程的pom.xml
<!-- 使用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> -->
4.8.4 在父工程中配置依赖的统一管理
<!-- 父工程统一管理依赖信息 --> <!-- 使用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>
4.8.5 子工程中引用那些被父工程管理的依赖
关键点:省略版本号
<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 --> <!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 --> <!-- 具体来说是由父工程的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>
4.8.6 在父工程中升级依赖信息的版本
…… <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.4.RELEASE</version> </dependency> ……
然后在子工程中运行mvn dependency:list,效果如下:
[INFO] org.springframework:spring-aop:jar:4.1.4.RELEASE:compile [INFO] org.springframework:spring-core:jar:4.1.4.RELEASE:compile [INFO] org.springframework:spring-context:jar:4.1.4.RELEASE:compile [INFO] org.springframework:spring-beans:jar:4.1.4.RELEASE:compile [INFO] org.springframework:spring-expression:jar:4.1.4.RELEASE:compile
4.8.7 在父工程中声明自定义属性
<!-- 通过自定义属性,统一指定Spring的版本 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 自定义标签,维护Spring版本数据 --> <atguigu.spring.version>4.3.6.RELEASE</atguigu.spring.version> </properties>
在需要的地方使用${}的形式来引用自定义的属性名:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${atguigu.spring.version}</version> </dependency>
真正实现“一处修改,处处生效”。
4.8.8 实际意义
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。
如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。
4.9 聚合
💦 Maven 中的聚合
使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。
- 项目:整体
- 模块:部分
概念的对应关系:
从继承关系角度来看:
●父工程
●子工程
从聚合关系角度来看:
●总工程
●模块工程
💦 好处
- 一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。
以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。 - 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
💦 依赖循环问题
如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
这个错误的含义是:循环引用。
5. 其他核心概念
5.1 生命周期
💦 作用
为了让构建过程自动化完成,Maven 设定了三个生命周期,生命周期中的每一个环节对应构建过程中的一个操作。
💦 三个生命周期
生命周期名称 | 作用 | 各个环节 |
Clean | 清理操作相关 | pre-clean |
clean | ||
post-clean | ||
Site | 生成站点相关 | pre-site |
site | ||
post-site | ||
deploy-site | ||
Default | 主要构建过程 | validate 检查 |
generate-sources | ||
process-sources 源码 | ||
generate-resources 配置文件 | ||
process-resources 复制并处理资源文件,至目标目录,准备打包。 | ||
compile 编译项目 main 目录下的源代码。 | ||
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将最终的包复制到远程的仓库,以让其它开发人员共享;或者部署到服务器上运行(需借助插件,例如:cargo)。 |
💦 特点
- 前面三个生命周期彼此是独立的。
- 在任何一个生命周期内部,执行任何一个具体环节的操作,都是从本周期最初的位置开始执行,直到指定的地方。(本节记住这句话就行了,其他的都不需要记)
Maven 之所以这么设计其实就是为了提高构建过程的自动化程度:让使用者只关心最终要干的即可,过程中的各个环节是自动执行的。
5.2 插件和目标
💦 插件
Maven 的核心程序仅仅负责宏观调度,不做具体工作。具体工作都是由 Maven 插件完成的。例如:编译就是由 maven-compiler-plugin-3.1.jar 插件来执行的。
💦 目标
一个插件可以对应多个目标,而每一个目标都和生命周期中的某一个环节对应。 (目标其实是插件的功能)
Default 生命周期中有 compile 和 test-compile 两个和编译相关的环节,这两个环节对应 compile 和 test-compile 两个目标,而这两个目标都是由 maven-compiler-plugin-3.1.jar 插件来执行的。
5.3 仓库
- 本地仓库:在当前电脑上,为电脑上所有 Maven 工程服务
- 远程仓库:需要联网
- 局域网:我们自己搭建的 Maven 私服,例如使用 Nexus 技术。
- Internet
- 中央仓库
- 镜像仓库:内容和中央仓库保持一致,但是能够分担中央仓库的负载,同时让用户能够就近访问提高下载速度,例如:Nexus aliyun
建议:不要中央仓库和阿里云镜像混用,否则 jar 包来源不纯,彼此冲突。
专门搜索 Maven 依赖信息的网站:https://mvnrepository.com/