填坑記: 古董項目Apache POI 依賴異常排除

當你看到NoSuchMethodError的時候,不要慌,深呼吸,這可能只是JAR包版本的問題…

引子:一個平靜的周二下午

那是一個看似平常的周二下午,系統運行良好,開發團隊在有條不紊地推進著新功能的開發。突然,測試環境中的報表導出功能失效了,用戶反饋頁面卡住,后臺日志瘋狂刷屏:

java.lang.NoSuchMethodError: 'byte[] org.apache.poi.util.IOUtils.peekFirstNBytes(java.io.InputStream, int)'

作為職業填坑人,我看到這個錯誤的第一反應是:“這是依賴不對?”

錯誤分析:深入錯誤堆棧的兔子洞

先來仔細分析一下錯誤信息:

Caused by: java.lang.NoSuchMethodError: 'byte[] org.apache.poi.util.IOUtils.peekFirstNBytes(java.io.InputStream, int)'at org.apache.poi.poifs.filesystem.FileMagic.valueOf(FileMagic.java:209)at org.apache.poi.openxml4j.opc.internal.ZipHelper.verifyZipHeader(ZipHelper.java:143)at org.apache.poi.openxml4j.opc.internal.ZipHelper.openZipStream(ZipHelper.java:175)at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:130)at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:319)at ca.terrasoft.poi.POIUtil.openFile(POIUtil.java:57)

從堆棧可以看出:

  1. 系統試圖調用IOUtils.peekFirstNBytes方法
  2. 該方法在運行時的類路徑中不存在
  3. 錯誤發生在處理Excel文件時(看調用鏈包含openFileZipPackage

這應該是一個典型的JAR包版本不一致問題。簡單說,代碼期望調用的方法在運行時環境中找不到,通常是因為編譯時使用的庫版本與運行時加載的版本不同。

偵探工作:尋找證據

由于項目是一個20年前的古董JSP項目,沒有使用Maven、Gradle等現代構建工具,所有依賴都直接堆在WEB-INF/lib目錄下。我們只能通過手動和腳本方式排查依賴。

直接檢查WEB-INF/lib目錄

$ ls -la /webapps/myapp/WEB-INF/lib/poi-*.jar
.....
-rw-r--r-- 1 tomcat tomcat 2758112 May 10 14:32 poi-5.2.3.jar

表面上看,POI的版本是5.2.3,應該沒問題,會不會是某個古董jar中可能有依賴老版本poi,那咋整? 一個一個去翻所有jar包? 嗯嗯,這好像不是碼農該做的事。

終極武器:編寫診斷JSP頁面

為了更詳細地了解web容器中類加載情況,直接整一個簡單的診斷頁面:

<%@ page import="java.net.URL" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.io.File" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Classpath Info</title></head>
<body>
<h1>Classpath Information</h1>
<h2>Finding POI libraries:</h2>
<ul><%// 查找指定類的位置try {Class<?> clazz = Class.forName("org.apache.poi.util.IOUtils");out.println("<li>IOUtils class found at: " + clazz.getProtectionDomain().getCodeSource().getLocation() + "</li>");// 查找方法是否存在try {clazz.getDeclaredMethod("peekFirstNBytes", java.io.InputStream.class, int.class);out.println("<li>peekFirstNBytes method exists!</li>");} catch (NoSuchMethodException e) {out.println("<li>peekFirstNBytes method NOT found!</li>");}} catch (Exception e) {out.println("<li>Error: " + e.getMessage() + "</li>");}%>
</ul><h2>All POI related JARs:</h2>
<ul><%ClassLoader cl = Thread.currentThread().getContextClassLoader();try {Enumeration<URL> resources = cl.getResources("META-INF/MANIFEST.MF");while (resources.hasMoreElements()) {URL url = resources.nextElement();String path = url.getPath();if (path.contains("poi")) {out.println("<li>" + path + "</li>");}}} catch (Exception e) {out.println("<li>Error: " + e.getMessage() + "</li>");}%>
</ul>
</body>
</html>

運行后,頁面輸出:

IOUtils class found at: file:/Users/xdev/workdir/myProject/classes/artifacts/myProject_war_exploded/WEB-INF/lib/tm-extractors.jar
* peekFirstNBytes method NOT found! .

這說明,雖然我們有poi-5.2.3.jar,但實際被加載的IOUtils類卻來自tm-extractors.jar

版本考古學:揭開歷史的面紗

進一步到Maven倉庫查詢tm-extractors.jar(https://mvnrepository.com/artifact/org.textmining/tm-extractors/0.4),發現它自帶了poi-2.5.1.jar,而且tm-extractors的發布時間非常久遠。

也就是說,tm-extractors.jar中自帶的老版本POI類覆蓋了新版本POI的類加載,導致我們即使有poi-5.2.3.jar,實際運行時卻用的是2.5.1的實現,自然沒有peekFirstNBytes方法。

病因揭曉:依賴地獄

最終我們發現,問題出在tm-extractors.jar這個古老依賴。它內部包含了POI 2.5.1的class文件,且優先被類加載器加載,覆蓋了我們顯式依賴的poi-5.2.3。

具體來說:

  1. 項目直接依賴poi-5.2.3.jar
  2. WEB-INF/lib目錄下還存在tm-extractors.jar,它內部自帶poi 2.5.1
  3. 由于類加載順序,IOUtils等POI類被加載自tm-extractors.jar
  4. 代碼中使用了5.x版本的API,但運行時加載了2.5.1版本的類

這就是經典的依賴地獄(Dependency Hell),而且在沒有構建工具的老項目中更為棘手。

解決方案:手動清理與依賴排查

對于沒有構建工具的老JSP項目,解決這類問題通常只能靠手動:

方案一:清理lib目錄,移除沖突依賴

  1. 停止Tomcat服務器
  2. 備份當前的WAR文件或lib目錄
  3. 檢查WEB-INF/lib目錄,移除tm-extractors.jar或用工具(如jar命令)剝離其中的POI相關class文件
  4. 確保只保留一個版本的POI(推薦新版本)
  5. 重新部署并測試

方案二:替換或升級依賴

  • 如果必須使用tm-extractors功能,嘗試尋找不自帶POI的版本,或用更現代的替代庫
  • 或者自行編譯一個去除POI依賴的tm-extractors.jar

方案三:類加載器隔離(高階方案)

  • 對于有能力自定義類加載器的容器,可以嘗試隔離不同JAR包的類加載(但對老JSP項目不現實)

我們的選擇

考慮到項目情況,我們最終選擇了方案一:手動清理WEB-INF/lib目錄,移除tm-extractors.jar,只保留poi-5.2.3.jar。這樣雖然失去了一些老庫的功能,但保證了POI相關功能的正常運行。

具體步驟:

  1. 手動排查并清理lib目錄
  2. 檢查所有JAR包是否有嵌套依賴(可用jar tf命令查看)
  3. 全面測試Excel導入導出功能
  4. 在測試環境部署并驗證

預防措施:避免再次踩坑

痛定思痛,我們制定了一系列措施來防止類似問題再次發生:

  1. 定期清理lib目錄:避免歷史遺留JAR包混雜
  2. 建立依賴引入審核機制:新依賴必須經過技術負責人審核
  3. 自動化測試:為Excel導入導出功能添加全面的自動化測試
  4. 文檔化依賴關系:手工維護一份依賴清單
  5. 推動構建工具改造:有條件時逐步引入Maven/Gradle等現代構建工具

結語:教訓與收獲

這次POI依賴踩坑的經歷,讓我們深刻認識到了Java生態系統中依賴管理的重要性。對于沒有構建工具的老項目,依賴沖突更容易發生且更難排查。

關鍵在于:

  • 時刻保持警惕,特別是看到NoSuchMethodError這類錯誤時
  • 建立系統的依賴管理機制
  • 深入理解類加載機制和JAR包結構
  • 不斷學習和更新知識,跟蹤常用庫的版本變化

最后,我想用一句話來結束這篇文章:

在Java世界中,了解你的依賴就像了解你的朋友一樣重要,當它們和平相處時,你的應用才能健康成長。

希望我的經驗能幫助到同樣面臨依賴問題的開發者們。記住,你不是一個人在戰斗!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83515.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83515.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83515.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CAPL Class: TcpSocket (此類用于實現 TCP 網絡通信 )

目錄 Class: TcpSocketacceptopenclosebindconnectgetLastSocketErrorgetLastSocketErrorAsStringlistenreceivesendsetSocketOptionshutdown函數調用的基本流程服務器端的基本流程客戶端的基本流程Class: TcpSocket學習筆記。來自CANoe幫助文檔。 Class: TcpSocket accept /…

微信小程序的開發及問題解決

HttpClient 測試例子 SpringBootTest public class HttpClientTest {/*** 測試通過httpclient發送get方式的請求*/Testpublic void testGET() throws IOException {//創建httpclient對象CloseableHttpClient httpClient HttpClients.createDefault();//創建請求對象HttpGet ht…

foreach中使用await的問題

目錄 1.說明 2.示例 3.解決方案 1.說明 在foreach中調用異步方法&#xff0c;即使使用了await&#xff0c;不會依次執行每個異步任務&#xff0c;也就是說Array.prototype.forEach不會等待 Promise 完成&#xff0c;即使你在回調函數中返回一個 Promise&#xff0c;forEach …

Linux調試生成核心存儲文件

1.核心存儲文件配置&#xff1a; 不知道理解對不對&#xff0c;Linux中的核心存儲文件的配置是在/proc/sys/kernel/core_pattern中的&#xff0c;使用 cat /proc/sys/kernel/core_pattern # 打印出 |/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E表示核…

Compose筆記(二十三)--多點觸控

這一節主要了解一下Compose中多點觸控&#xff0c;在Jetpack Compose 中&#xff0c;多點觸控處理需要結合Modifier和手勢API來實現&#xff0c;一般通過組合 pointerInput、TransformableState 和 TransformModifier 來創建支持縮放、旋轉和平移的組件。 一、 API 1. Pointer…

【Java ee初階】HTTP(4)

構造HTTP請求 1&#xff09;開發中&#xff0c;前后端交互。瀏覽器運行的網頁中&#xff0c;構造出HTTP請求 2&#xff09;調試階段&#xff0c;通過構造HTTP請求測試服務器 樸素的方案&#xff1a; 通過tcp socket 的方式構造HTTP請求 按照HTTP請求格式&#xff0c;往TCP…

STM32 __main

STM32開發中__main與用戶main()函數的本質區別及工作機制 在STM32開發中&#xff0c;__main和用戶定義的main()函數是啟動過程中的兩個關鍵節點&#xff0c;分別承擔運行時初始化和用戶程序入口的職責。以下是它們的核心差異及協作機制&#xff1a; 一、定義與層級差異 ?__ma…

什么是PMBus

一、PMBus的定義與背景 PMBus&#xff08;Power Management Bus&#xff0c;電源管理總線&#xff09; 是一種基于SMBus&#xff08;System Management Bus&#xff09;的開放標準數字通信協議&#xff0c;專為電源設備的監控、配置和控制設計。由PMBus聯盟&#xff08;現并入…

Python OOP核心技巧:如何正確選擇實例方法、類方法和靜態方法

Python方法類型全解析&#xff1a;實例方法、類方法與靜態方法的使用場景 一、三種方法的基本區別二、訪問能力對比表三、何時使用實例方法使用實例方法的核心場景&#xff1a;具體應用場景&#xff1a;1. 操作實例屬性2. 對象間交互3. 實現特定實例的行為 四、何時使用類方法使…

業務中臺-典型技術棧選型(微服務、容器編排、分布式數據庫、消息隊列、服務監控、低代碼等)

在企業數字化中臺建設中&#xff0c;業務中臺是核心支撐平臺&#xff0c;旨在通過技術手段將企業核心業務能力抽象、標準化和復用&#xff0c;以快速響應前端業務需求。其核心技術流涉及從業務抽象到服務化、治理和持續優化的全流程。以下是業務中臺建設中的核心技術體系及關鍵…

期望是什么:(無數次的均值,結合概率)21/6=3.5

https://seeing-theory.brown.edu/basic-probability/cn.html 期望是什么:(無數次的均值,結合概率)21/6=3.5 一、期望(數學概念) 在概率論和統計學中,**期望(Expectation)**是一個核心概念,用于描述隨機變量的長期平均取值,反映隨機變量取值的集中趨勢。 (一…

matlab官方免費下載安裝超詳細教程2025最新matlab安裝教程(MATLAB R2024b)

文章目錄 準備工作MATLAB R2024b 安裝包獲取詳細安裝步驟1. 文件準備2. 啟動安裝程序3. 配置安裝選項4. 選擇許可證文件5. 設置安裝位置6. 選擇組件7. 開始安裝8. 完成輔助設置 常見問題解決啟動失敗問題 結語 準備工作 本教程將幫助你快速掌握MATLAB R2024b的安裝技巧&#x…

第3章 自動化測試:從單元測試到硬件在環(HIL)

在前兩章中,我們已完成從環境搭建到流水線編譯的自動化配置。為了真正保障軟件質量、降低回歸風險,本章將聚焦測試自動化,涵蓋從最基礎的單元測試,到集成測試,再到硬件在環(Hardware-in-the-Loop, HIL)測試的全流程。通過腳本驅動、測試報告可視化和與 CI 平臺深度集成,…

信息收集+初步漏洞打點

目標&#xff1a;理解信息收集在滲透測試中的意義&#xff0c;熟悉常用工具用法&#xff0c;完成基本打點測試 一.理論學習&#xff1a; 模塊內容說明信息收集分類主動信息收集 vs 被動信息收集目標發現子域名、IP、端口、子站點、目錄、接口技術指紋識別Web框架&#xff08;如…

uniapp+vue3開發項目之引入vuex狀態管理工具

前言&#xff1a; 我們在vue2的時候常用的狀態管理工具就是vuex&#xff0c;vue3開發以后&#xff0c;又多了一個pinia的選項&#xff0c;相對更輕便&#xff0c;但是vuex也用的非常多的&#xff0c;這里簡單說下在uni-app中vuex的使用。 實現步驟&#xff1a; 1、安裝&#x…

淺談“量子計算應用:從基礎原理到行業破局”

量子計算應用:從基礎原理到行業破局 引言:量子計算為何成為科技革命新引擎? 量子計算利用量子力學原理(疊加態、糾纏態、量子干涉)突破經典計算的極限,在特定領域可實現指數級加速。根據中研普華預測,2025年全球量子計算市場規模將突破80億美元,2035年可達8117億美元。…

UNiAPP地區選擇

<template> <view class"container"> <!-- 左側地區列表 --> <scroll-view class"left-list" scroll-y :scroll-into-view"currentLetterId" scroll-with-animation scroll"…

嵌入式硬件篇---CAN

文章目錄 前言1. CAN協議基礎1.1 物理層特性差分信號線終端電阻通信速率總線拓撲 1.2 幀類型1.3 數據幀格式 2. STM32F103RCT6的CAN硬件配置2.1 硬件連接2.2 CubeMX配置啟用CAN1模式波特率引腳分配過濾器配置&#xff08;可選&#xff09; 3. HAL庫代碼實現3.1 CAN初始化3.2 發…

DeepSeek-R1 Supervised finetuning and reinforcement learning (SFT + RL)

DeepSeek-R1Supervised finetuning and reinforcement learning (SFT RL) 好啊&#xff0c;我們今天的直播會非常透徹的跟大家系統性的分享一下整個agents AI就大模型智能體系統和應用程序。我們在做開發的時候&#xff0c;或者實際做企業級的產品落地的時候&#xff0c;你必…

機器學習 day04

文章目錄 前言一、線性回歸的基本概念二、損失函數三、最小二乘法 前言 通過今天的學習&#xff0c;我掌握了機器學習中的線性回歸的相關基本概念&#xff0c;包括損失函數的概念&#xff0c;最小二乘法的理論與算法實現。 一、線性回歸的基本概念 要理解什么是線性回歸&…