java.lang.NoSuchMethodError : org.apache.poi.poifs.filesystem.POIFSFileSystem.hasPOIFSHeader(Ljava/io/InputStream;)Z 問題解決方法


這個是在使用 POI 讀取 EXCEL 的 xlsx 檔案造成的

LOCAL.WorkBook = WorkBookFactory.create(FileStream);


使用 WorkBookFactory.create 時內部會呼叫 POIFSFileSystem.hasPOIFSHeader 函式。
 如果呼叫失敗就出現這個錯誤訊息。

所以正常來說 POIFSFileSystem.class 裡面是包含 hasPOIFSHeader 函式的。

看大部分網路上的論壇討論結果或是意見,通常是 POI 所需使用的 jar 檔案使用了不匹配/不同版本的套件導致。

但這個問發現在我另一個 jetty 版的 Railo 4.0.4.001 伺服器上卻不會發生。

為什麼?兩個伺服器版本都是一樣的啊?裡面套件也應該相同的啊?

為此,我便進行研究了一下。

1、發生這個錯誤的伺服器是以 Tomcat7 執行的伺服器,而另一個不會發生這個錯誤的是以 jetty 執行的伺服器。而兩方的版本都是當初官方公佈的 4.0.4.001 版本。

2、比對兩邊使用 POI 的 jar 檔案,雙方的檔案尺寸是相同的,檔案數量也相同。


    在此處 POI 相關的 jar 共有4個,這些都是同時隨伺服器版本一起發佈的,不是我另外安裝的,應該不會存在版本問題吧。

3、如果 POIFSFileSystem.class 不包含 hasPOIFSHeader 函式,理論上 jetty 伺服器也會出現錯誤。結果卻沒有發生這個錯誤,代表 POIFSFileSystem.class 是正確的。

但,為甚麼?

所以我用解壓縮軟體來觀察 jar 檔案的成員。



發現了奇怪的的是在 apache-poi.jar 和 apache-poi-tm-extractors.jar 裡面都出現了成員 POIFSFileSystem.class ,而且檔案尺寸完全不一樣!

而且路徑都在 org.apache.poi.poifs.filesystem 下,我猜這就是貓膩了。

因為在這種情況下只會有一個成員會被載入成功,至於是哪一個先被載入要看伺服器核心的 javaloader 處理規則。

因此,我把這兩個 jar 檔案分別解開,然後看看這兩個 POIFSFileSystem.class 是不是都包含有 hasPOIFSHeader 函式。


我利用 HEX 編輯器檢查這兩個 Class 是否包含了 hasPOIFSHeader 文字



啊啊啊,
很明顯這個 apache-poi-tm-extractors.jar 裡面沒有 hasPOIFSHeader 文字,也代表沒有函式存在。

既然如此,代表 javaloader 在 jetty 版本一定是先載入 apache-poi.jar 才能正常工作,而 Tomcat7 則是先載入了  apache-poi-tm-extractors.jar 。

至於有沒有辦法變更 javaloader 的順序呢?這個很難說。

因為我有做了下面的實驗:

在 jetty 伺服器下,我把  apache-poi-tm-extractors.jar 更改名稱  apache-o-poi-tm-extractors.jar 讓它的檔案名稱正排序往前:


此時,啟動 jetty 服務後,再度執行 xlsx 檔案讀取就發生錯誤了,這代表 jetty 的 javaloader 是依照檔案名稱的正序來載入的,當載入重複的類別時,第二次的類別實例就會失敗。

但是,依照這種方式使用在 Tomcat7 伺服器時卻是無效的,既然不是名稱順序,那也很難猜出是用甚麼載入順序了方式。

所以,對於 Tomcat7 的修正方式,我是採用把  apache-poi-tm-extractors.jar 裡面的  POIFSFileSystem.class  移除,重新封裝成 jar 放回原目錄後,重新啟動 Tomcat7 ,然後就成功了。







留言

這個網誌中的熱門文章

【研究】列印的條碼為什麼很難刷(掃描)

C# 使用 Process.Start 執行外部程式

統一發票列印小程式