填坑记: 古董项目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.mzph.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;最小二乘法的理论与算法实现。 一、线性回归的基本概念 要理解什么是线性回归&…