源码实战 | 本地可跑,上线就崩?慌了!
前言
上周一好友向我反饋一個問題,他們項目在本地是可以跑的,但是在線上環(huán)境,就報錯.報錯日志如下:
Could?not?find?result?map?cn.mycs.server.persistence.dao.UserMapper.BaseResultMap說實話,我每天這么忙,看到這種直接丟個異常出來的根本不想理.但是他一句話徹底改變了我的想法.
首先出現(xiàn)了這幾個關(guān)鍵詞.
-
無法解決的bug
之前肥朝反復(fù)強(qiáng)調(diào),我們看源碼,是為了解決問題,而不是簡單為了面試裝裝逼,如果搜索引擎隨便搜索第一頁都能解決,那還看源碼真的是風(fēng)騷走位完美避開了最高效的解決問題方式
-
特定環(huán)境出現(xiàn)
從聊天記錄中可以看出,該問題還受到環(huán)境的條件限制,不方便模擬,最關(guān)鍵是肥朝還不能直接連上他們公司的環(huán)境去幫他看問題.
事出反常必有妖,加上他是肥朝公眾號粉絲(劃重點),那就只能來一波捉妖記了!
望聞問切
其實很多人寫了幾年代碼之后都常常感嘆,寫代碼真的好容易,就是用各種框架,堆積木式編程.其實他們之所以有這樣的感嘆,主要是工作中遇到的挑戰(zhàn)還不夠多.以至于他們認(rèn)為原理、源碼這些東西純粹只是面試裝逼.
當(dāng)然會看源碼解決搜索引擎無法解決問題,還是遠(yuǎn)遠(yuǎn)不夠的.高并發(fā)下.會出現(xiàn)很多難以重現(xiàn)的問題,這個時候,必須要學(xué)會一個新的技能,就是通過日志,通過眼神編譯,靜態(tài)看源碼.
因此,我詢問得到了報錯日志如下:
從報錯日志中可以看出,這個報錯還和Mybatis plus有關(guān),但是肥朝沒用過什么Mybatis plus啊,這可如何是好?沒關(guān)系,前面都說了,靜態(tài)看源碼,眼神編譯!,于是我開始新建一個demo,引入相關(guān)的依賴.
九淺一深
將上圖的異常棧再標(biāo)記一下重點
從我標(biāo)記的三個重點加上小學(xué)簡單的英文就可以看出,在解析UserPersonalMapper.xml時,沒有找到BaseResultMap.另外一點,從我標(biāo)記中的重點中也可以看出,這個BaseResultMap是在另外一個XML,也就是UserMapper.xml中聲明的.
這個時候可能就有朋友想到,那是不是加載UserPersonalMapper.xml的時候,UserMapper.xml還沒加載導(dǎo)致的呢.導(dǎo)致無法找到UserMapper.xml定義的BaseResultMap
坦白說,這個猜測,一點毛病都沒有,非常合情合理
但是最關(guān)鍵的是,本地跑是沒問題的.那為什么我本地跑的時候,又沒有報錯,這個你又怎么解釋?
很多朋友都問到我怎么看源碼,那么我現(xiàn)在就手把手,根據(jù)僅有的線索,九淺一深直入源碼.
日志告訴我們是583行的時候報錯的(圖中已圈),然后mapperLocations也很明顯,就是我們配置我mapper集合,他就是從這里集合中遍歷出每一個mapper來進(jìn)行解析的.
那么關(guān)鍵問題來了,我們現(xiàn)在是靜態(tài)看代碼,眼神編譯,我們打不了斷點,那么這個mapper是什么時候set進(jìn)去的,set了哪些值呢?為了做到毫不保留向公眾號粉絲傳輸心路歷程,我就詳細(xì)截圖一下.
以下幾個技巧完全是IDEA的使用問題
1.查看變量在哪里被引用
2.查看方法在哪里被調(diào)用
終于,讓我們找到了核心處理邏輯
從resolveMapperLocations和PathMatchingResourcePatternResolver這兩個類名和返回值Resource[],哪怕是把單詞拆開一個一個翻譯都大概能猜出,這個是根據(jù)配置的/*.xml這種配置,找到所有的xml資源.Resource[]是數(shù)組,數(shù)組是有序的,所以這個數(shù)組中的元素(mapper)順序的順序,就能決定我們前面的猜想是不是正確的.
驗證猜想
于是我就叫該好友添加上這段日志,驗證一下猜想
然后他把能正常啟動的日志和異常的日志發(fā)出來,如下
我們發(fā)現(xiàn),果然如我們所料,這個加載的順序果然有問題,異常啟動的,UserMapper.xml在最后才加載,自然導(dǎo)致遍歷的時候最后才解析到這個xml,所以這個xml上定義的BaseResultMap不能被之前加載的使用
但是關(guān)鍵問題是,還是沒說清楚,為什么本地就沒問題.為什么本地跑加載的順序就OK了呢?
深入淺出
現(xiàn)在范圍已經(jīng)很小了,我們從前面的猜想,到驗證猜想,已經(jīng)把目標(biāo)逐步縮小,現(xiàn)在問題就只剩下一個,只要弄清楚PathMatchingResourcePatternResolver的邏輯,一切就豁然開朗了
因為線上環(huán)境,都是打成jar依賴啟動的.而本地走的是classes,所以他們走的代碼分支是不一樣的
深入思考
看問題,一定要經(jīng)過深度思考.比如這里應(yīng)該有的疑問是,為什么肥朝就知道.他們走的分支不一樣.再說男人的山盟海誓都是假的,我怎么知道肥朝說的是不是真的.
坦白說,關(guān)注肥朝最好的時間是在兩年前,其次是現(xiàn)在,如果你從源碼解析系列文章就開始就關(guān)注肥朝,練就了眼神編譯,靜態(tài)看源碼的能力,這里自然不會有這個疑問
如果不幸錯過了最好的關(guān)注時機(jī),我再給你一個猥瑣的辦法.你把PathMatchingResourcePatternResolver這個類拷貝出來,改個名字.比如FeichaoPathMatchingResourcePatternResolver.然后打上一些簡陋的日志信息,如下圖
然后你本地啟動,和jar啟動,看日志輸出.
那么,這兩個分支究竟有什么區(qū)別呢?這個本地代碼走的代碼分支,有一段很重要的邏輯
他這里會根據(jù)資源文件進(jìn)行排序,那么到底根據(jù)什么規(guī)則排序
我們拿出肥朝之前文章里介紹的常用開發(fā)工具來看一眼.
此時豁然開朗.為什么本地一直沒有重現(xiàn)?因為本地跑的時候,他走的代碼邏輯已經(jīng)把資源文件默認(rèn)按照文件名排序了,導(dǎo)致了這個UserMapper.xml一直在UserPersonalMapper.xml之前加載.
規(guī)范開發(fā),拒絕偶然成功
坦白說,其實這種兩個mapper引用的做法我認(rèn)為是非常不規(guī)范的.因為資源文件打進(jìn)jar的文件排序是非常不可控的,他可能會因為構(gòu)建工具的不同比如Maven、Gradle,甚至還會因為構(gòu)建工具的版本,比如Maven的版本,還有可能受到環(huán)境,比如window、linux等等的影響.之所以本地開發(fā)時候沒有發(fā)現(xiàn)問題,純屬是偶然成功.我們最后再來用簡單的JDK Api驗證一下問題.我們拿一個能正常啟動和報錯的jar
很明顯,這兩個jar中的資源文件順序是不一樣的.所以如果按照他的這種mapper寫法,可能會出現(xiàn)因為A環(huán)境的maven版本比較高,做了優(yōu)化等因素,默認(rèn)按照文件名順序打入jar中,但是另一個B環(huán)境,打入jar的資源是無序的.到時候問題描述可能就變成了很神秘的"一樣的代碼,A環(huán)境打包運(yùn)行一直是成功的,B環(huán)境打包偶然性運(yùn)行失敗".這就是我在團(tuán)隊經(jīng)常提到的一個名詞,偶然成功.
總結(jié)
以上是生活随笔為你收集整理的源码实战 | 本地可跑,上线就崩?慌了!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你的接口能承受高并发吗?
- 下一篇: 【斩获7枚offer,入职阿里平台事业部