通過IDEA快速定位和排除依賴衝突
前言
我們程式設計師在開發的時候經常會遇到各種各樣的 BUG 問題,其中大部分是業務邏輯異常,還有一些是程式碼書寫不規範造成的異常例如:NullPointException(NPE),IndexOutOfBoundsException 等等,其實這些我們都好定位和修復。但是還有一些執行時異常定位起來是特別頭疼的,那就是 jar 包衝突引起的異常。
一般程式在執行時發生類似於 java.lang.ClassNotFoundException,Method not found: '......',或者莫名其妙的異常資訊,這種情況一般很大可能就是 jar包依賴衝突的問題引起的了。
至於為什麼會發生 jar包依賴衝突?這種問題大致可以歸納為如下幾個原因:
- 版本不匹配,高版本依賴了低版本,或者低版本依賴了高版本。例如引入第三方庫,但是第三方庫基於的是 JDK7,而你們專案使用的是JDK8。
- 重複引入不同版本jar包,造成使用錯誤。很多時候我們引入第三方輪子,它們依賴引入某個基礎工具使用的是 v 1.0 的 jar,但是我們專案中自己也引入了該 jar,但是版本是 v 2.3,這時就會造成專案中使用同一個元件但是依賴了兩個不同版本的jar,衝突就會發生。
可以看到,其實總的來說 jar 包衝突的主要原因就是依賴的版本衝突。
異常發生
專案中需要匯出報表,技術選型的時候,一般是選用 Apache POI,但是 POI 的使用方式比較基礎,開發量大,容易出現記憶體溢位的問題。
考慮到阿里開源了一套解析和生成Excel的工具 - EasyExcel,具有避免記憶體溢位OOM的情況發生,而且使用方便簡單,所以就將它引入到了我們的專案中,具體的使用版本是 1.0.2。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.0.2</version>
</dependency>
而另一個模組需要使用 POI 的將 Word 轉成 PDF 的功能,所以同時又引入瞭如下 POI 的依賴:
<!-- poi utils -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
我們從 Maven Repository 可以發現,阿里 EasyExcel 1.0.2 依賴的 POI 也是 3.15,所以照理說應該是沒問題的。
但是在介面除錯的時候還是出問題了,而且異常資訊很奇怪,不是看一眼就能知道問題原因的並解決的。
Caused by: java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.getXmlStandalone()Z
at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.setDocumentInfo(DOM2TO.java:377)
at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:131)
at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:98)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:693)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:737)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:351)
at org.apache.poi.openxml4j.opc.StreamHelper.saveXmlInStream(StreamHelper.java:80)
at org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller.marshallRelationshipPart(ZipPartMarshaller.java:181)
at org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:560)
at org.apache.poi.openxml4j.opc.OPCPackage.save(OPCPackage.java:1557)
at org.apache.poi.POIXMLDocument.write(POIXMLDocument.java:248)
at org.apache.poi.xssf.streaming.SXSSFWorkbook.write(SXSSFWorkbook.java:941)
at com.alibaba.excel.write.ExcelBuilderImpl.finish(ExcelBuilderImpl.java:64)
at com.alibaba.excel.ExcelWriter.finish(ExcelWriter.java:95)
at com.pingan.haofang.creams.common.utils.ExcelUtil.writeExcel(ExcelUtil.java:71)
......
... 65 common frames omitted
提取關鍵資訊,可以看到錯誤型別 java.lang.AbstractMethodError
,這個錯誤型別望名知義:抽象方法錯誤。這種型別的錯誤和我們上面說的 ClassNotFoundException 類似,很大可能就是 Jar包依賴衝突所導致的。
異常定位
那我們來定位下是哪個 jar 包衝突了,只需要將衝突的 jar 包排除掉,留下正確的就可以了。
我們可以看到錯誤型別是 java.lang.AbstractMethodError,錯誤型別後面是具體的錯誤資訊描述 :org.apache.xerces.dom.DocumentImpl.getXmlStandalone()Z,意思是在包 org.apache.xerces.dom 下的類DocumentImpl
它的方法getXmlStandalone()
調用出現了錯誤。
那麼具體是誰在呼叫呢?我們在異常資訊的緊密下一行可以看到如下這一行程式碼:
at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.setDocumentInfo(DOM2TO.java:377)
在包路徑 com.sun.org.apache.xalan.internal.xsltc.trax 下,DOM2TO
類程式碼的的第377行,有個setDocumentInfo
方法,我們滑鼠左鍵點進去,在該行加個 Debug 斷點。
我們發現這個 DOM2TO 類是 JDK1.8中 rt.jar 包裡面的,具體類路徑如下:
通過斷點除錯得知,這個 document 物件是 DocumentImpl 例項,
這個DocumentImpl 的真實路徑也是 JDK1.8中 rt.jar 包裡面的,它是 CoreDocumentImpl 的子類,CoreDocumentImpl 是介面Document 的實現類。
package com.sun.org.apache.xerces.internal.dom;
public class DocumentImpl
extends CoreDocumentImpl
implements DocumentTraversal, DocumentEvent, DocumentRange {
......
}
CoreDocumentImpl
package com.sun.org.apache.xerces.internal.dom;
public class CoreDocumentImpl
extends ParentNode implements Document {
......
}
我們在 CoreDocumentImpl 類中第983行發現了getXmlStandalone
方法。
這時報錯原因赤條條的擺在我們面前了,顯而易見,DOM2TO類中 setDocumentInfo 方法的引數 Document 是屬於 JDK1.8 中 rt.jar 包下類路徑 com.sun.org.apache.xerces.internal.dom 下的實現類 DocumentImpl。而我們報錯的資訊提示中是:
Caused by: java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.getXmlStandalone()Z
這個 org.apache.xerces.dom.DocumentImpl 明顯不屬於我們 JDK1.8 的 rt.jar 包,而且也沒有 getXmlStandalone 這個方法。
所以得知,我的專案中 jar 包依賴衝突了,我們只需要排除掉 org.apache.xerces.dom.DocumentImpl 所屬的 jar 包就可以了。如何排除呢?
排除衝突
我們在 IDEA 中雙擊 Shift 鍵,輸入 DocumentImpl,得到如下結果:
可以發現,這裡有兩個 CoreDocumentImpl,一個是我們的 JDK1.8的,一個是屬於 xerce的,而且確實在依賴的 maven jar 包中發現了 xercesImpl-2.4.0.jar,這個 jar包就是需要排除的 jar包。
發現了衝突的 jar包,我全域性搜尋關鍵字 xerces,並沒有發現哪一個 pom 中有依賴的程式碼,所以很可能是其他的 jar 包傳遞依賴進來的。
我們藉助 IDEA 的 maven 工具,在 maven 欄右鍵專案模組,選擇 show Dependencies
或 Ctrl + Shift + Alt + U
,這時候會展示當前模組的 jar 包依賴圖,如下:
雖然這裡展示了很多衝突的jar包,其中紅線連線的就是衝突的jar 包,但是我們 Ctrl + F
查詢 xerces 還是沒有結果。
所以我們需要額外的方式來解決,這時我想到了 IDEA 有個外掛 Maven Helper
,具體的外掛下載可以參考前面的內容,下載好外掛後,我們開啟 pom.xml 檔案,在pom.xml 檔案的左下方有個 Dependency Analyzer
,我們點選之後顯示如下:
- Conflicts:展示所有衝突。
- All Dependencies as List:以列表的方式展示所有依賴。
- All Dependencies as Tree:以樹形的方式展示所有依賴。
我們輸入 xerces,選擇以樹形展示所有依賴,得到如下的資訊顯示。
清晰明瞭,原來這個罪魁禍首是被 file-web-sdk 帶進來的,我們右鍵選擇 Jump To Source
或者 F4 定位到這個 jar 在 pom.xml 的依賴引入位置,如下圖所示,我們通過 exclusion 標籤排除 xercesImpl 的引入即可。
<dependency>
<groupId>com.xx.xx.gov.fileservice</groupId>
<artifactId>file-web-sdk</artifactId>
<exclusions>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
</exclusions>
</dependency>
再次啟動專案,測試介面發現功能正常了,整個排查過程也就結束了,IDEA的功能還是很強大的。
總結
很多時候的 jar 包衝突,有些是我們很容易排除,例如在pom.xml 中我們就可以發現一些重複引入,但是版本不相同的依賴。還有一些是其他依賴傳遞依賴進來的,我們在 pom.xml 檔案中不能很直觀的發現,這時候我們藉助工具可以發現這種衝突的依賴。
但是還有一些是更隱祕的衝突,就像本文中描述的依賴衝突,這時候我們需要分析異常資訊,並定位衝突的原因和找到具體衝突的依賴引入,最後將它排除就可以了。
本文比較詳細的介紹了異常的分析和衝突的定位,以及最後的排除。類似的依賴衝突基本都可以參考上述的方式進行排查,希望通過本篇文章對大家解決專案中依賴衝突有所幫助。