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

开心一刻

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

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

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

我:对不起,我不买了

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

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

先抛问题

关于 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 依赖

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

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

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

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

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

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

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

这是为什么?

你以为没关系,实际启动时会出现如下异常(原因请看: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/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 

然后你就懵逼了

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

我们再调整下 pom.xml

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

此时的 log 依赖如下

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

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

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

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 会将那些必要的间接依赖以传递性依赖的形式引入到当前的项目中

结合实例看 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

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

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

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

而我们自己声明的自定义属性 <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> 

此时的依赖

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

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

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

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

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

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

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

传递依赖长度是 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 依赖优先级遵循两个原则

    第一原则:最短路径优先

    第二原则:最先声明优先

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

举报
发表评论

评论已关闭。

相关文章

当前内容话题
  • 0