【问题标题】:MOXy: Efficient deep copy of JAXBElementMOXy:JAXBElement 的高效深拷贝
【发布时间】:2016-08-22 00:14:35
【问题描述】:

docx4j v3.3.0 使用以下代码克隆 JAXB 对象:

    public static <T> T deepCopy(T value, JAXBContext jc) {

    if (value==null) {
        throw new IllegalArgumentException("Can't clone a null argument");
    }

    try {
        @SuppressWarnings("unchecked")
        Class<T> clazz = (Class<T>) value.getClass();
        JAXBElement<T> contentObject = new JAXBElement<T>(new QName(clazz.getSimpleName()), clazz, value);
        JAXBSource source = new JAXBSource(jc, contentObject);
        JAXBElement<T> elem = jc.createUnmarshaller().unmarshal(source, clazz);

        T res;
        if (value instanceof JAXBElement<?>) {
            @SuppressWarnings("unchecked")
            T resT = (T) elem;
            res = resT;
        } else {
            @SuppressWarnings("unchecked")
            T resT = (T) elem.getValue();
            res = resT;
        }

        return res;
    } catch (JAXBException ex) {
        throw new IllegalArgumentException(ex);
    }
}

使用 MOXy v2.5.2(我们使用它,因为它支持 Java 6)和最新的 2.6.3,尝试克隆 JAXBElement,例如:

public void testIssue212() {

    CTBookmark bookmark = Context.getWmlObjectFactory().createCTBookmark();
    JAXBElement<CTBookmark> el =Context.getWmlObjectFactory().createBodyBookmarkStart(bookmark);

    Object o = XmlUtils.deepCopy(el);
}

结果:

    [Exception [EclipseLink-25007] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.XMLMarshalException
    Exception Description: A descriptor for class javax.xml.bind.JAXBElement was not found in the project.  For JAXB, if the JAXBContext was bootstrapped using TypeMappingInfo[] you must call a marshal method that accepts TypeMappingInfo as an input parameter.]
        at org.eclipse.persistence.jaxb.JAXBUnmarshaller.handleXMLMarshalException(JAXBUnmarshaller.java:980)
        at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:303)
        at org.docx4j.XmlUtils.deepCopy(XmlUtils.java:974)
        ... 25 more
    Caused by: Exception [EclipseLink-25007] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.XMLMarshalException
    Exception Description: A descriptor for class javax.xml.bind.JAXBElement was not found in the project.  For JAXB, if the JAXBContext was bootstrapped using TypeMappingInfo[] you must call a marshal method that accepts TypeMappingInfo as an input parameter.
        at org.eclipse.persistence.exceptions.XMLMarshalException.descriptorNotFoundInProject(XMLMarshalException.java:140)
        at org.eclipse.persistence.internal.oxm.Context$ContextState.getSession(Context.java:145)
        at org.eclipse.persistence.oxm.XMLContext$XMLContextState.getSession(XMLContext.java:795)
        at org.eclipse.persistence.oxm.XMLContext$XMLContextState.getSession(XMLContext.java:1)
        at org.eclipse.persistence.internal.oxm.Context.getSession(Context.java:466)
        at org.eclipse.persistence.oxm.XMLContext.getSession(XMLContext.java:364)
        at org.eclipse.persistence.oxm.XMLContext.getSession(XMLContext.java:1)
        at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:466)
        at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:695)
        at org.eclipse.persistence.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:655)
        at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:301)
        ... 26 more

我们可以通过以下方式解决这个问题:

        JAXBElement<T> elem; 

        if (Context.getJaxbImplementation().equals(JAXBImplementation.ECLIPSELINK_MOXy)
                && value instanceof JAXBElement<?>) {

            elem = (JAXBElement<T>) value;
            Class<?> valueClass = elem.getDeclaredType();

            Marshaller mar = jc.createMarshaller();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(256);
            mar.marshal(elem, bout);

            Unmarshaller unmar = jc.createUnmarshaller();
            elem = (JAXBElement<T>)unmar.unmarshal(new StreamSource(new ByteArrayInputStream(
                    bout.toByteArray())), valueClass);

        }

但是有更好的方法吗?

【问题讨论】:

    标签: jaxb eclipselink moxy docx4j


    【解决方案1】:

    免责声明:我是JAXB2-Basics 的作者,其中包括Copyable Plugin´,我认为它非常适合这项任务。

    你可能对Copyable Plugin感兴趣,它会生成无反射的策略复制方法。

    在 Maven 中激活(另见 Using JAXB2 Basics Plugins):

            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <configuration>
                    <extension>true</extension>
                    <args>
                        <arg>-Xcopyable</arg>
                    </args>
                    <plugins>
                        <plugin>
                            <groupId>org.jvnet.jaxb2_commons</groupId>
                            <artifactId>jaxb2-basics</artifactId>
                        </plugin>
                    </plugins>
                </configuration>
            </plugin>
    

    该插件然后生成深度、无反射和基于策略的 clonecopyTo 方法(见下文)。这为您提供了非常有效的复制。您还可以“复制”到现有实例或通过指定自己的策略来自定义应复制的内容和方式。例如,您可能希望避免复制 id 字段或类似的内容。生成的代码也知道如何处理JAXBElement

    这是一种生成的代码:

    public Object clone() {
        return copyTo(createNewInstance());
    }
    
    public Object copyTo(Object target) {
        final CopyStrategy2 strategy = JAXBCopyStrategy.INSTANCE;
        return copyTo(null, target, strategy);
    }
    
    public Object copyTo(ObjectLocator locator, Object target, CopyStrategy2 strategy) {
        final Object draftCopy = ((target == null)?createNewInstance():target);
        if (draftCopy instanceof IssueJIIB35) {
            final IssueJIIB35 copy = ((IssueJIIB35) draftCopy);
            {
                Boolean nameShouldBeCopiedAndSet = strategy.shouldBeCopiedAndSet(locator, this.isSetName());
                if (nameShouldBeCopiedAndSet == Boolean.TRUE) {
                    String sourceName;
                    sourceName = this.getName();
                    String copyName = ((String) strategy.copy(LocatorUtils.property(locator, "name", sourceName), sourceName, this.isSetName()));
                    copy.setName(copyName);
                } else {
                    if (nameShouldBeCopiedAndSet == Boolean.FALSE) {
                        copy.name = null;
                    }
                }
            }
            // ...
        }
        return draftCopy;
    }
    
    public Object createNewInstance() {
        return new IssueJIIB35();
    }
    

    可能看起来有点奇怪/麻烦,但它需要考虑很多 JAXB 特性。

    【讨论】:

    • 感谢您。 iirc,我过去在尝试一次使用多个扩展时遇到了麻烦。 docx4j 使用父指针插件
    【解决方案2】:

    原来https://github.com/plutext/docx4j/pull/163 中引入的 docx4j 代码在复制 JAXBElement 时出现问题,无论是使用 MOXy 还是 Sun/Oracle 参考实现。

    https://github.com/plutext/docx4j/commit/b5d8b4722e814945e502da9f0516d59c498b64bb 修复它

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-29
      • 2012-04-12
      • 2015-01-13
      • 2011-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      相关资源
      最近更新 更多