Apache POI 5与Weblogic xmlbeans JAR冲突类转换异常问题咨询
问题背景
Java项目中使用Apache POI的SXSSF流式API生成Excel文件时,运行时抛出如下异常:
Caused By: java.lang.ExceptionInInitializerError at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderImpl.build(SchemaTypeLoaderImpl.java:161) at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.<init>(SchemaTypeSystemImpl.java:198) at org.apache.xmlbeans.metadata.system.sXMLSCHEMA.TypeSystemHolder.<init>(TypeSystemHolder.java:9) Truncated. see log file for complete stacktrace Caused By: org.apache.xmlbeans.XmlRuntimeException: java.lang.ClassCastException: org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl cannot be cast to org.apache.xmlbeans.SchemaTypeLoader at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderImpl.build(SchemaTypeLoaderImpl.java:164) at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.<init>(SchemaTypeSystemImpl.java:198) at org.apache.xmlbeans.metadata.system.sXMLTOOLS.TypeSystemHolder.<init>(TypeSystemHolder.java:9) at org.apache.xmlbeans.metadata.system.sXMLTOOLS.TypeSystemHolder.<clinit>(TypeSystemHolder.java:6) at java.lang.Class.forName0(Native Method) Truncated. see log file for complete stacktrace Caused By: java.lang.ClassCastException: org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl cannot be cast to org.apache.xmlbeans.SchemaTypeLoader at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderImpl.build(SchemaTypeLoaderImpl.java:162) at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.<init>(SchemaTypeSystemImpl.java:198) at org.apache.xmlbeans.metadata.system.sXMLTOOLS.TypeSystemHolder.<init>(TypeSystemHolder.java:9) at org.apache.xmlbeans.metadata.system.sXMLTOOLS.TypeSystemHolder.<clinit>(TypeSystemHolder.java:6) at java.lang.Class.forName0(Native Method) Truncated. see log file for complete stacktrace
该异常本质是Weblogic内置的xmlbeans版本与应用自带POI依赖的xmlbeans版本冲突,类加载时混装了两个版本的类导致类型转换失败。
常规方案的局限性
同类问题的通用修复方式是修改weblogic.xml配置,指定相关包优先加载应用内置版本,配置内容如下:
<prefer-web-inf-classes>false</prefer-web-inf-classes> <wls:container-descriptor> <wls:prefer-application-packages> <wls:package-name>org.apache.commons.collections4.*</wls:package-name> <wls:package-name>org.apache.commons.compress.*</wls:package-name> <wls:package-name>org.apache.poi.*</wls:package-name> <wls:package-name>org.apache.xmlbeans.*</wls:package-name> <wls:package-name>org.apache.xmlbeans.XmlOptions</wls:package-name> <wls:package-name>org.openxmlformats.*</wls:package-name> <wls:package-name>schemaorg_apache_xmlbeans.*</wls:package-name> </wls:prefer-application-packages> <wls:prefer-application-resources> <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLCONFIG/TypeSystemHolder.class</wls:resource-name> <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLLANG/TypeSystemHolder.class</wls:resource-name> <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLSCHEMA/TypeSystemHolder.class</wls:resource-name> <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLTOOLS/TypeSystemHolder.class</wls:resource-name> </wls:prefer-application-resources> </wls:container-descriptor>
但该方案需要将prefer-web-inf-classes设为false,会破坏应用现有依赖Weblogic内置类的功能,无法直接使用。
代码层面类加载隔离方案
完全可以通过自定义类加载器实现单功能点的类隔离,不需要修改全局Weblogic配置,也不会影响其他业务逻辑。之前尝试URLClassLoader失败,核心原因是JDK默认的URLClassLoader遵循双亲委派机制,加载类时会先委托父类加载器(即Weblogic的系统类加载器)查找,最终还是会加载到服务器内置的旧版xmlbeans类,导致冲突。
实现步骤
- 第一步:确认依赖完整性
检查pom.xml中POI相关依赖的版本匹配,poi-ooxml会传递引入对应版本的xmlbeans,不需要额外引入,确保最终打包后WEB-INF/lib下存在POI、xmlbeans、commons-compress、poi-ooxml-lite相关jar包。参考依赖配置如下:<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> <!-- 选择和现有业务兼容的版本即可 --> </dependency> - 第二步:实现子优先的隔离类加载器
自定义类加载器打破默认双亲委派规则,对POI、xmlbeans相关的类,优先从应用自带的jar包加载,不委托给Weblogic父加载器,其他类仍走正常双亲委派逻辑,避免影响其他功能:import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.List; public class PoiIsolatedClassLoader extends URLClassLoader { // 需要隔离加载、优先从应用jar读取的类前缀 private static final List<String> ISOLATED_PACKAGE_PREFIX = Arrays.asList( "org.apache.poi.", "org.apache.xmlbeans.", "org.apache.commons.compress.", "org.apache.commons.collections4.", "org.openxmlformats.", "schemaorg_apache_xmlbeans." ); public PoiIsolatedClassLoader(URL[] jarPaths, ClassLoader parentClassLoader) { super(jarPaths, parentClassLoader); } @Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(className)) { // 先检查类是否已经被当前加载器加载 Class<?> loadedClass = findLoadedClass(className); if (loadedClass != null) { if (resolve) resolveClass(loadedClass); return loadedClass; } // 属于隔离范围的类,优先从当前类加载器绑定的jar路径查找 boolean needIsolate = ISOLATED_PACKAGE_PREFIX.stream().anyMatch(className::startsWith); if (needIsolate) { try { loadedClass = findClass(className); if (resolve) resolveClass(loadedClass); return loadedClass; } catch (ClassNotFoundException ignored) { // 本地jar找不到时再 fallback 到父加载器 } } // 非隔离类走默认双亲委派逻辑 return super.loadClass(className, resolve); } } } - 第三步:通过隔离类加载器执行Excel生成逻辑
注意:不要在调用入口类中直接import任何POI、xmlbeans相关类,否则这些类会被当前上下文的Weblogic类加载器提前加载,导致隔离失效。
实现方式可根据业务复杂度二选一:- 全反射调用:适合Excel逻辑简单的场景,直接通过反射实例化POI类、调用对应方法:
// 1. 收集WEB-INF/lib下所有POI相关jar的路径 String webInfLibPath = request.getServletContext().getRealPath("/WEB-INF/lib"); File libDir = new File(webInfLibPath); List<URL> poiJarUrls = new ArrayList<>(); File[] libJars = libDir.listFiles((dir, name) -> name.startsWith("poi-") || name.startsWith("poi-ooxml") || name.startsWith("xmlbeans-") || name.startsWith("commons-compress") || name.startsWith("commons-collections4") || name.startsWith("poi-ooxml-lite") ); if (libJars != null) { for (File jar : libJars) { poiJarUrls.add(jar.toURI().toURL()); } } // 2. 初始化隔离类加载器,父加载器传入当前线程的上下文类加载器 PoiIsolatedClassLoader isolatedLoader = new PoiIsolatedClassLoader( poiJarUrls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader() ); // 3. 反射执行Excel生成逻辑 try ( OutputStream out = response.getOutputStream(); // 实例化SXSSFWorkbook AutoCloseable workbook = (AutoCloseable) isolatedLoader .loadClass("org.apache.poi.xssf.streaming.SXSSFWorkbook") .getDeclaredConstructor().newInstance() ) { Class<?> workbookClass = workbook.getClass(); // 创建Sheet Method createSheetMethod = workbookClass.getMethod("createSheet", String.class); Object sheet = createSheetMethod.invoke(workbook, "导出数据"); // ... 此处补充行、单元格创建、数据填充的反射逻辑 ... // 写出Excel到输出流 Method writeMethod = workbookClass.getMethod("write", OutputStream.class); writeMethod.invoke(workbook, out); }- 独立工具类封装:适合Excel生成逻辑复杂的场景,把所有Excel生成逻辑封装在单独的工具类(比如
PoiExcelGenerator)中,把这个工具类也放在隔离类加载器的加载路径里,调用时只需要反射调用工具类的入口方法即可,不需要全写反射,维护成本更低。
常见踩坑点
- 隔离类加载器的jar路径不要漏
xmlbeans、schemaorg_apache_xmlbeans对应的类资源,否则还是会触发类加载冲突。 - 不要提前import POI相关类,否则类会被Weblogic类加载器提前加载,隔离完全失效。
- 确保应用自带的POI和xmlbeans版本匹配,比如POI 4.1.2对应xmlbeans 3.1.0,POI 5.2.3对应xmlbeans 5.0.3,版本不兼容也会抛出运行时异常。
内容的提问来源于stack exchange,提问作者bgarrett315
相关产品推荐
相关产品推荐

