日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

一 背景

為什么想寫此文

去年的Log4j-core的安全問題,再次把供應鏈安全推向了高潮。在供應鏈安全的場景,螞蟻集團在靜態代碼掃描平臺-STC和資產威脅透視平臺-哈勃這2款產品的聯動合作下,優勢互補,很好的解決了直接依賴和間接依賴的場景。

但是由于STC是基于事前,受限于掃描效率存在遺漏的風險面,而哈勃又是基于事后,存在修復時間上的風險。基于此,筆者嘗試尋找一種方式可以同時解決2款產品的短板。筆者嘗試研究了一下Maven是如何處理一個項目中的直接依賴和間接依賴的,并且在遇到相同依賴時,Maven是如何進行抉擇的,這里的如何抉擇其實就是Maven的仲裁機制。帶著這些問題,筆者嘗試調研了Maven的源碼和做了一些本地的測試實驗。總結了這篇文章。

坐標是什么?

在空間坐標系中,我們可以通過xyz表示一個點,同樣在Maven的世界里,我們可以通過一組GAV在依賴的世界里明確表示一個依賴,比如:

: com.alibaba 一般是公司的名稱

: fastjson 項目名稱

: 1.2.24 版本號

影響依賴的標簽都有哪些

1.

直接引入具體的依賴信息。注意是不在標簽內的情況。如果是在內的情況,請參考2號標簽。

2.

只聲明但不發生實際引入,作為依賴管理。依賴管理是指真正發生依賴的時候,再去參考依賴管理的數據。

這樣使用dependency的時候,可以缺省version。

另外還可以管控所有的間接依賴,即使間接依賴聲明了version,也要被覆蓋掉。

3.

聲明自己的父親,Maven的繼承哲學跟JAVA很類似,因為Maven本身也是用Java實現的,滿足單繼承。

一旦子pom繼承了父pom,那么會把父pom里的,等等屬性都繼承過來的。當然如果在繼承的過程中,出現一樣的元素,也是子去覆蓋父親,和Java類似。

繼承時,會分類繼承。dependencies繼承dependencies,dependencyManagement里的依賴管理只能繼承dependencyManagement范圍內的依賴管理。

每一個pom文件都會有一個父親,即使不聲明Parent,也會默認有一個父親。和Java的Object設計哲學類似。后面在源碼分析中我們還會提到。

4.

代表當前自己的項目的一個屬性的集合。

properties僅僅代表屬性的聲明,一個屬性聲明了,和他是否被引用并無關系。我完全可以聲明一系列不被人使用的屬性。

依賴的作用域都有哪些

一個依賴在引入的時候,是可以聲明這個依賴的作用范圍的。比如這個依賴只對本地起作用,比如只對測試起作用等等。作用域一共有compile,provided,system,test,import,runtime 這幾個值。

簡單總結一下:

compile和runtime會參與最后的打包環節,其余的都不會。compile可以不寫。

test只會對 src/test目錄下的測試代碼起作用。

provided是指線上已經提供了這個Jar包,打包的時候不需要在考慮他了,一般像servlet的包很多都是provided。

system和provided沒什么太大的區別。

import只會出現在dependencyManagement標簽內的依賴中,是為了解決Maven的單繼承。引入了這個作用域的話,maven會把此依賴的所有的dependencyManagement內的元素加載到當前pom中的,但不會引入當前節點。如下圖,并不會引入fastjson作為依賴管理的元素,只是會把fastjson文件定義的依賴管理引入進來。

二 單個Pom樹的依賴競爭

Pom文件本質

一個Pom文件的本質就是一棵樹。

在人的視角來觀察一個Pom文件的時候,我們會認為他是一個線狀的一個依賴列表,我們會認為下圖的Pom文件抽象出來的結果是C依賴了A,B,D。但我們的視角是不完備的,Maven的視角來看,Maven會把這一個Pom文件直接抽象成一個依賴樹。Maven的視角能看到除了ABD之外的節點。而人只能看到ABD三個節點。

既然是在一棵樹上,那么相同的節點就必然會存在競爭關系。這個競爭關系就是我們提到了仲裁機制。

Maven仲裁機制原則

1.依賴競爭時,越靠近主干的越優先。

2.單顆樹在依賴在競爭時(dependencies)(注意:不是dependencyManagement里的dependencies):

當deep=1,即直接依賴。同級是靠后優先。

當deep>1,即間接依賴。同級是靠前優先。

3.單顆樹在依賴管理在競爭時(注意:是dependencyManagement里的dependencies)是靠前優先的。

4.maven里最重要的2個關系,分別是繼承關系和依賴關系。我們所有的規律都應該只從這2個關系入手。

下圖中分別是2個子pom文件(方塊代表依賴的節點,A-1 表示A這個節點使用的是1版本,字母代表節點,數字代表版本)。

左邊這個子pom生成的樹依賴了 D-1,D-2和D-5。滿足依賴競爭原則1,即越靠近樹的左側越優先的原則,所以D-5會競爭成功。

但是B-1和B-2同時都位于樹的同一深度,并且深度為1,由于B-2更加靠后,所以B-2會競爭成功。

右邊的子pom生成的樹依賴了 D-1和D-2,并且位于同一深度,但由于D-1和D-2是屬于間接依賴的范圍,deep大于1,所以是靠前優先,那么也就是D-1會競爭成功。

常見場景

看到這里,想必大家已經了解了Maven的仲裁原則。但是在實際的工作中,光有原則還需要在代碼中可以靈活的運用才能有屬于自己的理解,這里筆者準備了5個場景,每個場景對應的答案都在后面,大家閱讀時,可以自己嘗試用Maven的原則來去推理,看看有沒有哪里不符合預期的情況。

場景一 難度(*)

場景描述

主POM里有這個屬性為1.2.24。

父親是spring-boot-starter-parent-3.13.0。父親里的是1.2.77。

并且在主pom中,消費了這個屬性。

那么針對主POM這顆樹,他最終會是使用哪一個fastjson呢?

場景示例

結構圖

場景二 難度(**)

在同一個主POM或者子POM中的dependencies中同時使用了Fastjson,第一個聲明了1.2.24的版本,第二個聲明了1.2.25版本。那么針對主POM或者子pom這棵樹,最終會選擇fastjson 1.2.24還是1.2.25呢?

場景示例

結構圖

場景三 難度(***)

下圖中左圖為主POM文件內的dependencyManagement里的fastjson為1.2.77,這個時候子POM中顯示聲明自己的版本1.2.78。那么針對子POM這顆樹,子POM會選擇聽從父命還是遵從內心呢?

場景示例

結構圖

場景四 難度(****)

主POM的dependenciesFastjson:1.2.24主POM的dependencymanagentFastjson:1.2.77

主POM的父親(springboot)的dependenciesFastjson 1.2.78

子POM里的dependenciesFastjson 1.2.25

這種情況下針對子pom來說,他會選擇4個版本中的哪一個呢?

場景示例

結構圖

場景五 難度(*****)

主POM的dependenciesFastjson:1.2.24主POM的dependencymanagentFastjson:1.2.77

主POM的父親(springboot)的dependenciesFastjson 1.2.78

子POM里的dependencies 不寫version

場景五跟場景四整體沒有差別,只是將子pom的dependencies的版本進行缺省。

這種情況下針對子pom來說,針對子pom,他會選擇3個版本中的哪一個呢?

場景示例

結構圖

答案

場景一

1.2.24會最終生效。

因為子會繼承父親的屬性,但是由于自己有這個屬性,那么則覆蓋!

繼承一定會伴隨著覆蓋的,這個設計在編程語言中還是比較普遍的。

場景二

1.2.25會最終生效。

參考 單顆樹在依賴在競爭時:當deep=1,即直接依賴。同級是靠后優先。

滿足Maven的核心競爭依賴策略!

場景三

1.2.78最終會生效。

一個項目里的dependencyManagement只能對不聲明version的dependency和間接依賴有效!

場景四

1.2.25會最終生效。這個比較復雜。

〇: 首先根據父子的繼承關系,1.2.24會覆蓋掉1.2.78。所以78版本淘汰

一: 由于一個項目里的dependencyManagement只能對不聲明version的dependency和間接依賴有效,所以

1.2.77無法對1.2.25起作用。

二: 由于父子的繼承關系,1.2.25會覆蓋掉1.2.24.

所以最終1.2.25勝出!

場景五

1.2.77會最終生效。

〇: 首先根據父子的繼承關系,1.2.24會覆蓋掉1.2.78。所以78版本淘汰

一: 由于一個項目里的dependencyManagement是可以對不聲明的version起作用,所以子pom的版本為1.2.77

二: 由于父子的繼承關系,1.2.77會覆蓋掉1.2.24.

所以最終1.2.77勝出!

三 多個Pom樹合并打包

多棵樹構建順序原則

現在的項目一般都是多模塊管理,會存在非常多的pom文件。多棵樹的情況下每棵樹的出場順序都是事先已經被計算好的。

這個功能在Maven的源碼中是一個叫Reactor(反應堆)實現的。它主要做了一件事情就是決定一個項目中,多個子pom誰先進行build的順序,這個出廠順序很重要,在合并打包時,往往決定了最終誰會在多個pom之間勝出的問題。

Reactor的原則

多棵樹(多個子pom)構建的順序是按照被依賴方的要在前,依賴方在后的原則。

項目要保證這里是不能出現循環依賴的。

Reactor的原則圖解

如下圖子pom1 在被子pom2和子pom3同時依賴,所以子pom1最先被構建,子pom3沒有人被依賴,所以最后構建。

SpringBoot Fatjar打包的策略

SpringBoot 打包會打成一個Fatjar,所有的依賴都會放在BOOT-INF/lib/目錄下。SpringBoot的打包是越靠后的構建pom越優先,因為一般會把springboot的打包插件放在最不被依賴的module里(比如上圖里的Pom3)。(SpringBoot的打包插件一般放在bootstrap pom里,這個名字可以我們自己起,一般都是依賴關系最靠上的module。在多模塊管理的springboot應用內,bootstrap往往是最不被依賴的那個module。)

子pom3最后參與構建,而且SpringBoot打包插件一般打的就是這個module。所以最終進入到SpringBoot打包產物的有A-2,B-2,E-2,F-2和D-1。因為A-2和B-2相比于其他幾個相同節點更靠近樹的主干。E-2和F-2也是同理。這個規律體感上是靠后優先了,因為靠后的樹天然更加靠近主干。

四 仲裁機制在Maven源碼中的實現

以Maven的3.6.3版本的源碼進行分析,我們嘗試分析Maven中對依賴處理的幾處原則,方能從源碼的層面上正向的證明仲裁機制的準確性。另外從源碼上也可以看出一些Maven上的機制為什么是這樣,而不是單單的他的機制是什么樣。因為筆者相信,任何機制都無法保證與時俱進下的先進性,所以筆者認為上文中提到的所有的仲裁機制有一天可能會發生變化,這些結論并非最重要,而是如何調研這些結論更為重要!

Maven是如何實現出繼承并且相同屬性子覆蓋父的

Maven中有2條非常重要的主線。一個是依賴,另一個就是繼承。Maven在源碼中實現繼承大體如下。在下圖中使用readParent進行對父親的模型獲取之后,便讓自己陷入這個循環中。唯一可以出去這個循環的方式就是追不到父親為止。并且把每次取到模型數據放到linega這個對象當中。下圖中最下面的assembleInheritance我們看他消費了linega這個對象,目的就是完成真實的繼承和覆蓋。

在assembleInheritance中我們會發現一個很有意思的現象,lingage是倒著進行遍歷,并且是從倒數第二個元素開始,這正是上文中我們提到了的Maven的一個設計哲學。Maven認為這個世界上所有的pom文件都存在一個父親,類似Java的Object。這里便是對這個哲學處理的一個淺邏輯。

另外Maven自上而下的去遍歷,更加方便自己去實現相同的元素子覆蓋父的能力,這也是筆者認為在編碼上的一個小心思。

Reactor反應堆在源碼中的實現

上文中我們還提到了一個非常重要的概念,就是反應堆。反應堆直接決定了各個子pom是如何決定構建順序的。在Maven的源碼中,他是在getProjectsForMavenReactor函數中進行實現的。并且我們從下圖中也可以看到,Maven的反應堆是不能解決循環依賴的,他直接捕獲了這種異常!

真正實現反應堆算法的是在ProjectSorter的構造函數中通過Dag進行實現的。Dag(有向無環圖)和廣度優先搜索是解決依賴場景是一個很好的方式。

在有向無環圖中通過每次挑選出入度為0的節點,再刪除該節點和此節點的相鄰邊,不斷重復上述步驟。就可以高效率的計算出DAG上的所有節點的依賴順序,Maven也正是用到了這個思路。

從這個源碼的視角也可以解釋為什么Maven必須要保證每一個子pom之前不能出現循環依賴。

同一個Pom文件內dependency 后聲明的優先的實現

在處理Dependencies時,Maven并沒有對此進行特殊處理,是直接使用的Map的方式進行覆蓋的。關于這里為什么這么設計,筆者并不清楚。筆者曾一度猜測這么設計是為了讓開發同學更好的編寫,因為靠后優先往往符合大部分人的編碼習慣。但是在這里我們看到了作者的一行注釋,意思大概是說,這樣設計是為了向后兼容Maven2.x,因為Maven2.x 是不會去校驗一個文件是否只存在一個同GA的唯一依賴。所以后面的maven的版本應該也是延續了這種風格。

當循環進行處理到1.2.25的時候,依然進行對normalized這個map進行put操作導致了 key值相同的情況下的覆蓋。

五 安全視角應如何避免間接依賴

分析

作為安全同學,筆者更希望的是針對這種多module的Maven項目可以梳理出一個經驗,怎樣去避免間接依賴的問題。

經過上面的分析,我們可以得出3條結論:

1.子pom聲明版本在安全視角是非常危險的,子pom不應該顯示聲明版本。

由于子pom會繼承主pom的元素,并且在繼承的時候會出現覆蓋的場景。那么針對CE或者SpringBoot打包時,有可能出現子pom的build的順序位置天然非常有優勢,容易造成子pom的版本進入最終的打包產物。

2.主POM的dependencyManagent可以管控到 間接依賴 和 不顯示聲明version的直接依賴。

3.主POM的dependencies不能出現危險版本。否則子pom天然的繼承了這個危險版本參與打包。

結論

以上幾條同時滿足,便可以解決間接依賴的問題。

即:

針對SpringBoot而言,子pom不應該顯示聲明版本,主Pom的dependencyManagent應該管控安全版本的依賴,并且主pom不能出現危險版本。(主Pom dependencies強行寫上安全版本更佳,這樣可以避免掉依賴的父親里存在殘留的不安全的依賴)

六 最后

Maven的源碼地址

https://archive.Apache.org/dist/maven/maven-3/

我是怎么分析的

本人在本地針對SpringBoot,做多輪測試。在根目錄下執行mvn clean package即可!

另外就是嘗試在源碼中找到這里的實現,這樣更能加深理解!

常用的分析命令

0. mvn clean package -DSkipTest 直接進行打包,進行結果分析

1. mvn dependency:tree 會把整個的maven的樹形結構輸出

2.mvn help:effective-pom -Dverbose 這個命令輸出的信息更加完整,輸出的是effectivepom

分享到:
標簽:Maven
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定