— 一次 POI 版本不一致导致的 SPI 加载异常排查记录 —
一、问题现场
今天用Spring AI Alibaba的PoiDocumentReader把 docx 灌进向量库,跑起来直接抛异常:
Your InputStream was neither an OLE2 stream, nor an OOXML stream or you haven't provide the poi-ooxml.jar and/or poi-scratchpad.jar in the classpath/modulepath - FileMagic: OOXML
doc 文件毫发无损,唯独 docx 报错。明明是 docx 的 OOXML 流,凭啥说找不到解析器?
二、跟进源码
报错堆栈指向org.apache.poi.extractor.ExtractorFactory#wp,核心逻辑就是遍历Singleton.INSTANCE.provider找能处理当前 FileMagic 的工厂:
private static POITextExtractor wp(FileMagic fm, ProviderMethod fun) throws IOException { for (ExtractorProvider prov : Singleton.INSTANCE.provider) { if (prov.accepts(fm)) { POITextExtractor ext = fun.create(prov); if (ext != null) { return ext; } } } throw new IOException( "Your InputStream was neither an OLE2 stream, nor an OOXML stream " + "or you haven't provide the poi-ooxml*.jar and/or poi-scratchpad*.jar in the classpath/modulepath - FileMagic: " + fm + ", providers: " + Singleton.INSTANCE.provider); }断点打进去一看,Singleton.INSTANCE.provider这个 ArrayList 里只有两个元素:
OLE2ScratchpadExtractorFactoryMainExtractorFactory
关键的那一个没了——负责 OOXML(.docx/.xlsx/.pptx)的
根本没被加载进来。
三、真相只有一个:版本不一致
POI 的这些ExtractorFactory是通过Java SPI机制加载的,入口在META-INF/services/org.apache.poi.extractor.ExtractorProvider。
打开 IDEA 的 Maven 依赖面板一看,果然:
poi5.4.0
poi-ooxml4.1.2← 拖后腿的
Spring AI Alibaba 引入的是 5.4.0,但项目里某个老依赖把poi-ooxml锁死在 4.1.2。问题来了:
poi 5.4.0 启动时扫描 SPI,想加载
OOXML的解析器;但这个类的 class 文件在 4.1.2 的 jar 里,5.4.0 找不到对应的实现类;
加载失败被吞掉,ArrayList 里就只剩两个老格式工厂了;
docx 流进来,遍历到底也没人接,最后抛 IOE。
四、解决方案
统一版本,把poi-ooxml也升级到 5.4.0:
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.4.0</version></dependency>
重启后Singleton.INSTANCE.provider里就能看到OOXML的解析器了,docx 顺利入向量库,问题解决。
五、踩坑经验
- POI 系列必须同版本共进退
:
poi/poi-ooxml/poi-scratchpad版本一旦不一致,SPI 加载就会静默失败。 - 遇到 SPI 加载异常先看依赖树
:
mvn dependency:tree | grep poi一眼能看出谁在拖后腿。 - 推荐用 BOM 统一管理
:Spring AI Alibaba 依赖的是 POI 5.4.0,建议在父 POM 用
poi.version统一锁定,避免被别的传递依赖带偏。