Maven系列3:详解maven解决依赖问题(该系列从 路人甲java 学习)
使用maven创建的项目,有一个重要的文件,就是pom.xml文件。想要用maven帮忙处理项目,我们就要在pom.xml中说明要导入什么包,解决什么依赖,怎么打包,部署,并且项目结构也要按照maven的规定来。
1、约定配置
使用maven搭建的项目时,提倡使用共同的标准目录结构,maven使用约定优于配置的原则。
${basedir} 放置pom.xml和所有的子目录
${basedir}/src/main/java 放置项目的java源代码
${basedir}/src/main/resources 放置项目的资源文件,例如property文件,springmvc.xml文件
${basedir}/src/main/webapp/WEB-INF web应用文件目录,web项目的信息,比如存放web.xml,本地图片,jsp视图页面
${basedir}/src/test/java 放置测试代码,例如junit
${basedir}/src/test/resources 放置测试用的资源
${basedir}/target 打包输出目录
${basedir}/target/classes 编译输出目录
${basedir}/target/test-classes 测试编译输出目录
Test.java maven只会自动运行符合该命名规则的测试类
~/.m2/repository 默认的本地仓库目录位置
2、pom文件
当我们使用maven来解决jar冲突,jar依赖导入,项目的编译,测试,打包,部署时,必须要有pom.xml文件,这些都是在pom文件配置的。
pom(project object model,项目对象模型),是maven工作的基本工作单元,是一个xml文件,项目运行时,maven到pom中获取配置信息,然后执行目标
pom中可以指定以下配置:
项目依赖、插件、执行目标、项目构建、项目版本、开发者名单、相关邮件列表信息
3、maven坐标
maven引入了坐标的概念,每一个构件都有自己的坐标,我们使用maven创建的项目需要标柱其坐标信息,依赖的其他构件也需要这些构建的坐标信息
maven中构建坐标是通过一些元素定义的,它们是groupId,artifactId,version,packaging,classifier
<groupid>定义当前构件所属的组,通常与域名反向一一对应</groupid>
<artifactId>项目组中构件的编号<artifactId/> (一个groupId中可以包含有多个项目,就是通过artifactId来进行区别的)
<version>当前构件的版本号<version/>
<packaging>定义该构件的打包方式,默认为jar,可选(jar,war,ear,pom,maven-plugin)<packaging/>
其中前三个是必须要定义的
例如:
<groupId>com.javacode2018</groupId>
<artifactId>springboot-chat01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
我们可以将创建的springboot项目发布出去,然后只需要告诉别人springboot这个项目的坐标信息,其他人就可以直接使用了,而不用知道该项目都依赖了什么
4、maven导入依赖的构件(maven如何引入需要的依赖)
maven可以帮我们引入需要的构件,而maven是如何定位到某个构件上的呢?
当引入第三方jar包时,我们需要知道其坐标信息,然后将这些信息放入到pom文件的dependencies元素中
<dependencies>
<dependency>
<groupId><groupId/>
<artifactId><artifactId/>
<version><version/>
<type><type/>
<scope><scope/>
<optional><optional/>
<exclusions>
<exclusion><exclusion/>
<exclusion><exclusion/>
<exclusions/>
<dependency/>
<dependencies/>
在dependencies中,有多个dependency,每一个dependency都是一个依赖的的构件,groupid、artifactid、version这个三个是必须的
type:依赖的构件类型,和packaging对应,默认是jar
scope:构件作用的范围(编译源码,编译测试代码,运行测试,运行项目)
option:标记依赖是否可选
exclusion:用于排除传递性的依赖
5、maven依赖范围(scope)
需求:一个项目引入的构件要让它在一些时期起作用,一些时期不起作用。例如junit,只在编译测试代码,运行测试用例时使用,项目发布后就不再使用
这需求怎么实现呢?
java中编译代码、运行代码都需要通过classpath变量,classpath用来列出当前项目需要依赖的jar包,maven需要用到classpath的阶段又4个,编译源代码,编译测试代码,运行测试代码,运行项目,这四个阶段需要的claspath值不一定相同,这个maven中scope可以提供这个支持,scope是用来控制被依赖的构建和classpath的关系(编译,打包,运行所用到的classpath)
<scope><scope/>标签解决这一个问题:scope可选择的值:
compile:编译依赖范围,默认值。编译代码,编译测试代码,运行测试用例,运行项目都起作用
test:测试依赖范围,使用此依赖的话,只对测试代码编译,测试代码运行有用。比如junit
provide:已提供的依赖范围。表示项目的运行环境中已经提供了所需要的构件,此构件在编译源码,编译测试代码,运行测试代码时都有效,但是在运行时无效。比如servlet-api,在运行时由web容器提供,不需要maven在帮忙引入。
runtime:运行时依赖范围,使用此依赖范围的maven依赖,对于编译测试代码,运行测试,运行项目时都有效,但是在编译源码是无效。例如jdbc驱动实现
system:系统依赖范围,该依赖与3中classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显示第指定依赖文件的路径。这种依赖直接依赖于本地路径中的构件,可能每个开发者机器中构件的路径不一致,所以如果使用这种写法,你的机器中可能没有问题,别人的机器中就会有问题,所以建议谨慎使用。
如下:
<dependency>
import:这个比较特殊,后面的文章中单独讲,springboot和springcloud中用到的比较多。
<groupId>com.javacode2018</groupId>
<artifactId>rt</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
*******:scope在运行范围中有效,意思是指依赖的jar包会被打包到运行包中,最后运行时被添加到classpath中运行
6、依赖的传递
假如项目中引入了a,而a又依赖b和c,这时候maven就会自动的把b和c给导入进来,这就是依赖的传递。依赖的传递会受到scope的影响。
假如a依赖于b,b依赖于c,我们说a对于b是第一直接依赖,b对于c是第二直接依赖,a对于c是传递性依赖,而第一直接依赖的scope和第二直接依赖的scope决定了传递依赖的范围,即决定了a对于c的scope的值。
下面我们用表格来列一下这种依赖的效果,表格最左边一列表示第一直接依赖(即A->B的scope的值),而表格中的第一行表示第二直接依赖(即B->C的scope的值),行列交叉的值显示的是A对于C最后产生的依赖效果。
理解:以a对b的scope是complie,b对c的scope是runtime为例
a是在什么时候都需要b,但是当在运行项目时,b这时也是要运行了,就需要引入c。a对c的传递依赖就是在runtime了
7、maven依赖调节功能
现实中可能存在这样的情况,a->b->c->d(1.0),a->h->d(2.0),此时d出现了两个版本,maven会选择d的那个版本呢?
解决这种问题,有两个原则:
路径最近原则:
上面d的2.0版本离a更近一点,所以会选择2.0。
如果路径一样呢?像a->b->d(1.0),a->h->d(2.0)
这时候遵循 最先声明原则:
就看b和h在pom文件中谁最先声明
8、可选依赖
当 a->b:compile
b->c:compile 时,
c会传递到a,假如b不想c让a依赖,怎办么,就使用optional元素,在b依赖c的过程中将optional的值设置为true
9、排除依赖
a->b,b->c(1.0),这两个scope都是compile,此时c会传递到a,但是a要使用c的2.0版本,不能让c传递过来,就可以在b->c中的exclusions
元素中配置,如:
<exclusions>
<exclusion>
<groupId>c的groupid<groupId/>
<artifactId>c的artifactId<artifactId/>
<exclusion/>
<exclusions/>