②验证test范围对main目录无效
测试方式:在主体程序中导入org.junit.Test这个注解,然后执行编译。
具体操作:在pro01-maven-java\src\main\java\com\atguigu\maven目录下修改Calculator.java
package com.atguigu.maven; import org.junit.Test; public class Calculator { public int sum(int i, int j){ return i + j; } }
执行Maven编译命令:
[ERROR] /D:/maven-workspace/space201026/pro01-maven-java/src/main/java/com/atguigu/maven/Calculator.java:[3,17] 程序包org.junit不存在
③验证test和provided范围不参与服务器部署
其实就是验证:通过compile范围依赖的jar包会放入war包,通过test范围依赖的jar包不会放入war包
④验证provided范围对测试程序有效
测试方式是在pro02-maven-web的测试程序中加入servlet-api.jar包中的类。
修改:pro02-maven-web\src\test\java\com\atguigu\maven\CalculatorTest.java
package com.atguigu.maven; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import org.junit.Test; import com.atguigu.maven.Calculator; // 静态导入的效果是将Assert类中的静态资源导入当前类 // 这样一来,在当前类中就可以直接使用Assert类中的静态资源,不需要写类名 import static org.junit.Assert.*; public class CalculatorTest{ @Test public void testSum(){ // 1.创建Calculator对象 Calculator calculator = new Calculator(); // 2.调用Calculator对象的方法,获取到程序运行实际的结果 int actualResult = calculator.sum(5, 3); // 3.声明一个变量,表示程序运行期待的结果 int expectedResult = 8; // 4.使用断言来判断实际结果和期待结果是否一致 // 如果一致:测试通过,不会抛出异常 // 如果不一致:抛出异常,测试失败 assertEquals(expectedResult, actualResult); } }
然后运行Maven的编译命令:mvn compile
然后看到编译成功。
实验七:测试依赖的传递性
1、依赖的传递性
①概念
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?
②传递的原则
在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
- B 依赖 C 时使用 compile 范围:可以传递
- B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。
2、使用 compile 范围依赖 spring-core
测试方式:让 pro01-maven-java 工程依赖 spring-core
具体操作:编辑 pro01-maven-java 工程根目录下 pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.0.RELEASE</version> </dependency>
使用 mvn dependency:tree 命令查看效果:
[INFO] com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT [INFO] +- junit:junit:jar:4.12:test [INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test [INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile [INFO] - commons-logging:commons-logging:jar:1.1.1:compile
还可以在 Web 工程中,使用 mvn dependency:tree 命令查看效果(需要重新将 pro01-maven-java 安装到仓库):
[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 [INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile [INFO] - commons-logging:commons-logging:jar:1.1.1:compile
3、验证 test 和 provided 范围不能传递
从上面的例子已经能够看到,pro01-maven-java 依赖了 junit,但是在 pro02-maven-web 工程中查看依赖树的时候并没有看到 junit。
要验证 provided 范围不能传递,可以在 pro01-maven-java 工程中加入 servlet-api 的依赖。
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
效果还是和之前一样:
TIP [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 [INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile [INFO] - commons-logging:commons-logging:jar:1.1.1:compile
实验八:测试依赖的排除
1、概念
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。
2、配置方式
<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>
3、测试
测试的方式:在 pro02-maven-web 工程中配置对 commons-logging 的排除
<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> 运行 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 [INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile 发现在 spring-core 下面就没有 commons-logging 了。
实验九:继承
1、概念
Maven工程之间,A 工程继承 B 工程
- B 工程:父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。
2、作用
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
- 对一个比较大型的项目进行了模块拆分。
- 一个 project 下面,创建了很多个 module。
- 每一个 module 都需要配置自己的依赖信息。
它背后的需求是:
- 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
- 使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
- 使用框架时所需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。
3、举例
在一个工程中依赖多个 Spring 的 jar 包
[INFO] +- org.springframework:spring-core:jar:4.0.0.RELEASE:compile [INFO] | - commons-logging:commons-logging:jar:1.1.1:compile [INFO] +- org.springframework:spring-beans:jar:4.0.0.RELEASE:compile [INFO] +- org.springframework:spring-context:jar:4.0.0.RELEASE:compile [INFO] +- org.springframework:spring-expression:jar:4.0.0.RELEASE:compile [INFO] +- org.springframework:spring-aop:jar:4.0.0.RELEASE:compile [INFO] | - aopalliance:aopalliance:jar:1.0:compile
使用 Spring 时要求所有 Spring 自己的 jar 包版本必须一致。为了能够对这些 jar 包的版本进行统一管理,我们使用继承这个机制,将所有版本信息统一在父工程中进行管理。
4、操作
①创建父工程
创建的过程和前面创建 pro01-maven-java 一样。
工程名称: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 工程的工程。
②创建模块工程
模块工程类似于 IDEA 中的 module,所以需要进入 pro03-maven-parent 工程的根目录,然后运行 mvn archetype:generate 命令来创建模块工程。
假设,我们创建三个模块工程:
③查看被添加新内容的父工程 pom.xml
下面 modules 和 module 标签是聚合功能的配置
<modules> <module>pro04-maven-module</module> <module>pro05-maven-module</module> <module>pro06-maven-module</module> </modules>
④解读子工程的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> -->
⑤在父工程中配置依赖的统一管理
<!-- 使用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>
⑥子工程中引用那些被父工程管理的依赖
关键点:省略版本号
<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 --> <!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 --> <!-- 具体来说是由父工程的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>
⑦在父工程中升级依赖信息的版本
…… <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
⑧在父工程中声明自定义属性
<!-- 通过自定义属性,统一指定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>
真正实现“一处修改,处处生效”。
5、实际意义
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。
如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。
实验十:聚合
1、聚合本身的含义
- 部分组成整体
2、Maven 中的聚合
使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。
- 项目:整体
- 模块:部分
概念的对应关系:
从继承关系角度来看:
- 父工程
- 子工程
从聚合关系角度来看:
- 总工程
- 模块工程
3、好处
- 一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。
以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。 - 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
4、聚合的配置
在总工程中配置 modules 即可:
<modules> <module>pro04-maven-module</module> <module>pro05-maven-module</module> <module>pro06-maven-module</module> </modules>
5、依赖循环问题
如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
DANGER [ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
这个错误的含义是:循环引用。
chapter4 使用Maven:IDEA环境
创建父工程
1、创建 Project
2、开启自动导入
- 创建 Project 后,IDEA 会自动弹出下面提示,我们选择『Enable Auto-Import』,意思是启用自动导入。
这个自动导入一定要开启,因为 Project、Module 新创建或 pom.xml 每次修改时都应该让 IDEA 重新加载 Maven 信息。这对 Maven 目录结构认定、Java 源程序编译、依赖 jar 包的导入都有非常关键的影响。
- 另外也可以通过 IDEA 的 Settings 设置来开启:
配置Maven信息
每次创建 Project 后都需要设置 Maven 家目录位置,否则 IDEA 将使用内置的 Maven 核心程序(不稳定)并使用默认的本地仓库位置。这样一来,我们在命令行操作过程中已下载好的 jar 包就白下载了,默认的本地仓库通常在 C 盘,还影响系统运行。
配置之后,IDEA 会根据我们在这里指定的 Maven 家目录自动识别到我们在 settings.xml 配置文件中指定的本地仓库。
创建Java模块工程
创建Web模块工程
1、创建模块
- 按照前面的同样操作创建模块,此时这个模块其实还是一个Java模块。
2、修改打包方式
Web 模块将来打包当然应该是 war 包。
<packaging>war</packaging>
3、Web 设定
首先打开项目结构菜单:
然后到 Facets 下查看 IDEA 是否已经帮我们自动生成了 Web 设定。正常来说只要我们确实设置了打包方式为 war,那么 IDEA 2019 版就会自动生成 Web 设定。
另外,对于 IDEA 2018 诸版本没有自动生成 Web 设定,那么请参照下面两图,我们自己创建:
4、借助IDEA生成web.xml
5、设置 Web 资源的根目录
结合 Maven 的目录结构,Web 资源的根目录需要设置为 src/main/webapp 目录。
其他操作
1、在IDEA中执行Maven命令
①直接执行
②手动输入
如果有需要,还可以给命令后面附加参数:
# -D 表示后面要附加命令的参数,字母 D 和后面的参数是紧挨着的,中间没有任何其它字符
# maven.test.skip=true 表示在执行命令的过程中跳过测试
mvn clean install -Dmaven.test.skip=true
2、在IDEA中查看某个模块的依赖信息
3、工程导入
Maven工程除了自己创建的,还有很多情况是别人创建的。而为了参与开发或者是参考学习,我们都需要导入到 IDEA 中。下面我们分几种不同情况来说明:
①来自版本控制系统
目前我们通常使用的都是 Git(本地库) + 码云(远程库)的版本控制系统,结合 IDEA 的相关操作方式请点这里 (opens new window)查看克隆远程库部分。
②来自工程目录
直接使用 IDEA 打开工程目录即可。下面咱们举个例子:
[1]工程压缩包
假设别人发给我们一个 Maven 工程的 zip 压缩包:maven-rest-demo.zip。从码云或GitHub上也可以以 ZIP 压缩格式对项目代码打包下载。
[2]解压
如果你的所有 IDEA 工程有一个专门的目录来存放,而不是散落各处,那么首先我们就把 ZIP 包解压到这个指定目录中。
[3]打开
只要我们确认在解压目录下可以直接看到 pom.xml,那就能证明这个解压目录就是我们的工程目录。那么接下来让 IDEA 打开这个目录就可以了。
[4]设置 Maven 核心程序位置
打开一个新的 Maven 工程,和新创建一个 Maven 工程是一样的,此时 IDEA 的 settings 配置中关于 Maven 仍然是默认值:
所以我们还是需要像新建 Maven 工程那样,指定一下 Maven 核心程序位置:
4、模块导入
①情景重现
在实际开发中,通常会忽略模块(也就是module)所在的项目(也就是project)仅仅导入某一个模块本身。这么做很可能是类似这样的情况:比如基于 Maven 学习 SSM 的时候,做练习需要导入老师发给我们的代码参考。
②导入 Java 类型模块
[1]找到老师发的工程目录
[2]复制我们想要导入的模块目录
[3]粘贴到我们自己工程目录下
[4]在 IDEA 中执行导入
[5]修改 pom.xml
刚刚导入的 module 的父工程坐标还是以前的,需要改成我们自己的 project。
[6]最终效果
③导入 Web 类型模块
其它操作和上面演示的都一样,只是多一步:删除多余的、不正确的 web.xml 设置。如下图所示: