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 ,然後就成功了。
留言