麻了! 被 jar 包沖突坑哭了!
一杯茶一包煙夹厌,一行代碼寫一天
聽說這個是編程大佬的行為準則豹爹。而我沒有茶沒有煙,但是一行代碼寫了一天矛纹,(算起來可能不止一天)臂聋。
大家好,我是janker或南。今天來聊下關(guān)于jar包沖突的事兒孩等。
Part 1
最近接了一個產(chǎn)品需求:
永哥,最近業(yè)務(wù)方提出了一個訴求采够,就是讓我們把某某功能開放給他們肄方,入口參數(shù)就只需要添加一個字段,我都調(diào)研過了蹬癌,我們系統(tǒng)都是支持的权她。
一看到這樣的話術(shù),作為接過很多一句話產(chǎn)品需求的爬坑程序員逝薪,我建議你們不要亂答應(yīng)隅要。因為很有可能加一個字段,你加了一天董济,并且看不到產(chǎn)出步清。但是耳根子很軟的永哥忍不住接了。
一般這種需求感局,都是要升級jar包的尼啡,加字段三板斧暂衡,改依賴版本、加字段崖瞭、發(fā)布狂巢。帶上測試環(huán)境部署的時間,目測15分鐘就差不多了书聚,自測一下唧领,20分鐘怎么也夠了。自信如我雌续,如風隨性斩个。
`but` 奈何部署了`20`分鐘,容器重啟了好幾次一直不成功驯杜,熟悉的味道受啥,記得上次出現(xiàn)這個情況還是在上次,問題不大鸽心,看下錯誤日志滚局,沒有日志、只有一個簡單的`Main`函數(shù)日志顽频,然后就沒然后了藤肢。
一般出現(xiàn)這種情況的,多半是lib包依賴的時候糯景,有沖突嘁圈。為了驗證我的猜想,我部署了一下mastar
分支蟀淮,竟然也啟動不起來最住,what
?是誰動了我的 jar
包怠惶,一邊逼逼賴賴温学,一邊繼續(xù)修bug。思來想去甚疟,只有一種可能,會影響到master
分支的部署逃延,那就是我們引用了快照包览妖,因為快照包在我們上線 master
的時候可能是好的,但是我們在發(fā)布到測試環(huán)境的時候快照包做了更改揽祥,導致啟動不了讽膏。到了這里只有一招可以用了,就是對比打包lib
文件夾下的jar包拄丰,多出來的就是罪魁禍首府树。最終俐末,搞了個jar
包對比腳本,發(fā)現(xiàn)新的包里多出來了 xxx.jar
奄侠,我們只知道多了 xxx.jar
怎么知道是哪個依賴引起的卓箫,辦法有兩種:1. 反解jar
包,找到MANIFEST.MF
里面有相應(yīng)的包信息垄潮,然后結(jié)合 class
很容易就知道哪個依賴包影響的 2. 根據(jù)jar
包名烹卒,見名知意猜測一下(針對常見的jar包完全是ok的)。找出相應(yīng)的依賴弯洗,排掉就好了旅急。我以為快快樂樂的啟動就好了呐舔,沒想到容器啟動了辐啄,但是沒有日志掸刊。只是沒日志宵统,又不是不能用干旧。
短暫總結(jié)下前面的脈絡(luò):
Part 2
既然沒有日志蹦锋,咱們就看看jar包列表對比中宋距,差異部分有沒有跟log包相關(guān)的jar
裆装,果不其然秋泳,在新的jar包列表中多了 logback-classic
這個jar
潦闲,見名知意順手我就把 logback-classic
這個包給排掉了,這下子日志大概好像能顯示了吧迫皱。
run 啟動直接連日志都木得了歉闰,不過我絲毫不慌,從啟動腳本中copy出啟動命令卓起,登錄上機器 jar -server
啟動一下和敬,我到底看下是什么要么鬼怪。java.lang.ClassNotFoundException
戏阅,ch/qos/logback/classic/Level
這不是我剛排查的包里的類嗎昼弟?我陷入了兩難,不排除的話沒日志奕筐、排除了的話啟動不起來舱痘。我還在慌亂中,師兄也沒解決我的問題离赫,緩緩心情給它過個周末吧芭逝,明天再整他,萬一他自己好了呢(雖然知道他一定不會好)渊胸。
截止到發(fā)文前旬盯,我終于解決了,本質(zhì)原因是使用的腳手架封裝了logback
(基于logback
包二次開發(fā)),引入logback
兩者天然是水火不容胖翰,既然原生logback
包刪除不了接剩,打不過就加入(KD語錄)
,直接把log
包直接替換掉萨咳,加上logback
配置(logback.xml
)懊缺,完美解決。
What?
在實際開發(fā)過程中某弦,我們往往會引入一些其他服務(wù)的 API
包桐汤,調(diào)用API
包中的 RPC
服務(wù)來達到調(diào)用別人服務(wù)的目的。在我們的想入引入別人的 jar
包時靶壮,總是會遇到基礎(chǔ)能力總是會遇到相關(guān)基礎(chǔ)能力jar
包的版本沖突怔毛,又或者權(quán)限定類名沖突,在對方?jīng)]有自定義類加載器的時候腾降,我們是需要解決這些沖突問題的拣度,不然再項目運行時就會發(fā)生找不到類或者找不到具體的方法。常見的異常有兩種:
-
java.lang.NoSuchMethodError
螃壤; -
java.lang.ClassNotFoundException
抗果;
Why?
沖突的來源源于類加載器沒有加載到合適的類奸晴,類丟了冤馏,或者方法丟了。
舉個例子:
比如我們使用的User
對象寄啼,a逮光、b
包的依賴我們都引入了,在默認的類加載器加載的時候墩划,因為我們是按照權(quán)限定名來做唯一標識的涕刚,如果我們在程序中使用的是a
中的User.class
,但是加載的時候加載的是b
中的User.class
乙帮,當我們程序中使用的方法在B中沒有時杜漠,就會出現(xiàn)java.lang.NoSuchMethodError
。
還有一種情況:
我們引入jar包的時候一般都是靠groupId
和artifactId
確定一個依賴包察净,如下圖驾茴,a 和 b 的 groupId
和 artifactId
都一致,只是版本不同氢卡,一般情況下Maven
打包的時候就會把高版本的給打進去沟涨,如果a為高版本,但是程序中引用了 b 中的User.class
運行時就會出現(xiàn) java.lang.ClassNotFoundException
异吻。
How?
我們已經(jīng)知道 jar 包沖突是什么?并且知道是什么引起 jar 包沖突了诀浪,接下來我們看一下如何去解決 jar 包沖突棋返。
確定沖突依賴包
啟動時我們已經(jīng)看到,java.lang.ClassNotFoundException
雷猪,ch/qos/logback/classic/Level
這樣的異常信息睛竣,在IDEA 中搜索到這個類,并且定位到依賴包求摇。
<img src="https://upload-images.jianshu.io/upload_images/957716-eac071e901f4e292.png" style="zoom: 50%;" />
首先確定依賴包射沟,然后確定pom信息,pom中包含 groupId 和 artifactId 和version信息与境。有了這些信息验夯,我們就可以找到具體都那些pom依賴,把那些新進并且沒有用的依賴排除掉摔刁。
找到有問題的依賴
如果依賴較少挥转,直接把依賴樹打出來,然后根據(jù)樹形關(guān)系共屈,排掉沖突的依賴即可绑谣。
- 分析當前pom依賴樹
mvn dependency:tree
- 人肉搜索,確定沖突
當然依賴樹也是有篩選功能的拗引,并且可以直接展示沖突信息借宵。
mvn dependency:tree > dependency_tree.txt -Dverbose -Dincludes={groupId}:{artifactId }
這個就比較厲害了,直接根據(jù) groupId
和 artifactId
篩選出相關(guān)的 dependency
并在下方直接 exclusion
即可矾削,排除示例:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
<exclusions>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
</exclusions>
</dependency>
格式類似于這樣的壤玫。
總結(jié)
jar 包沖突在開發(fā)過程中是不可避免的,為了不挨罵(給小伙伴埋坑)怔软,我這里有幾個建議垦细。
- api 包定義盡量簡單純粹。(盡量不要包含一些跟api定義本身無關(guān)的包挡逼,例如:
spring
全家桶括改、log
全家桶等)。 - 經(jīng)臣铱玻看下依賴樹是否存在大量的依賴沖突嘱能,并修正。(可以安裝
meven helper
插件便于查看依賴沖突) - 日常開發(fā)使用快照包版本虱疏,上線發(fā)布前必須改為
release
版本(避免快照版本變更造成線上啟動問題惹骂、線上事故等)。
忙時做業(yè)績做瞪,閑時修內(nèi)功对粪。我是janker右冻。咱們下期見。
本文由mdnice多平臺發(fā)布