前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >结合实例看 maven 传递依赖与优先级,难顶也得上丫

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

作者头像
青石路
发布2024-08-08 08:31:13
870
发布2024-08-08 08:31:13
举报
文章被收录于专栏:开发技术

先抛问题

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

代码语言:javascript
复制
<?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依赖
log依赖

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

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

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

新加一行
新加一行

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

log依赖变化
log依赖变化

这是为什么?

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

代码语言:javascript
复制
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/StaticLoggerBinder
	at 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.StaticLoggerBinder
	at 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日志
pom去掉springboot日志

此时的 log 依赖如下

logback1.3.14依赖的slf4j怎么是1.7.36
logback1.3.14依赖的slf4j怎么是1.7.36

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

logback1.3.14依赖slf4j2.0.7
logback1.3.14依赖slf4j2.0.7

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

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

传递性依赖

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

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

代码语言:javascript
复制
<?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 功能,只需要简单的引入

代码语言:javascript
复制
<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)会被忽略

我们再来看

代码语言:javascript
复制
<?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依赖变化
log依赖变化

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

logback1.2.12
logback1.2.12

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

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

代码语言:javascript
复制
<?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
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
slf4j1.7.36

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

slf4j2.0.7
slf4j2.0.7

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

代码语言:javascript
复制
<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 依赖优先级遵循两个原则 第一原则:最短路径优先 第二原则:最先声明优先 第一原则处理不了的情况才会采用第二原则;自定义属性同样遵循这两个原则
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先抛问题
  • 传递性依赖
  • 依赖优先级
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档