如何修改JAXB编组输出流以包含任意内联XML?

时间:2010-11-19 11:32:42

标签: java xml jaxb marshalling

我想修改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(具有任意复杂性),并将其与最终编组结果一起包含,该怎么办?

说,extraXml包含“<abc><def>Something extra</def></abc>”,我真的想要一个解决方案,让我能够像这样编组Product(格式化可选):

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

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

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

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

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

2 个答案:

答案 0 :(得分:6)

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

将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节点。

答案 1 :(得分:5)

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;

}

主要(一些演示代码)

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 xercesImpl.jarlib文件夹中的/lib/endorsed使用Java META-INF/Services覆盖工具来阻止JDK使用内部类进行编组。您可能需要使用以下方法直接指定备用DocumentBuilderFactory

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

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

希望它有所帮助。