一個java空指針異常的解決過程
背景
上一篇講了我們從另外一個部門遷移了一個線上系統回來,遷回來是為啥呢,因為這個好幾年沒新需求的系統,突然有新需求要開發,然后我就開發唄,其實就是在某個服務里加點表,然后提供個查詢接口給app。這個服務用的架構是廠商的,不是servlet容器那一套,它技術還是很厲害,其實是c語言寫了個reactor這種類似netty的通信框架,通信協議是私有的tcp協議,然后啟動時還通過jni拉起了一個java虛擬機,通信框架就負責底層通信,并且像servlet那樣來調用上層的java類中的service方法這樣。
至于為啥廠商這么玩,我覺得無非是把底層這套通信框架打成二進制提供給我們,提高技術壁壘,防破解啥的,并且通過私有協議提升維護難度,這樣的話,就可以一直收我們維護費了。這些就不過多吐槽了,反正好多服務就是這樣被廠商綁死了。
在windows上,這個進程沒法像tomcat這類容器一樣運行起來,每次只能對java代碼部分進行junit單元測試,只能在linux上才能運行起來。
像java代碼部分,我們自己是用了spring那一套,就像下面這樣,啟動時直接new一個spring的ClassPathXmlApplicationContext,而下面的location就是application.xml這種spring配置文件的url。

由于廠商也提供了些自己的訪問數據庫的框架,之前這個服務就是用的廠商這套(類似于jdbcTemplate操作sql語句);我接手這個服務之后,感覺不太喜歡廠商那套,就還是引入mybatis的mapper這套,如下:

結果,我本地junit調試時好好的,丟到服務器上,直接運行不起來了。給我報了個空指針。

解決過程
各種懷疑
上面的圖里,看著是mybatis在創建一個什么XPathFactory的時候報錯了,然后上圖又說解析什么mapper.xml失敗了,我還在想,是文件路徑沒對嗎,結果文件路徑挺對的:

接下來,開始懷疑起了mybatis,由于接手的這個項目,素質不怎么樣,各種jar包沖突啥的,大家看我下圖就知道了,就上面報錯的堆棧里的org.apache.ibatis.parsing.XPathParser這個類,我在項目里一搜,發現同名類有兩個,為啥呢,因為下面第一個jar包是廠商的,它把mybatis的源碼封進了自己的jar包里,且沒改包名:

當然了,廠商那個jar包里不止這一個,拷貝了很多類進來,也不知道拷貝進來后改了些啥。
我這時候的想法是,看看到底加載的哪個class吧。
查看類加載情況
然后在jvm的啟動參數加了打印類加載的jvm參數: -verbose:class

注意,這個是打印到console,或者對console進行重定向到文件。
我這邊看了下,發現是對的,仔細檢查了,沒發現加載了廠商的jar包的類,看起來是對的:

看堆棧相關代碼
然后開始在本地junit調試,結果沒走到報錯的地方。
看之前那個錯誤堆棧,報錯就是在下面的jdk1.7中的XPathFactoryFinder類的220這一行:

然后,這個resource應該就是觸發空指針的,如果說resource為null,那么217行這個迭代器就有問題,通過迭代器的next獲取出來的值為null;而迭代器是在215行的createServiceFileIterator賦值的。

點進來后就傻了,有分支,不知道走了哪一條,好在天無絕人之路,此時,之前的-verbose:class參數起了作用:

通過這里分析,在加載完XPathFactoryFinder類后,不久就加載了javax.xml.xpath.XPathFactoryFinder.SingleIterator,那意思就是走了if那個分支。
但目前對怎么解決這個問題還是不清楚,無奈,先本地debug。
debug
雖然本地junit不能完全模擬linux服務器上的狀況,但是先試試吧,結果,本地調試發現:
在之前那個分支處,本地windows時,classloader不是null:

那我是否懷疑了classloader的問題呢,懷疑的不多,因為以前本地調試tomcat的時候已經遇到過,本地debug時用的classloader和最終把war包丟到tomcat里運行時用的classloader那些,確實不一樣。
在debug的過程中,對這個類的理解加深了一些,看起來,這個XPathFactoryFinder主要就是要獲取到一個XPathFactory。

先是從property中查找,property為javax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom,如果能找到對應的實現類,就用這個實現類去創建對應的XPathFactory,這塊就和java中的SPI機制類似:

接下來呢,會嘗試從配置文件獲取C:\Program Files\Java\jdk1.7.0_80\jre\lib\jaxp.properties:

如果還是沒有,才會走到我們報錯的地方,即用spi機制,問題是我們因為classloader為null,走不到下圖這里:

開啟debug日志
在debug過程中,發現很多debug日志:

這個日志如何開啟呢?

我們設置了下,如下:

這下,通過日志,我們更加明確了程序運行的軌跡。
設置property解決bug
接下來,就是怎么解決了,在debug過程中,我們發現,本地windows的話,默認最終的實現類就是com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl,其實就是jdk自帶的:


那我們這里也就好說了,設置下property吧,解決問題就行了,至于為啥classloader為null,就沒有繼續深究了:

這次就運行正常了:

總結
bug記錄下還是挺好,因為很快就忘了,后來要寫上線變更文檔,才想起來這么個事。

浙公網安備 33010602011771號