【问题标题】:How can I modify the JAXB marshalling output stream to include arbitrary inline XML?如何修改 JAXB 编组输出流以包含任意内联 XML?
【发布时间】:2010-11-19 11:32:41
【问题描述】:

我想修改 JAXB 编组操作的输出流以包含一些任意 XML。这是一个说明情况的示例。

我有一个带有 JAXB 注释的任意 Product 域对象,目前看起来像这样:

@XmlRootElement(name="Product")
public class Product {

  @XmlElement(name="CommonProperty")
  private String commonProperty="Something";  

  @XmlElement(name="ExtraXml")
  private String extraXml="Something extra";

}

这通常会编组:

<Product>
  <CommonProperty>Something</CommonProperty>
  <ExtraXml>Something else</ExtraXml>
</Product>

现在,如果 extraXml 字段包含一些额外的 XML(任意复杂度),这些 XML 将包含在最终编组结果中?

假设extraXml 包含“&lt;abc&gt;&lt;def&gt;Something extra&lt;/def&gt;&lt;/abc&gt;”,我真的很想要一个解决方案,让我能够像这样编组Product(格式可选):

<Product>
  <CommonProperty>Something</CommonProperty>
  <abc>
    <def>Something extra</def>
  </abc>
</Product>

我查看了this related question,但它并没有完全产生我想要的结果,因为它似乎更倾向于整体格式更改而不是 DOM 插入。

extraXml 属性仅用于说明,它可以标记为@XmlTransient 或标记为单个专用类。唯一的标准是它可以以某种方式获取包含完全任意 XML 内容的 String,以附加到单独的 Product 编组输出。

我还应该提到,由此产生的输出的消费者能够以适合他们的方式解析任意内容。这里的目的是简化服务器端的处理。

提前感谢您提供的任何帮助。

【问题讨论】:

    标签: java xml jaxb marshalling


    【解决方案1】:

    您可以将额外的 XML 表示为 DOM 节点而不是字符串。

    将 extraXML 属性更改为 org.w3c.dom.Node 而不是 String,并使用 @XmlAnyElement 对其进行注释。

    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlAnyElement;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    import org.w3c.dom.Node;
    
    @XmlRootElement(name="Product")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Product {
    
      @XmlElement(name="CommonProperty")
      private String commonProperty="Something";  
    
      @XmlAnyElement
      private Node extraXml;
    
    }
    

    然后,当您编组一个 XML 文档时,例如:

    <Product>
      <CommonProperty>Something</CommonProperty>
      <abc>
        <def>Something extra</def>
      </abc>
    </Product>
    

    使用以下代码:

    import java.io.File;
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.Unmarshaller;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Product.class);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            File xml = new File("input.xml");
            Product product = (Product) unmarshaller.unmarshal(xml);
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(product, System.out);
        }
    }
    

    “额外的 XML”将保存为 DOM 节点。

    【讨论】:

    • 听起来很有希望 - 你能详细说明一下吗?
    • @Gary,我已经更新了我的答案,以解释如何使用 @XmlAnyElement 来完成。
    • +1 为您的重大更新 - 这是一个很大的帮助(并祝贺您今天打破 3K)。如果你不介意的话,还有最后一点。在编组开始时,我只有extraXml 作为字符串和new Product() 创建的Product 实例。有没有办法以 JAXB 乐于编组的方式指定节点?
    • 实际上,我已经设法完成了最后一块拼图。完全归功于你让我完成了 99.9% 的工作。我已经为其他人添加了答案。
    【解决方案2】:

    Blaise Doughan 的帮助下,这是我确定的最终解决方案。这包括一些额外的演示代码,以帮助可能遇到这种情况的其他人。

    ProductList(一个新的包装类,用于展示它如何适用于多个产品条目)

    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlElementWrapper;
    import javax.xml.bind.annotation.XmlRootElement;
    import java.util.ArrayList;
    import java.util.List;
    
    @XmlRootElement(name="ProductList")
    public class ProductList {
    
      @XmlElementWrapper(name="Products")
      @XmlElement(name="Product")
      public List<Product> products = new ArrayList<Product>();
    
    }
    

    产品(经过 Blaise 的修改)

    import org.w3c.dom.Node;
    
    import javax.xml.bind.annotation.*;
    
    @XmlRootElement(name="Product")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Product {
    
      @XmlElement(name="CommonProperty")
      public String commonProperty="Something";
    
      @XmlAnyElement
      public Node extraXml;
    
    }
    

    Main(一些演示代码)

    import org.w3c.dom.Document;
    import org.xml.sax.InputSource;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.Marshaller;
    import javax.xml.parsers.DocumentBuilderFactory;
    import java.io.StringReader;
    
    public class Main {
    
      public static void main(String[] args) throws Exception {
    
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    
        // Build some arbitrary extra XML and prepare an InputStream
        String fragment1 = "<abc><def>Some extra 1</def></abc>";
        String fragment2 = "<ghi><jkl>Some extra 2</jkl></ghi>";
        Document document1 = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment1)));
        Document document2 = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment2)));
    
        Product product1 = new Product();
        product1.commonProperty = "Hello 1";
        product1.extraXml=document1.getFirstChild();
    
        Product product2 = new Product();
        product2.commonProperty = "Hello 2";
        product2.extraXml=document2.getFirstChild();
    
        ProductList productList = new ProductList();
        productList.products.add(product1);
        productList.products.add(product2);
    
        JAXBContext jc = JAXBContext.newInstance(ProductList.class, Product.class);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(productList, System.out);
    
      }
    }
    

    最终输出:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <ProductList>
        <Products>
            <Product>
                <CommonProperty>Hello 1</CommonProperty>
                <abc>
                    <def>Some extra 1</def>
                </abc>
            </Product>
            <Product>
                <CommonProperty>Hello 2</CommonProperty>
                <ghi>
                    <jkl>Some extra 2</jkl>
                </ghi>
            </Product>
        </Products>
    </ProductList>
    

    结果!

    但它在 JBoss 中不起作用...

    如果您在 JBoss 4.2.3.GA 或 4.3 中尝试上述方法,您可能会发现您得到了一个

    class com.sun.org.apache.xerces.internal.dom.DocumentFragmentImpl nor any of its super class is known to this context.
    

    报告异常。这(可能)是由于 JBoss lib/lib/endorsed 文件夹中的 xercesImpl.jar 使用 Java META-INF/Services 覆盖工具来防止 JDK 使用内部类进行编组。您可能需要使用以下方法直接指定替代 DocumentBuilderFactory

    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance("oracle.xml.jaxp.JXDocumentBuilderFactory", this
          .getClass().getClassLoader());
    

    Oracle 实现似乎缓解了这些问题,可能是因为它在 JAXB 上下文中保持对 DOM 节点类的认识。这些类可以在 Oracle 客户端附带的 xdb.jar 和 xmlparserv2.jar 中找到。

    希望对你有帮助。

    【讨论】:

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