结合实例看 maven 传递依赖与优先级,难顶也得上丫

news/2024/11/20 12:23:11/文章来源:https://www.cnblogs.com/youzhibing/p/18346010

开心一刻

想买摩托车了,但是钱不够,想找老爸借点

我:老爸,我想买一辆摩托车,上下班也方便

老爸:你表哥上个月骑摩托车摔走了,你不知道?还要买摩托车?

我:对不起,我不买了

老板:就是啊,骑你表哥那辆得了呗,买啥新的

你是认真的吗

先抛问题

关于 maven 的依赖(dependency),我相信大家多少都知道点

<?xml version="1.0" encoding="UTF-8"?>
<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.qsl</groupId><artifactId>spring-boot-2_7_18</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
</project>

依赖什么就引入什么,是不是很合理,也很合逻辑?我们来看下此时的 log 依赖

log依赖

使用了 idea 的 Maven Helper 插件,一款不错的 maven dependency 分析工具,推荐使用

此时你们是不是有疑问了:不就依赖 spring-boot-starter-web,怎么会有各种 log 的依赖?

然后我在 pom.xml 中加一行,仅仅加一行

新加一行

此时的 log 依赖与之前就有了变化

log依赖变化

这是为什么?

你以为没关系,实际启动时会出现如下异常(原因请看:SpringBoot2.7还是任性的,就是不支持Logback1.3,你能奈他何)

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinderat org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:304)at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:118)at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:238)at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:220)at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:178)at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:171)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:145)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:133)at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:79)at org.springframework.boot.SpringApplicationRunListeners.lambda$starting$0(SpringApplicationRunListeners.java:56)at java.util.ArrayList.forEach(ArrayList.java:1249)at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:56)at org.springframework.boot.SpringApplication.run(SpringApplication.java:299)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1289)at com.qsl.Application.main(Application.java:16)
Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinderat java.net.URLClassLoader.findClass(URLClassLoader.java:381)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)... 17 more

然后你就懵逼了

怎么会这样

我们再调整下 pom.xml

pom去掉springboot日志

此时的 log 依赖如下

logback1.3.14依赖的slf4j怎么是1.7.36

也许你们觉得没问题,我再给你们引申下;logback1.3.14 依赖的 slf4j 版本是 2.0.7

logback1.3.14依赖slf4j2.0.7

slf4j1.7.36 是哪来的,为什么不是 2.0.7 ?

这一连串问题下来,就问你们慌不慌,但你们不要慌,因为我会出手!

传递性依赖

在 maven 诞生之前,那时候添加 jar 依赖可以说是一个非常头疼的事,需要手动去添加所有的 jar,非常容易遗漏,然后根据异常去补遗漏的 jar;很多有经验的老手都会分类,比如引入 Spring 需要添加哪几个 jar,引入 POI 又需要添加哪几个 jar,但还是容易遗漏;而 maven 的传递性依赖机制就很好的解决了这个问题

何谓传递性依赖,回到我们最初的案例

<?xml version="1.0" encoding="UTF-8"?>
<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.qsl</groupId><artifactId>spring-boot-2_7_18</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
</project>

直观看上去,只依赖了 spring-boot-starter-web,但 spring-boot-starter-web 也有自身的依赖,maven 也会进行解析,以此类推,maven 会将那些必要的间接依赖以传递性依赖的形式引入到当前的项目中

传递性依赖

问题

不就依赖 spring-boot-starter-web,怎么会有各种 log 的依赖?

是不是清楚了?

依赖优先级

传递性依赖机制大大简化了依赖声明,对我们开发者而言非常友好,比如我们需要用到 spring 的 web 功能,只需要简单的引入

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

就 ok 了,是不是 so easy ?但同样会带来一些问题,比如项目 P 有如下两条传递性依赖

P -> A -> B -> C(1.0)

P -> D -> C(2.0)

那么哪个 C 会被 maven 引入到 P 项目中呢?此时 maven 会启用它的第一原则

最短路径优先

这里的 路径 指的是传递依赖的长度,一次传递依赖的长度是 1,P 到 C(1.0)传递依赖的长度是 3,而 P 到 C(2.0)传递依赖的长度是 2,所以 C(2.0)会被 maven 引入到 P 项目,而 C(1.0)会被忽略

最短路径优先 并不能解决所有问题,比如项目 P 有如下两条传递性依赖

P -> B -> C(1.0)

P -> D -> C(2.0)

两条传递依赖的长度都是 2,那 maven 会引入谁了?从 maven 2.0.9 开始,maven 增加了第二原则

第一声明优先

用来处理 最短路径优先 处理不了的情况;在项目 P 的 pom 中,先被声明的会被 maven 采用而引入到项目 P,所以 B 和 D 的声明顺序决定了 maven 是引入 C(1.0)还是引入 C(2.0),如果 B 先于 D 被声明,那么 C(1.0)会被 maven 引入到 P,而 C(2.0)会被忽略

我们再来看

<?xml version="1.0" encoding="UTF-8"?>
<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.qsl</groupId><artifactId>spring-boot-2_7_18</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><logback.version>1.3.14</logback.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
</project>

此时的 logback

log依赖变化

为什么是 1.3.14,而不是 1.2.12?这里其实涉及到 自定义属性 的覆盖,有点类似 java 中的 override;1.2.12 是在父依赖(spring-boot-starter-parent)的父依赖(spring-boot-dependencies)中声明的自定义属性

logback1.2.12

而我们自己声明的自定义属性 <logback.version>1.3.14</logback.version> 正好覆盖掉了 1.2.12,所以 maven 采用的是 1.3.14

是不是只剩最后一个问题了,我们先来回顾下问题,pom.xml 内容如下

<?xml version="1.0" encoding="UTF-8"?>
<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.qsl</groupId><artifactId>spring-boot-2_7_18</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><logback.version>1.3.14</logback.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><artifactId>spring-boot-starter-logging</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version></dependency></dependencies>
</project>

此时的依赖

logback1.3.14依赖的slf4j怎么是1.7.36

slf4j 为什么是 1.7.36,而不是 logback 中的 2.0.7?这里其实涉及到 自定义属性 的优先级

自定义属性的优先级同样遵循 maven 传递依赖的第一、第二原则

从爷爷(spring-boot-dependencies)继承来的 slf4j.version1.7.36

slf4j1.7.36

相当于是自己的,传递依赖的长度是 0,而 logback 从其父亲继承而来的 slf4j.version (2.0.7)

slf4j2.0.7

传递依赖长度是 1,所以 maven 采用的是 1.7.36 而不是 2.0.7;那如何改了,最简单的方式如下

<properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><logback.version>1.3.14</logback.version><slf4j.version>2.0.7</slf4j.version>
</properties>

总结

  1. maven 的传递依赖是个很强大的功能,以后碰到那种引入一个依赖而带入了超多依赖的情况,不要再有疑问

  2. maven 依赖优先级遵循两个原则

    第一原则:最短路径优先

    第二原则:最先声明优先

    第一原则处理不了的情况才会采用第二原则;自定义属性同样遵循这两个原则

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/779099.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EmpireCMS_V7.5 sql注入漏洞

没有什么好说的侵权声明 本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。 如果您是版权持有人,并且认为您的作品被侵犯,请通过以下方式与我们联系: […

增强用户体验:2个功能强大的.NET控制台应用帮助库

前言 对于.NET开发者而言,构建控制台应用程序时,如何提升用户交互的流畅性和满意度,是一个持续探索与优化的话题。今天大姚给大家分享2个功能强大的.NET控制台应用帮助库,希望可以帮助大家能够快速的构建漂亮、强交互性、丰富功能的控制台应用程序。 Terminal.Gui Terminal…

2024牛客暑期多校训练营7

你说的对但是今天是伟大的御阿礼之子第九代稗田阿求同志的三十华诞目录写在前面JIKDCH写在最后 写在前面 比赛地址:https://ac.nowcoder.com/acm/contest/81602#question 以下按个人难度向排序。 dztlb 大神回去补办身份证了,于是单刷,打的像史。 呃呃抽象场。 J 签到。 手玩…

读零信任网络:在不可信网络中构建安全系统11用户组的认证和授权

读零信任网络:在不可信网络中构建安全系统11用户组的认证和授权1. 用户组的认证和授权 1.1. 几乎在每个系统中都有一小部分操作需要被密切关注1.1.1. 每个应用对这部分操作的风险容忍度各有不同,且没有任何下限1.1.2. 一部分风险是由用户个人的可信度决定的1.1.2.1. 单个用户…

软件测试设计1探索性测试

1 探索性测试 本章将介绍探索性测试:手动试用新功能,快速获得有关其行为的反馈。我们将详细介绍探索性测试,考虑它的优缺点,以及何时应在项目中执行探索性测试。 我们将了解开始探索性测试所需的先决条件以及应采取的方法。这种测试可以是完整测试计划的一个缩影,它从客户…

Kubernetes高级部署组件 Argo Rollout

Argo介绍 https://argoproj.github.io/ Argo 是一个开源项目,旨在提供一套用于在 Kubernetes 上运行和管理容器化工作负载的工具。 Argo 项目最早在2017年由Applatix公司创立开源,在2018年被美国加利福尼的 Intuit 公司收购并持续维护,并得到了广泛的社区支持。 Argo 项目主要…

n00bzCTF 2024

n00bzCTF 2024n00bzCTF 2024 Crypto Vinegar 题目:Encrypted flag: nmivrxbiaatjvvbcjsf Key: secretkeyexp: 维吉尼亚密码 ​​ flag:n00bz{vigenerecipherisfun} RSA 题目:e = 3 n = 1351123252887151367278321777355120706250832196704807178418175833438514454543565797…

两个coca略有不同词频文件 比较

coca20000xlsxFuzhi.a应该也是这样弄的.txt COCA60000.txt确实有一些词顺序不同,不知道为什么。。。

【第2期】2024 搜索客 Meetup | Elasticsearch 的代码结构和写入查询流程的解读

本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,活动主题将深入探讨 Elasticsearch 的两个核心方面:代码结构以及写入和查询的关键流程。本次活动将为 Elasticsearch 初学者和有经验的用户提供宝贵的见解,欢迎大家报名参加、交流学习。 活动主题:Elasticsearch 的…

【第2期】2024 搜索客 Meetup | Elasticsearch 的代码结构和写入查询流程的解读.md

本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,活动主题将深入探讨 Elasticsearch 的两个核心方面:代码结构以及写入和查询的关键流程。本次活动将为 Elasticsearch 初学者和有经验的用户提供宝贵的见解,欢迎大家报名参加、交流学习。 活动主题:Elasticsearch 的…

使用黑群晖webdav服务同步obsidian笔记

前言 本文使用的黑群晖套件 webdav server进行黑群晖配置,接着使用obsidian插件将笔记同步到黑群晖上 使用的黑群晖已经经过内网穿透。 配置黑群晖 配置 webdav 并创建共享文件夹 首先下载套件,在套件中心搜索webdav server ,下载安装并打开进行配置此处已经安装好了,打开,…

文件的读写

1.判断文件夹是否存在 2.文件的数据写入 3.文件内容的读取 4.文件的复制,移动,删除 5.log日志的记录 6.三种序列化器: