一、背景
在日常的開發(fā)中为狸,排查問題是一個合格Java開發(fā)者的的基本能力歼郭。對于常見的NullPointerException,NoClassDefFoundError等問題一般通過google直接就能找到答案辐棒。
不過還有一些異常情況不是那么直觀病曾,google一般搜不到有效的信息,就需要深入研究排查漾根。新人遇到這類問題泰涂,往往一臉懵逼,不知如何下手辐怕,請教高手逼蒙,高手如果只是簡單指導(dǎo)一個方向,新人踩過幾個坑沒解決后會更加沮喪寄疏。甚至懷疑自己遇到一個神秘的無法解決的bug是牢。有經(jīng)驗的開發(fā)往往也不能一眼就看出問題的修復(fù)方案,但是排查問題多了會有大概的思路陕截,需要沉下心來不斷的踩坑驳棱、爬坑,最終才能定位解決問題农曲。
本文以Maven構(gòu)建工具為例社搅,從原理、思路朋蔫、工具罚渐、實踐幾方面分享Java中復(fù)雜jar包依賴問題排查經(jīng)驗。
二驯妄、Maven依賴問題的表現(xiàn)形式
1荷并、老項目
一般是新引入了一個jar包,導(dǎo)致項目啟動不起來
原來的服務(wù)好好的青扔,新的改動不涉及這塊源织,但是新改動代碼后突然報錯了
2、新項目
日志問題:眾所周知微猖,jar生態(tài)的日志包及其混亂谈息,同一套日志組件,不同版本之間也可能會帶來問題
組件整合問題:各組件底層依賴jar包版本不一致凛剥,導(dǎo)致問題侠仇。最麻煩的是不兼容的依賴。
三、問題排查策略
1逻炊、基礎(chǔ)知識:Maven依賴傳遞策略
compile (編譯范圍):默認(rèn)策略互亮;編譯,測試余素,運(yùn)行豹休,打包都能用
provided (已提供范圍):編譯,測試可用桨吊,不會打包到package包中威根。大部分情況下需要底層環(huán)境提供,比如tomcat服務(wù)中已經(jīng)內(nèi)置了servlet包视乐,代碼寫代碼時需要調(diào)用servlet的類
runtime (運(yùn)行時范圍):只有運(yùn)行時才可用洛搀,但在編譯的時候不需要。比如炊林,你可能在編譯的時候只需要JDBC API JAR姥卢,而只有在運(yùn)行的時候才需要JDBC驅(qū)動實現(xiàn)卷要。
test (測試范圍):test范圍依賴在編譯和運(yùn)行時都不需要渣聚,它們只有在測試編譯和測試運(yùn)行階段可用。
system (系統(tǒng)范圍):system范圍依賴與provided 類似僧叉,但是你必須顯式的提供一個對于本地系統(tǒng)中JAR 文件的路徑奕枝。適合maven中央倉庫中不存在jar包引用。
2瓶堕、基礎(chǔ)知識:Maven依賴樹的解析規(guī)則
(1)深入優(yōu)先原則
深度優(yōu)先遍歷依賴隘道,并緩存節(jié)點剪枝。比如下圖:
A→B→D→E/F
A→C→D
在第二步A→C→D時郎笆,由于節(jié)點D已經(jīng)被緩存谭梗,所以會立即返回,不必再次遍歷E/F宛蚓,避免重復(fù)搜索激捏。
(2)短路徑優(yōu)先原則
比如下圖 A 通過 B 和 D 引入了 1.0 版本的 E,同時 A 通過 C 引入了 2.0 版本的 E凄吏。針對這種多個版本構(gòu)建依賴時远舅,Maven 采用短路徑優(yōu)先原則,即 A 會依賴 2.0 版本的 E痕钢。如果想引入 1.0 版本的 E图柏,需要直接在 A 的 pom 中聲明 E 的版本。
(3)依賴循環(huán)
maven中禁止有循環(huán)依賴任连。比如A 依賴了 B蚤吹,同時 B 又依賴了 A。這種循環(huán)依賴可能不會直接顯現(xiàn)随抠,但是會在一個很長的調(diào)用關(guān)系顯現(xiàn)出來
(4)Maven多模塊問題
B模塊依賴A模塊裁着,A模塊的依賴修改了余佃,需要install一下,B模塊才能感知到最新的依賴跨算。
3爆土、工具
(1)日志
開發(fā)環(huán)境,建議默認(rèn)日志級別設(shè)置為info诸蚕,對需要關(guān)注的模塊步势,建議設(shè)置為debug模式,比如當(dāng)前工程目錄背犯,正在聯(lián)調(diào)的依賴jar包坏瘩。
如果異常無法定位,第一反應(yīng)應(yīng)該是添加日志漠魏。
(2)maven命令行
添加以下有空的命令行參數(shù):
-e 出錯日志
-X 打印debug日志
-q 只打印錯誤
(3)IDEA插件:Maven Helper
安裝插件后左下角會出現(xiàn)個新標(biāo)簽頁倔矾,點擊后搜所有此模塊依賴的jar包及沖突情況
4、新項目
新項目碰到錯誤柱锹,復(fù)雜的問題哪自,先不要深入研究,先看下是否有以下情況:
新項目推薦Log4j2禁熏,代碼里統(tǒng)一用slf4j打日志
新項目推薦spring boot壤巷,它們內(nèi)部把依賴問題都修復(fù)了,可以避免很多問題
5瞧毙、從下往上遞歸根因
java的錯誤棧是從報錯的最底層逐步往上打胧华,這樣能很方便的定位秤茅,一般的問題通過此手段定位問題后即可很快修復(fù)摆马。
復(fù)雜點的異常,比如jar包沖突異常爸吮,最底層的異常一般都是基礎(chǔ)組件释漆,比如類加載器悲没、日志組件等,這種情況就要多看幾層灵汪。
6檀训、從上往下查看變動
大部分問題都是新引入依賴導(dǎo)致,從上往下查看變動享言,配合從下往上定位信息峻凫,定位出問題的組件。
四览露、案例分享
1荧琼、Tomcat項目啟動報錯
背景:新需求接入輿情SDK,測試環(huán)境測試通過,線上發(fā)布有大量機(jī)器tomcat啟動失敗命锄。
(1)查找錯誤日志
異常日志:
(2)嘗試修復(fù):第一次
通過module-info.class堰乔,和 tag in constant pool: 19分析,直觀感受是tomcat不支持java9中的模塊策略
機(jī)器上使用的是tomcat6, 還不支持java9的新模塊策略脐恩,遂升級tomcat6到tomcat8镐侯,啟動錯誤還有
(3)嘗試修復(fù):第二次
此異常是lombok-1.18.10引起的,這個版本為了支持java9添加了module-info.class驶冒,而lombok包是java源代碼增強(qiáng)的一個工具苟翻,運(yùn)行時,并不需要骗污,遂把依賴的scope改成provided崇猫。
重新發(fā)布,又報了以下異常:
(4)嘗試修復(fù):第三次
報錯Not running on Jetty需忿,而我們的服務(wù)是運(yùn)行在tomcat上诅炉,配合從上到下排查的思路,在新引入的maven依賴項打開pom.xml屋厘,查看里面的依賴項還有parent里的依賴項涕烧,找到了以下元兇:parent里會依賴***-boot-starter-web,而這里默認(rèn)使用jetty作為引擎擅这,遂排除掉澈魄,并驗證業(yè)務(wù)邏輯是否正常景鼠。
重新發(fā)布仲翎,問題解決。
(5)經(jīng)驗總結(jié)
- 排查jar包也有風(fēng)險铛漓,很可能導(dǎo)致業(yè)務(wù)功能受影響溯香,排除后要經(jīng)過充分驗證
2、新加依賴后服務(wù)啟動失敗
背景:新需求接入用戶個性化api jar包浓恶,使用MDP的thrift注解注冊了 thrift client bean玫坛,引入后項目啟動失敗。
(1)異常日志
[圖片上傳中...(image-1c74a8-1637415298229-3)]
(2)定位問題
從堆棧初步看是以下包不兼容導(dǎo)致的:swift-codec或guava包晰,還有mtthrift湿镀。剛開始重點排查了swift-codec或guava的兼容問題,將其降級和新引入jar包里的版本一致伐憾,啟動錯誤還在
剛開始沒往mtthrift方面想勉痴,因為mtthrift是美團(tuán)的基礎(chǔ)組件,一般情況下不會出現(xiàn)兼容的問題树肃,查看官方wiki也沒說有兼容問題
(3)從上到下排查
查看新引入的依賴蒸矛,打開pom.xml,發(fā)現(xiàn)里面引入了MDP 1.5.5版本,而我們的項目的是最新的MDP 1.6.6.1, 初步判斷是MDP包沖突雏掠,所以就把新包的MDP包排除掉
重新啟動斩祭,錯誤依舊
(4)排查版本不一致的依賴
到現(xiàn)在基本可以認(rèn)為是mtthrift升級導(dǎo)致的不兼容問題。我們引入的jar包只需要其中了api定義乡话,并不需要依賴的依賴摧玫,所以把所有的依賴都排除掉。重新引入和依賴包一致的mtthrift版本绑青。重新啟動席赂,問題解決。
[圖片上傳中...(image-43177d-1637415298229-1)]
(5)總結(jié)
這個問題解決過程中走了很多彎路时迫,一般我們的認(rèn)為基礎(chǔ)組件升級會兼容老版本颅停,還有一點加深了這個認(rèn)識:項目里引入的其他的thrift-client包,并沒有出現(xiàn)問題掠拳。
但不幸的是癞揉,這個正好遇到了此類問題。從最終的解決方案看溺欧,應(yīng)該是新引入的jar包編譯的問題喊熟。
五、對外發(fā)布jar包規(guī)范
對外提供的SDK或API包姐刁,不要包含不必要的額外依賴
嚴(yán)禁包含有依賴的parent的jar包發(fā)布
必須要依賴的芥牌,可以依賴通用包比如common-langs、guava包等聂使,最好設(shè)置provided壁拉。
六、總結(jié)
Maven包依賴問題是開發(fā)中的常見問題柏靶,如果不熟悉排查方案弃理,會浪費(fèi)大量時間。本文從工具屎蜓、方法論痘昌、實踐方面做了一些思考,希望對大家日常開發(fā)有幫助炬转。
作者簡介:木小豐辆苔,美團(tuán)Java高級工程師,關(guān)注架構(gòu)扼劈、軟件工程驻啤、全棧等,不定期分享軟件研發(fā)過程中的實踐测僵、思考街佑。