多個eclipse插件導出同名且版本不同的包帶來的問題之分析過程

問題現象


    某同事中途接手爲我們的一個甲方負責維護我們公司的一個平臺。公司的平臺是基於eclipse的rcp技術開發。公司此平臺的服務器端運行在ibm jre1.4.2上,客戶端運行在sun的jre1.4.2上。部分客戶端上也有使用版本爲1.5.x的jre。
    有一次同事根據甲方需求修改了部分舊插件,其中有一個插件會導出jna的相關包,以便其它插件調用第三方動態庫時方便使用。此次同事沒有調整這塊的內容。在開發測試、SIT測試時都正常。
    投產部署到服務器後,客戶端登錄後自動更新插件到本地後,並且重啓客戶端。重啓後,部分客戶端在登錄界面上提示找不到usbkey相關信息。也就是說有一部分客戶端更新後用戶無法使用u盾登錄到系統了,但在更新前還可以使用u盾正常登錄系統。
    在更新不能正常使用u盾的客戶端的日誌中有找到下列異常日誌:
java.lang.UnsupportedClassVersionError: com/sun/jna/Native (Unsupported major.minor version 49.0)
 at java.lang.ClassLoader.defineClass0(Native Method)
 at java.lang.ClassLoader.defineClass(Unknown Source)
 at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.defineClass(DefaultClassLoader.java:160)
 at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.defineClass(ClasspathManager.java:498)
 at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findClassImpl(ClasspathManager.java:468)
 at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClassImpl(ClasspathManager.java:427)
 at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClass(ClasspathManager.java:410)
 at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.findLocalClass(DefaultClassLoader.java:188)
 at org.eclipse.osgi.framework.internal.core.BundleLoader.findLocalClass(BundleLoader.java:339)
 at org.eclipse.osgi.framework.internal.core.SingleSourcePackage.loadClass(SingleSourcePackage.java:37)
 at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(BundleLoader.java:379)
 at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(BundleLoader.java:352)
 at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:83)
 at java.lang.ClassLoader.loadClass(Unknown Source)

    也就是說,插件調用u盾時是通過jna方式調用廠商動態庫的。但是更新一個會導出jna相關包的插件(更新時只是增加其它與jna本身沒有相關的內容)後,客戶端竟然奇妙出現UnsupportedClassVersionError問題。
    查看更新包中com.sun.jna.Native類的主版本不是49,而是jre1.4支持的48。此插件中的com.sun.jna包中的類沒有版本問題。
    同事們把有問題客戶端複製到自己的機器,使用與有問題客戶端的jre版本完全一致的jre運行客戶端後調用u盾是正常的。
    遇到這種奇妙的問題又不能及時解決,只能先回切程序版本,放棄此次投產更新了。此次投產以失敗告終。


排查過程


    甲方要求我們公司派人到現場分析。我們的另一個同事在現場分析了一天,嘗試了很多方法沒有找到在引發問題的直接原因。後來,甲方給我們的壓力越來越大。公司派我到現場主導分析,以便儘快查找問題根源。
    一、重現問題現象
    慶幸的是,在甲方現場的一個新的機器上也可以反覆重現故障。
    在測試分析過程中,發現一個有趣的現象:把有問題的客戶端複製別的盤的任意目錄下都能正常調用u盾,再把客戶端複製到原來的位置也變得正常了。
    經過對比檢查,發現一個規律:客戶端運行有問題時,所在分區文件系統是FAT32,正常運行時所在分區文件系統是NTFS。
    根據這個發現,我們弄了一個文件系統類型爲FAT32的u盤。把有問題客戶端打包成壓縮包,剪切到目標u盤中,解壓後運行客戶端能重現問題。
    終於可以在我們自己的電腦上能重現問題了。這是一個好的開端。
    二、分析排查過程
    (1)我們在命令行下啓動客戶端,添加一個eclipse的啓動參數:-console。在osgi控制檯,查看了各個插件bundle情況。正常情況下,前面提到的一個更新過插件的bundleID比較小。異常情況下,前面提到的一個更新的插件的bundleID比較大。
難道在異常情況下加載的Native的來源與正常時加載的Native的來源不一樣嗎?難道這個路徑的Native類之版本號就是49嗎?由於我手頭上沒有最新和完整的平臺源碼,無法從源碼方向完整的搜索出哪些地方有jna相關類並且導出這些包。
    (2)我們在命令行啓動客戶端,使用生產的jre版本運行程序。在啓動參數中,添加了-verbose:class和-XX:+TraceClassLoading兩個參數。在標準輸出流中能顯示一個標準內庫類是從哪個jar包中加載的。遺憾的是沒有顯示公司平臺自身插件類的加載路徑。
    (3)接着,我們又使用jre1.6.32,在命令行啓動客戶端,同樣添加了兩個啓動參數-verbose:class和-XX:+TraceClassLoading兩個參數,再加一個eclipse的參數:-console。此次終於找到問題根源了。從輸出信息可以看出Native類是從bundleID爲41的classpath中加載的。通過osgi的ss命令查看到bundleID爲41的插件是一個不是此次投產的內容。解壓此插件包,發現存在一個jna.jar,而且此插件導出了com.sun.jna相關包。Jna.jar中Native類的類版本號就是49。故障的元兇終於找到了。其實此種客戶端是不需要這個導致故障的插件的。甲方同種另一用途的客戶端才需要這個插件。
    三、故障覆盤
    在一些沒有意料的因素影響下,eclipse安裝加載插件的順序有所變化。如果引發問題插件的bundleID比前面提到的更新的插件的bundleID小,兩個插件都導出了com.sun.jna相關包。此時,當通過import package方式依賴com.sun.jna包的並且調用u盾的插件需要調用通過Native類加載第三方動態庫時,osgi查找加載Native類時,只會使用bundleID最小的並且導出同樣類的bundle中的同名類。這樣,就使用到了類版本爲49的Native類。在加載Native類時沒有通過類版本的檢查,引發了jvm拋出unsupportedClassVersionError的錯誤。


反思總結


   (1)在開發時,一定要避免不同插件導出同名包的做法。這種做法可能會帶來意料不到的隱患。不同的插件導出同樣包,但包的版本有差異的做法帶來的風險會更大。
   (2)實際項目中需要審慎地進行平臺設計規劃、帶着敬畏心態的進行編碼開發。這點如何反覆強調都不過份,並且要篤行。
   (3)版本控制如何進行更有效呢?代碼審查如何評審更能發揮更大的價值呢?這是需要我們去更深入研究實踐的。
 

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