JAXB:如何避免xmlns的重复名称空间定义:xsi
我有一个JAXB设置,我使用@XmlJavaTypeAdapter将类型为Person
的对象替换为仅包含此人的UUID的PersonRef
类型的对象。这工作非常好。但是,生成的XML在每次使用时都重新声明相同的名称空间(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
)。虽然这通常是好的,但它感觉不对。JAXB:如何避免xmlns的重复名称空间定义:xsi
如何配置JAXB在文档的最开始处声明xmlns:xsi?我可以手动将名称空间声明添加到根元素吗?
这里是什么,我想才达到一个例子:
电流:
<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a">
<relation type="CHILD">
<to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</relation>
<relation type="CHILD">
<to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</relation>
<!-- SNIP: some more relations -->
</person>
通缉:
<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<relation type="CHILD">
<to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0"/>
</relation>
<relation type="CHILD">
<to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a"/>
</relation>
<!-- SNIP: some more relations -->
</person>
您可以用代码做到这一点:
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] {
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
};
}
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI))
return "xsi";
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
return "xs";
if (namespaceUri.equals(WellKnownNamespace.XML_MIME_URI))
return "xmime";
return suggestion;
}
});
它是XML,因此你可以使用处理输出DOM或XSLT来摆脱多个名称空间引用。
很抱歉,但我宁愿拍自己的脚:) – sfussenegger 2010-02-22 13:02:09
但严重的是,在这个任务投掷XSLT或DOM似乎有点激烈。最终,这可以归结为简单的请我的美学愿望:) – sfussenegger 2010-02-22 13:05:28
JAXB是不可配置的。我认为这是做到这一点(或放手)的唯一方法。 – 2010-02-22 13:08:10
它看起来像一个JAXB customization Namespace mapper issue
当您使用马歇尔JAXB 1.0,编组器对象,控制编组的处理的JAXB对象的XML文档,提供所得到的XML文档中的命名空间声明。有时的Marshaller产生大量的命名空间声明看起来多余的,例如:
<?xml version="1.0"?>
<root>
<ns1:element xmlns:ns1="urn:foo"> ... </ns1:element>
<ns2:element xmlns:ns2="urn:foo"> ... </ns2:element>
<ns3:element xmlns:ns3="urn:foo"> ... </ns3:element>
</root>
JAXB 2.0改变了这种行为。如果使用JAXB 2.0(或更高版本)封送XML文档,则Marshaller会声明所有静态已知名称空间统一资源标识符(URI),即用作JAXB注释中元素或属性名称的那些URI。例如,当作为属性或元素值使用的限定名称(
QName
)需要新的名称空间URI时,或者当文档对象模型(DOM)被使用时,JAXB还可以在XML文档的中间声明其他名称空间。 )节点在内容树中需要一个新的名称空间URI。此行为可能会生成一个XML文档,其中包含大量带有自动生成名称空间前缀的名称空间声明。问题是自动生成的名称空间前缀(如ns1,ns2和ns3)不方便用户使用 - 它们通常不会帮助人们理解编组的XML。
幸运的是,JAXB 2.0(或更高版本)提供了名为
com.sun.xml.bind.marshaller.NamespacePrefixMapper
的服务提供者接口(SPI),您可以使用它来指定用于编组的更有用的名称空间前缀。当JAXBSample程序第一次收发XML文档时,它会在不使用
NamespacePrefixMapper
类的情况下执行此操作。因此,Marshaller会自动生成一个名称空间前缀,在本例中为ns2。的结构避免了命名空间重复的
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:JustAnElement xmlns:ns2="a">
<foo>true</foo>
</ns2:JustAnElement>
实施例:
由
JAXBSample
程序所做的第二编组使用NamespacePrefixMapper
类,如下所示:
NamespacePrefixMapper m = new PreferredMapper();
marshal(jc, e, m);
public static class PreferredMapper extends NamespacePrefixMapper {
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return "mappedNamespace" + namespaceUri;
}
}
getPreferredPrefix()
中的PreferredMapper
类中的方法将返回首选前缀,在此情况下,mappedNamespacea
将在编组XML的根元素处声明。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappedNamespacea:JustAnElement xmlns:mappedNamespacea="a">
<foo>true</foo>
</mappedNamespacea:JustAnElement>
另请参阅http://khylo.blogspot.com/2008/08/jaxb-multiple-namespace-definitions.html关于非直接相关的问题。 – VonC 2010-02-22 13:16:41
感谢您的回答。默认的NamespacePrefixMapper已经将“http://www.w3.org/2001/XMLSchema-instance”映射为“xsi”(请参阅http://j.mp/dkCcgC)。所以我认为没有办法使用NamespacePrefixMapper来更早地声明命名空间。 NamespaceContext看起来很有希望。尽管如此,我仍然试图弄清楚如何实现这一目标。 (无论如何+1为你的努力) – sfussenegger 2010-02-23 09:33:02
如果你感兴趣:3个月后,事实证明你的答案并不太远:正确的类,错误的方法(请参阅Dany的答案)。我无法相信在检查您的建议时我没有注意到getPreDeclaredNamespaceUris()方法。 – sfussenegger 2010-05-31 12:51:19
不是漂亮,但你可以添加一个空的schemaLocation根元素:
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");
的确不是*那*漂亮:)但至少它接近于正确的答案。 – sfussenegger 2010-03-01 09:07:40
我觉得它很华丽!我不得不继续使用较旧的JAXB 2.x继续使用Java 6运行时。随着您的更改,我只在根元素上获得架构声明,但不在任何子元素上。现在根元素看起来像这样:'
,如果你'重新使用Maven,然后将其添加到您的pom中:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
如果您配置上述示例中定义的注释,则无需使用PreferredMapper。虽然我有一个package-info.jave文件如下配置:
@javax.xml.bind.annotation.XmlSchema(
namespace = "mylovelynamespace1",
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "myns1", namespaceURI = "mylovelynamespace1"),
@javax.xml.bind.annotation.XmlNs(prefix = "myns2", namespaceURI = "mylovelynamespace2")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.mylovelycompanyname.package;
这是我在网上找到的最佳答案。
xsi:type
声明最有可能被创建,因为JAXBElement
的声明类型与值的类型不匹配。
如果ObjectFactory
有一个正确的JAXBElement
的创建方法,您应该使用它,因为它应该正确填充QName
和类型信息;否则,我会尝试设置JAXBElement
到String.class
(假定这是commentTest
的类型)的声明类型(第二构造函数arg)而不是CommentType.Comment
。
业主: cbrettin
您可以让命名空间只能写入一次。您将需要一个XMLStreamWriter的代理类和一个package-info.java。然后你会在你的代码做:
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
.newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());
Proxy类(重要的方法是 “和writeNamespace”):
class WrapperXMLStreamWriter implements XMLStreamWriter {
private final XMLStreamWriter writer;
public WrapperXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
//keeps track of what namespaces were used so that not to
//write them more than once
private List<String> namespaces = new ArrayList<String>();
public void init(){
namespaces.clear();
}
public void writeStartElement(String localName) throws XMLStreamException {
init();
writer.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
init();
writer.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
init();
writer.writeStartElement(prefix, localName, namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if(namespaces.contains(namespaceURI)){
return;
}
namespaces.add(namespaceURI);
writer.writeNamespace(prefix, namespaceURI);
}
// .. other delegation method, always the same pattern: writer.method() ...
}
包信息。Java的:
@XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
xmlns = {
@XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
通过这样添加您nsPrefix
映射:
marshaller.setNamespaceMapping("myns","urn:foo");
我使用jaxb2,这对我并不适用,抛出RuntimeException – arrehman 2012-03-02 19:51:44