使用docx4j+MOXy处理含形状docx时出现跨模块ClassCastException
使用docx4j读取docx文件,替换w:ins/w:del标记的修订作者名为固定字符串"External",pom.xml引入了docx4j-core、docx4j-JAXB-MOXy和org.eclipse.persistence.moxy依赖。处理包含形状的docx文档时,抛出如下ClassCastException:
org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTWordprocessingShape无法转换为org.eclipse.persistence.internal.oxm.Root
错误发生在AbstractMarshalRecordImpl.java第211行。已尝试改用ReferenceImpl规避问题,现寻求其他解决方案。
方案1:切换JAXB实现为JAXB-RI(JDK默认实现)
MOXy在处理Office形状相关JAXB类时存在类型转换逻辑冲突,改用JDK自带的JAXB-RI可避免该问题。
- 修改pom.xml,移除MOXy相关依赖,替换为docx4j的JAXB-Internal实现:
<dependencies> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-core</artifactId> <version>8.3.9</version> <!-- 替换为你当前使用的版本 --> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-JAXB-Internal</artifactId> <version>8.3.9</version> </dependency> </dependencies>
- 代码无需额外修改,docx4j会自动切换到JAXB-RI,对复杂形状元素的兼容性更好。
方案2:升级docx4j至最新稳定版
新版本docx4j通常会修复MOXy与形状类的兼容性bug,这是最稳妥的修复方式之一。
- 更新pom.xml中的依赖版本:
<dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-core</artifactId> <version>8.3.11</version> <!-- 替换为最新稳定版 --> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-JAXB-MOXy</artifactId> <version>8.3.11</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>org.eclipse.persistence.moxy</artifactId> <version>4.0.2</version> <!-- 匹配docx4j对应的MOXy版本 --> </dependency>
方案3:排除形状相关类的JAXB上下文初始化
若必须使用MOXy,可在初始化JAXB上下文时排除CTWordprocessingShape类,避免触发类型转换逻辑。
- 自定义JAXB上下文提供者:
import org.docx4j.jaxb.Context; import org.eclipse.persistence.jaxb.JAXBContextFactory; import javax.xml.bind.JAXBContext; import java.util.HashMap; import java.util.Map; import java.util.Arrays; public class CustomJAXBContextProvider { public static JAXBContext createContext() throws Exception { Map<String, Object> properties = new HashMap<>(); properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, Context.getJAXBContext().getProperties().get(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY)); // 排除形状相关类 Class<?>[] excludedClasses = {org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTWordprocessingShape.class}; return JAXBContext.newInstance( Arrays.stream(Context.getJAXBContext().getClasses()) .filter(clazz -> !Arrays.asList(excludedClasses).contains(clazz)) .toArray(Class<?>[]::new), properties ); } }
- 在代码初始化阶段设置自定义上下文:
Context.setJAXBContext(CustomJAXBContextProvider.createContext());
注意:此方案会导致形状元素无法正常序列化,仅适用于不需要修改形状、仅处理修订作者的场景。
方案4:处理修订时跳过形状相关节点
遍历修订节点时,显式过滤掉父节点为形状的元素,避免触发MOXy的序列化逻辑。
- 修订处理示例代码:
import org.docx4j.wml.*; import java.util.List; import java.util.stream.Collectors; public class RevisionProcessor { public static void replaceAuthors(WordprocessingMLPackage pkg) { MainDocumentPart mainPart = pkg.getMainDocumentPart(); List<Object> revisions = mainPart.getJAXBNodesViaXPath("//w:ins | //w:del", true); // 过滤掉形状内的修订节点 List<Object> filteredRevisions = revisions.stream() .filter(node -> { if (node instanceof Ins) { return !isParentShape((Ins) node); } else if (node instanceof Del) { return !isParentShape((Del) node); } return true; }) .collect(Collectors.toList()); for (Object revision : filteredRevisions) { if (revision instanceof Ins) { ((Ins) revision).setAuthor("External"); } else if (revision instanceof Del) { ((Del) revision).setAuthor("External"); } } } private static boolean isParentShape(ContentAccessor node) { Object parent = node.getParent(); if (parent instanceof org.docx4j.XmlUtils.Element) { org.docx4j.XmlUtils.Element elem = (org.docx4j.XmlUtils.Element) parent; return "http://schemas.microsoft.com/office/word/2010/wordprocessingShape".equals(elem.getNamespaceURI()) && "shape".equals(elem.getLocalName()); } return false; } }
此方案可绕过形状内的修订处理,但如果需要修改形状内的修订作者,则不适用。
内容的提问来源于stack exchange,提问作者user304611

