我想修改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
编组输出。
我还应该提到,此输出的消费者能够以适合他们的方式解析任意内容。这里的目的是简化服务器端的处理。
提前感谢您提供的任何帮助。
答案 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.jar
和lib
文件夹中的/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中找到。
希望它有所帮助。