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