開心一刻
想買摩托車了,但是錢不夠,想找老爸借點
我:老爸,我想買一輛摩托車,上下班也方便
老爸:你表哥上個月騎摩托車摔走了,你不知道?還要買摩托車?
我:對不起,我不買了
老闆:就是啊,騎你表哥那輛得了唄,買啥新的
先拋問題
關於 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
依賴
使用了 idea 的 Maven Helper 外掛,一款不錯的 maven dependency 分析工具,推薦使用
此時你們是不是有疑問了:不就依賴 spring-boot-starter-web
,怎麼會有各種 log
的依賴?
然後我在 pom.xml
中加一行,僅僅加一行
此時的 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/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 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
為什麼是 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 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.version
是 1.7.36
相當於是自己的,傳遞依賴的長度是 0,而 logback 從其父親繼承而來的 slf4j.version
(2.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>
總結
-
maven 的傳遞依賴是個很強大的功能,以後碰到那種引入一個依賴而帶入了超多依賴的情況,不要再有疑問
-
maven 依賴優先順序遵循兩個原則
第一原則:最短路徑優先
第二原則:最先宣告優先
第一原則處理不了的情況才會採用第二原則;自定義屬性同樣遵循這兩個原則