【问题标题】:How to marshall a string using JAXB that sometimes contains XML content and sometimes does not?如何使用 JAXB 编组有时包含 XML 内容有时不包含的字符串?
【发布时间】:2012-09-07 20:48:36
【问题描述】:

考虑这个例子 -

我有一个名为 Report 的类,它有一个 Message 类型的字段。 Message 类有一个名为“body”的字段,它是一个字符串。 “body”可以是任何字符串,但有时它包含格式正确的 XML 内容。如何确保当“正文”包含 XML 内容时,序列化采用 XML 结构的形式,而不是它目前给出的形式?

这是带有输出的代码 -

报告

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "Report")
@XmlType(propOrder = { "message"})
public class Report
{
    private Message message;
    public Message getMessage() { return message; }
    public void setMessage(Message m) { message = m; }
}

消息

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "body" })
public class Message
{
    private String body;
    public String getBody() { return body; }
    @XmlElement
    public void setBody(String body) { this.body = body; }
}

主要

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;



public class SerializationTest
{
    public static void main(String args[]) throws Exception
    {
       JAXBContext jaxbContext = JAXBContext.newInstance(Report.class);
       Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
       jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

       Report report = new Report();
       Message message = new Message();

       message.setBody("Sample report message.");
       report.setMessage(message);
       jaxbMarshaller.marshal(report, System.out);

       message.setBody("<rootTag><body>All systems online.</body></rootTag>");
       report.setMessage(message);
       jaxbMarshaller.marshal(report, System.out);
    }
}

输出如下——

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Report>
    <message>
        <body>Sample report message.</body>
    </message>
</Report>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Report>
    <message>
        <body>&lt;rootTag&gt;&lt;body&gt;All systems online.&lt;/body&gt;&lt;/rootTag&gt;</body>
    </message>
</Report>

正如您在上面的输出中看到的,对于“body”的第二个实例,产生了序列化

 <body>&lt;rootTag&gt;&lt;body&gt;All systems online.&lt;/body&gt;&lt;/rootTag&gt;</body>

而不是

<body><rootTag><body>All systems online.</body></rootTag></body>

如何解决这个问题?

【问题讨论】:

    标签: java serialization jaxb


    【解决方案1】:

    注意:我是EclipseLink JAXB (MOXy) 领导,也是JAXB (JSR-222) 专家组的成员。

    此用例使用@XmlAnyElement 注释并指定DOMHandler 进行映射。使用 JAXB RI 执行此操作时似乎存在错误,但以下用例适用于 EclipseLink JAXB (MOXy)。

    BodyDomHandler

    默认情况下,JAXB 实现会将未映射的内容表示为 DOM 节点。您可以利用 DomHandler 来替代 DOM 的表示形式,在这种情况下,我们将 DOM 表示为 String

    import java.io.*;
    import javax.xml.bind.ValidationEventHandler;
    import javax.xml.bind.annotation.DomHandler;
    import javax.xml.transform.Source;
    import javax.xml.transform.stream.*;
    
    public class BodyDomHandler implements DomHandler<String, StreamResult> {
    
        private static final String BODY_START_TAG = "<body>";
        private static final String BODY_END_TAG = "</body>";
    
        private StringWriter xmlWriter = new StringWriter();
    
        public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
            return new StreamResult(xmlWriter);
        }
    
        public String getElement(StreamResult rt) {
            String xml = rt.getWriter().toString();
            int beginIndex = xml.indexOf(BODY_START_TAG) + BODY_START_TAG.length();
            int endIndex = xml.indexOf(BODY_END_TAG);
            return xml.substring(beginIndex, endIndex);
        }
    
        public Source marshal(String n, ValidationEventHandler errorHandler) {
            try {
                String xml = BODY_START_TAG + n.trim() + BODY_END_TAG;
                StringReader xmlReader = new StringReader(xml);
                return new StreamSource(xmlReader);
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    

    留言

    以下是在 Message 类上指定 @XmlAnyElement 注释的方法。

    import javax.xml.bind.annotation.XmlAnyElement;
    import javax.xml.bind.annotation.XmlType;
    
    @XmlType(propOrder = { "body" })
    public class Message
    {
        private String body;
        public String getBody() { return body; }
        @XmlAnyElement(BodyDomHandler.class)
        public void setBody(String body) { this.body = body; }
    }
    

    输出

    以下是运行 SerialziationTest 的输出:

    <?xml version="1.0" encoding="UTF-8"?>
    <Report>
       <message>
          <body>Sample report message.</body>
       </message>
    </Report>
    <?xml version="1.0" encoding="UTF-8"?>
    <Report>
       <message>
          <body>
             <rootTag>
                <body>All systems online.</body>
             </rootTag>
          </body>
       </message>
    </Report>
    

    更多信息

    注意 - JAXB RI 中的错误

    JAXB 参考实现中似乎存在错误,示例代码将产生如下堆栈跟踪:

    Exception in thread "main" javax.xml.bind.MarshalException
     - with linked exception:
    [com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an @XmlRootElement annotation]
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
        at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
        at forum12428727.SerializationTest.main(SerializationTest.java:20)
    Caused by: com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an @XmlRootElement annotation
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:216)
        at com.sun.xml.internal.bind.v2.runtime.LeafBeanInfoImpl.serializeRoot(LeafBeanInfoImpl.java:126)
        at com.sun.xml.internal.bind.v2.runtime.property.SingleReferenceNodeProperty.serializeBody(SingleReferenceNodeProperty.java:100)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:664)
        at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:141)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
        ... 3 more
    

    【讨论】:

    • 执行此操作后,我收到一个异常消息 - “无法将类型“java.lang.String”编组为元素,因为它缺少 @XmlRootElement 注释”
    • @CodeBlue - 是的,此示例中的 JAXB (JSR-222) 实现中似乎存在错误。我使用 EclipseLink JAXB (MOXy) 将示例放在一起。
    • 哇。为什么他们还没有修复它?
    • @CodeBlue 和 BDoughan:这个错误修复了吗?我也遇到了这个错误。
    • JDK7 1.7.0_76似乎还没有修复这个bug。
    【解决方案2】:

    如果它只用于编组,并忽略 , 我们可以使用以下内容:

    marshaller.setProperty("com.sun.xml.bind.marshaller.CharacterEscapeHandler",
                    new CharacterEscapeHandler() {
                        @Override
                        public void escape(char[] ac, int i, int j, boolean flag,
                                Writer writer) throws IOException {
                            writer.write(ac, i, j);
                        }
                    });
    

    【讨论】:

    • 没有这样的属性:javax.xml.bind.PropertyException:名称:com.sun.xml.bind.marshaller.CharacterEscapeHandler...
    【解决方案3】:

    3 种不同的解决方案 1)、2) 3),如下:

    1) 以下帖子是您的解决方案 Loresh 的描述:

    http://anna-safronova.livejournal.com/2524.html?thread=9180

    这仍然缺少限制细节。

    • 使用嵌入的 html,我们需要一个 &lt;![CDATA

    • JAXB 的依赖关系

    com.sun.xml.bind.marshaller.CharacterEscapeHandler

    需要 import jaxb-impl 进行编译 / 并且可能需要执行,例如

    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>  
     <version>2.2.4</version>
    
    • 限制:此解决方案是特定于容器的,并且由于类加载策略可能无法运行。

    2) 另一个类似的做法是JDK的rt.jar依赖

    com.sun.xml.internal.bind.CharacterEscapeHandler

    http://theopentutorials.com/tutorials/java/jaxb/jaxb-marshalling-and-unmarshalling-cdata-block/

    同样的限制/依赖于目标 JDK,并且需要对 Eclipse/Maven 进行一些调整(不好的选择/我的意见)

    3) 最后,在 Reg Whitton 的另一篇文章中找到了最佳解决方案:

    https://stackoverflow.com/a/12637295/560410

    这是详细的收据:

    http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

    非常适合我!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-07
      相关资源
      最近更新 更多