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

开心一刻

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

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

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

我:对不起,我不买了

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

先抛问题

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

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>
    </properties>

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

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

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

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

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

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

这是为什么?

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

java 复制代码
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

此时的 log 依赖如下

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

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

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

传递性依赖

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

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

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>
    </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 功能,只需要简单的引入

xml 复制代码
<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 复制代码
<?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

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

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

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

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>

此时的依赖

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

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

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

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

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

xml 复制代码
<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 依赖优先级遵循两个原则

    第一原则:最短路径优先

    第二原则:最先声明优先

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

相关推荐
飞翔的佩奇1 小时前
Java项目: 基于SpringBoot+mybatis+maven校园资料分享平台(含源码+数据库+答辩PPT+毕业论文)
java·spring boot·spring·毕业设计·maven·mybatis·校园资料分享平台
AskHarries15 小时前
Spring Boot集成Akka Cluster实现在分布式节点中执行任务
java·spring boot·maven·akka
三小姐YY16 小时前
Maven 常见问题以及常用命令
maven
老王笔记16 小时前
MAVEN如何导入项目
maven
宋发元16 小时前
Gradle和Maven
gradle·maven
老王笔记16 小时前
maven简介
java·pycharm·maven
早起给猫开空调17 小时前
Maven高级
java·maven
陈大爷(有低保)17 小时前
Maven详细介绍
java·maven
doc_wei18 小时前
Java智慧云智能教育平台
java·spring·毕业设计·maven·intellij-idea·jquery·课程设计
SteveJi66621 小时前
农产品自动识别系统(Java+Springboot+SSM+Vue+Maven+二维码溯源+识别农作物CNN模型PyTorch框架)
java·spring boot·后端·毕业设计·maven