Maven命令构建工程
第五节 实验五:让 Web 工程依赖 Java 工程
1、观念
明确一个意识:从来只有 Web 工程依赖 Java 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下。
2、操作
在 pro02-maven-web 工程的 pom.xml 中,找到 dependencies 标签,在 dependencies 标签中做如下配置:
<!-- 配置对Java工程pro01-maven-java的依赖 -->
<!-- 具体的配置方式:在dependency标签内使用坐标实现依赖 -->
<dependency>
<groupId>com.jachie.maven</groupId>
<artifactId>pro01-maven-java</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3、在 Web 工程中,编写测试代码
①补充创建目录
pro02-maven-web\src\test\java\com\atguigu\maven
②确认 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、执行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 被传递到我们的工程了。
第六节 实验六:测试依赖的范围
1、依赖范围
标签的位置: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 包产生冲突,同时减轻服务器的负担。说白了就是:“服务器上已经有了,你就别带啦!”
2、测试
①验证 compile 范围对 main 目录有效
💁♂️
main目录下的类:HelloServlet 使用compile范围导入的依赖:pro01-atguigu-maven
验证:使用compile范围导入的依赖对main目录下的类来说是有效的
有效:HelloServlet 能够使用 pro01-atguigu-maven 工程中的 Calculator 类
验证方式:在 HelloServlet 类中导入 Calculator 类,然后编译就说明有效。
package com.jachie.maven;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
// 测试方式:
// 通过 import 语句将要测试的类引入当前类,引入后:
// 编译通过:可以使用,这个范围的依赖对当前类有效
// 编译失败:不能使用,这个范围的依赖对当前类无效
import com.jachie.maven.Calculator;
// main 目录下的 Java 类: HelloServlet
// compile 范围引入的依赖:pro01-maven-java
// pro01-maven-java中的类:Calculator
// 验证目标:
public class HelloServlet extends HttpServlet{
}
②验证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(){
}
}
然后运行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 安装到仓库):
注意:分析maven是分析本地仓库的,如果之前的包对依赖有变动,需要重新Install安装到本地仓库中
💁♂️
[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(test),但是在 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>
效果还是和之前一样:
💁♂️
[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 包之间的冲突。
所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
2、配置方式
<dependency>
<groupId>com.jachie.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-beans:jar:4.1.4.RELEASE:compile
结论:
对于已经在父工程进行了管理的依赖,子工程中引用可以不写 version
如果写了version:
- 如果配置了 version 和父工程管理版本一致,最终还是采纳这个版本
- 如果配置了 version 和父工程版本不一致,则子工程配置的版本则会覆盖父工程管理的版本并最终采纳。实现了单一版本更新(大多情况下子工程还是总从父工程统一管理的依赖)
⑧在父工程中升级依赖信息的版本
<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 工程,那么在执行构建操作时会报下面的错误:
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
这个错误的含义是:循环引用。