第二章、Maven依赖(重点)
maven管理依赖也就是jar包牛逼之处是不用我们自己下载,会从一些地方自动下载
maven远程仓库: https://mvnrepository.com/
maven远程仓库: https://maven.aliyun.com/mvn/search
maven工程中我们依靠在pom.xml文件进行配置完成jar包管理工作(依赖)
在工程中引入某个jar包,只需要在 pom.xml 中引入jar包的坐标,比如引入log4j的依赖:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> </dependencies>
Maven 通过 groupId 、 artifactId 与 version 三个向量来定位Maven仓库其jar包所在的位置,并把对应的jar包引入到工程中来。
jar包会自动下载,流程如下
1、依赖的范围
classpath就是编译好的 class字节码文件所在的路径,类加载器( classloader )就是去对应的classpath 中加在class二进制文件。
普通java项目
META-INF中有个文件,有以下内容,告诉jvm执行的时候去哪个类里找main方法。
普通的java工程类路径就是最外层的目录。
1 Manifest-Version: 1.0
2 Main-Class: com.xinzhi.HelloUser
web项目
src 目录下的配置文件会和class文件一样,自动copy到应用的 WEB-INF/classes 目录下 ,所以普通jar包 的类路径就是根路径,没有资源,如果有配置文件也放在src 目录下,他会同步打包在类路径下。 所以web项目的classpath是 WEB-INF/classes
maven项目
maven工程会将 src/main/java 和 src/main/resources 目录下的文件全部打包在classpath 中。运行时他们两个的文件夹下的文件会被放在一个文件夹下。
maven 项目不同的阶段引入到classpath中的依赖是不同的,例如:
①编译时 ,maven 会将与编译相关的依赖引入classpath中
②测试时 ,maven会将测试相关的的依赖引入到classpath中
③运行时 ,maven会将与运行相关的依赖引入classpath中
而依赖范围就是用来控制依赖于这三种classpath的关系。
依赖范围:使用scope标签表示依赖的范围。
依赖范围表示: 这个依赖(jar和里面类)在项目构建的哪个阶段起作用。
依赖范围scope :
compile:默认, 参与构建项目的所有阶段
test:测试,在测试阶段使用, 比如执行mvn test会使用junit 。
provided: 提供者。 项目在部署到服务器时,不需要提供这个依赖的jar , 而是由服务器提供这个依赖的jar包,典型的是servlet 和jsp 依赖
1.1、编译依赖范围(compile)
该范围就是默认依赖范围,此依赖范围对于编译、测试、运行三种classpath都有效,举个简单的例子,假如项目中有 fastjson 的依赖,那么 fastjson不管是在编译,测试,还是运行都会被用到,因此 fastjson必须是编译范围(构件默认的是编译范围,所以依赖范围是编译范围的无须显示指定)
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency>
1.2、测试依赖范围(test)
使用此依赖范围的依赖,只对测试classpath有效,在编译主代码和项目运行时,都将无法使用该依赖, 最典型的例子就是 Junit, 构件在测试时才需要,所以它的依赖范围是测试,因此它的依赖范围需要显示指定为<scope>test</scope>,当然不显示指定依赖范围也不会报错,但是该依赖会被加入到编译和运行classpath中,造成不必要的浪费 。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <!-- 需要显示的指定出来范围,用<scope>标签 --> <scope>test</scope> </dependency>
1.3、已提供依赖范围(provided)
使用该依赖范围的maven依赖,只对编译和测试的classpath有效,对运行的classpath无效,典型的例子就是servlet-api , 编译和测试该项目的时候需要该依赖,但是在运行时,web容器已经提供的该依赖,所以运行时就不再需要此依赖,如果不显示指定该依赖范围,并且容器依赖的版本和maven依赖的版本不一致的话,可能会引起版本冲突,造成不良影响。
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
1.4、运行时依赖范围(runtime)
使用该依赖范围的maven依赖,只对测试和运行的classpath有效,对编译的classpath无效,典型例子就是JDBC 的驱动实现,项目主代码编译的时候只需要JDK 提供的JDBC 接口,只有在测试和运行的时候才需要实现上述接口的具体JDBC 驱动。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> <scope>runtime</scope> </dependency>
2、依赖的传递
jar其实也是别人写的工程,他也会依赖其他的jar包,传递性让我们可以不关心所依赖的jar他依赖了其他哪些jar ,只要我们添加了依赖,他会自动将他所依赖的jar统统依赖进来。
我们只需依赖A.jar ,其他的会自动传递进来。
依赖传递的原则:
最短路径优先原则:如果A依赖于B,B依赖于C ,在B和C 中同时有log4j的依赖,并且这两个版本不一致,那么A会根据最短路径原则,在A中会传递过来B的log4j版本。
路径相同先声明原则:如果工程同时依赖于B和A ,B和C没有依赖关系,并且都有D的依赖,且版本不一致,那么会引入在pom.xml中先声明依赖的log4j版本
<dependency> <groupId>com.xinzi</groupId> <artifactId>B</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>com.xinzhi</groupId> <artifactId>A</artifactId> <version>1.12.2</version> </dependency>
因为B优先声明,所以会使用B中引入的D.jar。
不同范围的依赖的传递性
A依赖B,B依赖C,A能否使用C呢?要看B依赖C的范围是不是compile ,只有compile范围的依赖是可以传递的
3、依赖的排除
不同版本的jar选一个会导致一个问题,1.3.2版本高,A.jar可能用到了高版本的一些新的方法,此时因为某些原因系统选择了低版本,就会导致A.jar报错,无法运行。那么就要想办法把低版本排除掉,一般高版本会兼容低版本
结合上个例子,我们想把低版本的D.jar排除了,就可以这样做,这样系统就只能依赖高版本
<dependencies> <dependency> <groupId>com.xinzi</groupId> <artifactId>B</artifactId> <version>1.5.3</version> <!-- 使用exclusion标签,来定义需要排除的依赖 --> <exclusions> <exclusion> <artifactId>com.xinzhi</artifactId> <groupId>D</groupId> </exlcusion> </exclusions> </dependency> <dependency> <groupId>com.xinzhi</groupId> <artifactId>A</artifactId> <version>1.12.2</version> </dependency> </dependencies>
4、聚合和继承
4.1、继承
应用场景:
此时有两个maven项目 : Maven_Parent使用4.0版本的junit,Maven_Son使用3.8版本的junit, 由于junit是test范围的开发包,所以无法依赖不会被传递,那么就会分散在各个项目模块中,这就可能导致版本不统一的问题.
解决的思路:
将junit依赖版本统一提到"父"版本中,在子工程中不指定版本,以父工程中统一设定版本为准,同时也便于修改.
4.1.1、创建一个Maven工程作为父工程,打包的方式为pom(聚合模块(父模块)的打包方式必须为pom,否则无法完成构建)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- 父工程坐标 --> <groupId>com.mt.sec</groupId> <artifactId>Maven_Parent</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- 聚合工程打包方式为pom --> <packaging>pom</packaging> <dependencyManagement> <dependencies> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> <!-- 定义聚合--> <modules> <!-- 描述模块项目的 相对位置--> <module>../Maven_son</module> </modules> </project>
4.1.2、创建一个Maven工程作为子工程,下面是创建好的原始pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mt.sec</groupId> <artifactId>Maven_Son</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> </project>
4.1.3、在子工程中添加对父工程的引用
<parent> <groupId>com.mt.sec</groupId> <artifactId>Maven_Parent</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- 以当前项目为准的 父项目所在的相对路径--> <relativePath>../Maven_Parent/pom.xml</relativePath> </parent>
4.1.4、将子工程的坐标与父工程坐标中重复的内容删除。此时子项目和父项目的groupId和version坐标是一样的,所以可以删除掉重复的坐标
<!-- 父工程坐标 --> <groupId>com.mt.sec</groupId> <artifactId>Maven_Parent</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <!-- 子工程坐标 --> <groupId>com.mt.sec</groupId> <!-- 与父工程相同删除 --> <artifactId>Maven_Son</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- 与父工程相同删除 -->
4.1.5、在父工程中统一声明junit的依赖。在子工程中删除junit的版本
<!-- 父工程 --> <dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> <!-- 子工程 --> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <!-- 由父工程控制版本,子工程删除 --> <scope>test</scope> </dependency> </dependencies>
此时只需要修改父项目的 junit 版本,其他子项目junit依赖版本也会跟着修改
经过以上步骤,最终子工程的pom.xml文件修改为
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.mt.sec</groupId> <artifactId>Maven_Parent</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- 以当前项目为准的 父项目所在的相对路径--> <relativePath>../Maven_Parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Maven_Son</artifactId>> <dependencies> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
4.2、聚合
配置继承之后执行安装命令时要先安装父工程,否则会出现安装错误,那么如果每个都需要一个一个的安装,会非常的麻烦。聚合的作用就是一键安装各个模块工程,一般情况下会在父工程中定义聚合,聚合模块(父模块)的打包方式必须为pom,否则无法完成构建
在 Parent项目中定义聚合
<!-- 定义聚合--> <modules> <!-- 描述模块项目的相对位置--> <module>../Maven_son</module> </modules>
在定义聚合工程中进行maven安装命令
此时maven会根据项目的继承关系,依次安装模块项目
可以被继承的POM元素如下:
groupId :项目组ID,项目坐标的核心元素
version :项目版本,项目坐标的核心因素
properties :自定义的Maven属性; 一般用于统一制定各个依赖的版本号
dependencies :项目的依赖配置; 公共的依赖
dependencyManagement :项目的依赖管理配置
repositories :项目的仓库配置
build :包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
一些对项目的描述 :
description:项目的描述信息
organization:项目的组织信息
inceptionYear:项目的创始年份
url:项目的URL地址
developers:项目的开发者信息
contributors:项目的贡献者信息
distributionManagement:项目的部署配置
issueManagement:项目的缺陷跟踪系统信息
ciManagement:项目的持续集成系统信息
scm:项目的版本控制系统
malilingLists:项目的邮件列表信息
reporting:包括项目的报告输出目录配置、报告插件配置等