gson線上環境解析日期時報錯JsonSyntaxException

遇到問題:

問題發生在新功能上線的那一天,所有的測試工作都測試完了,測試還挺順利,比以往結束的早很多,正高高興興的聊着今天可以早回家了。

正高興着往ST環境部署,部署上去前端一測試卻返回了服務器異常,服務器異常信息是這樣的:

這裏寫圖片描述
這裏寫圖片描述
是用了gson解析json串,解析過程中出現了JsonSyntaxException,也給出了待解析的數據值,是一個常見的日期字符串“2018-03-14 00:00:00”。

這時候第一反應是,測試覆蓋不夠,沒有測試到這種情況?或者是測試偷懶了,每次都用的假數據,沒有完整走完測試路徑?

雖然是第一想法,但是,作爲有經驗的開發,我們應該相信測試的專業度,以及測試最基本的功能測試,況且是正向流程的測試,是不會出現問題的。在遇到問題時,我們應該首先選擇相信同事。

這種情況,還有另外一種可能性就是測試環境和線上環境不一樣。

應急處理:

從上述兩個問題出發,一個是確認同事真的走了完整的測試流程;另一個是確認測試和ST環境有什麼不同。

第一個問題,從提測開始的請求日誌進行了逐個的查看,發現測試的最後兩天確實請求了同樣的接口,並且返回了正確的數據,說明這個問題不是測試的鍋,而是開發自己的鍋。

經過百度和google搜索,發現有不少人遇到了同樣的異常,問題的原因是因爲服務器的Locale不一樣導致的,也就是說gson解析日期的方法是會受到Locale影響的。

但是對比測試服務器和線上服務器的Locale發現是一樣的,那麼想從根本解決問題就變的比較難以下手。

爲了趕上上線的節點,我們選擇了把日期解析成字符串類型,然後在返給前端之前再使用joda的DateTimeFormatter進行一次日期格式的解析。迅速修改,迅速上線。

追尋原因:

做爲非算法類工程師,唯一能夠迅速提升的方法就是:抓住問題,不斷深挖,找到問題的原因,解決問題。所以,放過這個問題,絕對是巨大的損失。

打開gson的源代碼,找到拋出異常的位置:
這裏寫圖片描述

可以看到拋出JsonSyntaxException是由於ISO8601Utils.parse方法內部出現的異常。再進入到parse方法中,發現它的功能很強大,但是可解析的日期格式卻不包含我們的日期字符串樣式:
這裏寫圖片描述

ISO 8601是國際標準化組織提出的日期和時間的國際標準表示方法。從gson中的parse方法來看,大致邏輯是這樣的:
1.如果日期值只有年月日(yyyy-MM-dd或者yyyyMMdd),直接返回年月日對應的Date對象;
2.如果日期字符串解析完年月日之後,還有多餘的字符,那麼按照有time zone進行解析,並獲取time zone;

問題就出在第二點上,我們的日期格式除了年月日字符之外還有時分秒字符,但是卻沒有timezone標記。從源代碼來看,我們的日期字符串不可能被成功解析,也就是測試環境也應該出異常才合理。

繼續,我們做了一個做簡單的示例程序,只有一個main方法,使用gson對我們的日期字符串”{“dt”:”2018-03-14 00:00:00”}”進行解析。

結果是:在臺式開發機上確實沒有異常,但是放到服務器上運行就會報異常。有了明確的差異,我們就比較容易定位問題了!

在臺式開發機上debug跟蹤代碼,開發機上沒有運行錯誤的代碼行,而是執行了parse中的前半部分代碼,我們再回顧一下parse中的代碼行:
這裏寫圖片描述

也就是開發機上執行的代碼行是dateFormat.parse(json),說明dateFormats中有能夠解析我們的日期格式的DateFormat類型。再跟蹤一下dateFormats的初始化的代碼:

這裏寫圖片描述

這裏默認初始化了一個Locale.US的dateFormat,初始化了一個默認非US的Locale,還有一個Java90rLater的。

當在開發機上運行的時候,會創建一個Locale=zh_CN.UTF-8的DateFormat是這個解析類,解析了我們的日期字符串“2018-03-14 00:00:00”。

我們的ST環境服務器上採用的是en_US.UTF-8,jdk1.7,所以只要一個Locale.US的日期解析器,他不能解析“2018-03-14 00:00:00”格式的日期字符串。

所以會執行ISO8601.parse去解析,代碼邏輯定位清楚了,問題原因就是ST環境服務器上的locale不是中文,所以解析不了“2018-03-14 00:00:00”樣式的日期字符串。

然而,我們的測試環境也是en_US.UTF-8的Locale,但爲什麼測試過程中沒有出錯呢?

原來,測試環境部署的人用的是Xterm,ST環境部署的人用的是secureCRT,登錄到同一臺機器上之後,當前shell的Locale是不一樣的,所以出現了這種詭異的情況。

好了,解決方案還是保留我們應急的解決方案,也就是gson解析成String日期字符串,再用joda的DateTimeFormatter解析成Date類型。因爲這種方案不會受到服務器環境的影響。

當然,你也可以像這篇博客那樣去改服務器的默認Locale。只是這樣的話,仍然存在風險,比如,其他人用的shell客戶端登錄之後,當前shell默認成en_US就會有問題了!

同類對比:

那麼,jackson有沒有同樣的問題呢?jackson解析日期也需要指定特定的解析器才行,如果用默認的解析器,也會遇到類似無法解析的情況,比如這篇博客寫到的案例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章