[Maven] 依赖管理

依赖坐标

在一个平面上,坐标(x,y)可以定位一个具体的位置。
在一个空间里,坐标(x,y,z)也可以定位一个具体的位置。

同理,在Maven仓库里面,同样有一套规则可以定位到一个具体的依赖包。

Maven坐标元素有五个:groupId、artifactId、version、packaging、classifier。

坐标元素坐标含义必须
groupId开发依赖包的组织机构,通常采用域名反写必须
artifactId依赖包的具体名称必须
version依赖包的版本必须
packaging依赖包的打包方式默认值jar
classifier依赖包插件输出的一些附属构建不能直接定义

仓库里的(groupId,artifactId,version)就好比是三维世界里的(x,y,z)坐标。

可以把Maven仓库当成五维空间来理解。

依赖范围

Java编程有个非常重要的概念:classpath,它的含义是指明class字节码文件的位置!

在Maven里面,classpath细分为三种场景:

  1. 编译项目主代码(main)的时候,叫做“编译classpath”
  2. 运行项目主代码(main)的时候,叫做“运行classpath”
  3. 编译或运行测试代码(test)的时候,叫做“测试classpath”

依赖包在这三种classpath中是否有效可用,就是依赖范围的概念。

Maven提供了六种依赖范围:

依赖范围编译classpath测试classpath运行classpath示例
compileYYYspring-core
testYjunit
providedYYservlet-api
runtimeYYjdbc驱动
systemYY仓库之外的,本地依赖包

表格中只有五种依赖范围,都会对classpath会产生影响,单元格中的Y表示依赖范围对应classpath是有效可用的,空白单元格表示无效不可用。

举例,引入依赖包junit并声明依赖范围为test:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

该依赖包只在编译和运行测试代码的时候有效,编译、运行主代码的时候无效。

还有一个依赖范围是import,导入依赖范围,它不会影响到classpath,就暂且不提它。

依赖传递性

A依赖B,B依赖C,那么,A和C之间就有了间接的联系。

我们说,A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。

传递性依赖的范围取决于第一直接依赖和第二直接依赖的范围。

Maven的官方文档给出了依赖性依赖范围的表格总结:

compileprovidedruntimetest
compilecompile(*)-runtime-
providedprovided-provided-
runtimeruntime-runtime-
testtest-test-

第一列表示:第一直接依赖的范围
第一行表示:第二直接依赖的范围

中间的单元格:传递性依赖的有效范围,-表示传递性依赖无效。拿上面的例子来说,就是A无法通过传递性依赖关系引用到C,如果需要,就直接引入C的依赖坐标并定义依赖范围。

我自己有个疑问未解决:传递性依赖的作用结果为什么是这样的?这里面有什么样的逻辑含义呢?

依赖调解

有这样一个依赖关系:A -> B -> C -> X(1.0)
还有另一个依赖关系:A -> D -> X(2.0)

两条依赖路径,引入了两个不同版本的依赖包X,到底哪个会被Maven解析使用呢?

两个版本的X都解析肯定不对,同样的类存在两个以上,JVM就会随机加载其中一个,这样多半会出错的。

依赖调解就是解决这个问题的,它遵循两个原则:

  1. 路径最近者优先
  2. 第一声明者优先

在上面的例子中,第一个依赖路径长度是3,第二个依赖路径长度是2,所以X(2.0)会被解析使用,X(1.0)直接被抛弃。

当依赖路径长度相同时,调解的第一原则就不能解决问题了。

比如,两个依赖路径是:A -> B -> X(1.0) 和 A -> C -> X(2.0)

在Maven 2.0.8及之前的版本中,会解析哪个X依赖是不确定的。

从Maven 2.0.9 版本开始,调解的第二原则诞生了,在pom.xml文件中,谁先声明就用谁。

Maven在pom.xml中查找依赖的规则是自上而下递归查询。

在这个例子中,如果A的pom.xml文件里先声明了B依赖,就会先找到X(1.0),反之,就会找到X(2.0)。

有了这两个调解原则,重复依赖的管理就变成了一个确定的事情。

可选依赖

声明可选依赖的方法:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>maven-a</artifactId>
    <version>1.0-SNAPSHOT</version>
    <optional>true</optional>
</dependency>

假如有这样的依赖关系:A -> B,B -> X,B -> Y,他们的依赖范围都是comiple。

如果X和Y不是可选依赖,那么通过传递性依赖关系,A会同时引入X和Y两个依赖。

如果X和Y是可选依赖,那么传递性依赖关系不再起作用,A不会引用到X和Y两个依赖包。

为什么Maven提供了可选依赖这个特性呢?

构建个场景:项目B实现了对数据库MySQL和Oracle的封装,并构建成依赖包,被项目A引用,但是A只用到了MySQL,不会用到Oracle。MySQL和Oracle就对应例子里的X和Y。

如果B对MySQL和Oracle的依赖声明不是可选的,那么A就会同时引入MySQL相关依赖和Oracle相关依赖,变得臃肿起来。

如果B对MySQL和Oracle的依赖声明是可选的,那么A就会缺少MySQL相关依赖,在A的pom.xml文件中显示引入MySQL就解决了这个问题,并且不会带入没用的Oracle依赖。

其实,在理想情况下,不应该使用可选依赖。因为面向对象编程中有个单一职责性原则,意思是代码职责应该单一,不要将太多职责糅合在一起。

单一职责性原则对Maven的项目规划应该同样适用。

考虑到单一职责性原则,我们将项目B拆分未两个项目B1和B2,一个实现对MySQL封装,一个实现对Oracle封装。A需要MySQL就引入B1依赖包。

如此一来,A就不用在自己的pom.xml文件中显示引入MySQL的相关依赖了。这也就避开了可选依赖的问题,而且从设计模式的角度来看,项目设计也会更加的合理些。

排除依赖

传递性依赖的好处是Maven帮我们隐式地引入了很多的依赖,极大的简化了依赖的管理。

但是,这样的好处可能也会带来一些问题:

  1. 可能会引入很多根本用不到的依赖包
  2. 引入的依赖包版本可能不是我们想要的

针对第一个问题,我们可以排除不想要的依赖包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

排除依赖包还有通配符的写法,这里就不展开了,理解意思就行。

针对第二个问题,排除掉版本不正确依赖包,然后显示引入版本正确依赖包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.example</groupId>
            <artifactId>xxx</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.example</groupId>
    <artifactId>xxx</artifactId>
    <version>2.0.0</version>
</dependency>

这里就是个举例,假如spring-boot-starter引入了依赖xxx的1.0.0版本,我们需要的是2.0.0的版本,就可以这么做。

依赖分析

Maven给出了三个依赖分析相关的命令:

  1. mvn dependency:list 查看当前项目解析出的所有依赖包
  2. mvn dependency:tree 查看当前项目的依赖树关系
  3. mvn dependency:analyze Maven对依赖结果的分析结果,可以参考,不可轻信!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值