Jaxb 在解组时忽略名称空间
Jaxb ignore the namespace on unmarshalling
我使用 Jaxb2 和 Spring。我正在尝试解组一些由我的 2 个客户发送的 XML。
到目前为止,我只需要处理一个客户发送了一些 xml 这样的:
<foo xmlns="com.acme">
<bar>[...]</bar>
<foo>
像这样绑定到 POJO:
@XmlType(name = "", propOrder = {"bar"})
@XmlRootElement(name = "Foo")
public class Foo {
@XmlElement(name = "Bar")
private String bar;
[...]
}
我发现之前的开发人员在解组器中对名称空间进行了硬编码以使其正常工作。
现在,第二个客户发送相同的 XML 但更改了命名空间!
<foo xmlns="com.xyz">
<bar>[...]</bar>
<foo>
显然,解组器无法解组,因为它期望一些 {com.acme}foo
而不是 {com.xyz}foo
。不幸的是,要求客户更改 XML 不是一个选项。
我试过的:
1) 在 application-context.xml
中,我搜索了一个允许我忽略命名空间但找不到的配置:
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan">
<list>
<value>com.mycompany.mypkg</value>
</list>
</property>
<property name="marshallerProperties">
<map>
<entry key="???"><value type="java.lang.Boolean">false</value></entry>
</map>
</property>
</bean>
似乎唯一可用的选项是 Jaxb2Marshaller 的 Javadoc 中列出的选项:
/**
* Set the JAXB {@code Marshaller} properties. These properties will be set on the
* underlying JAXB {@code Marshaller}, and allow for features such as indentation.
* @param properties the properties
* @see javax.xml.bind.Marshaller#setProperty(String, Object)
* @see javax.xml.bind.Marshaller#JAXB_ENCODING
* @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT
* @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION
* @see javax.xml.bind.Marshaller#JAXB_SCHEMA_LOCATION
*/
public void setMarshallerProperties(Map<String, ?> properties) {
this.marshallerProperties = properties;
}
2) 我还尝试在代码中配置解组器:
try {
jc = JAXBContext.newInstance("com.mycompany.mypkg");
Unmarshaller u = jc.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);//Tried this option.
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlFile.toFile());
u.unmarshal(new DOMSource(doc));
return (Foo)u.unmarshal(new StreamSource(xmlFile.toFile()));
} catch (ParserConfigurationException | SAXException | IOException | JAXBException e) {
LOGGER.error("Erreur Unmarshalling CPL");
}
3) SAXParser 的不同形式:
try {
jc = JAXBContext.newInstance("com.mycompany.mypkg");
Unmarshaller um = jc.createUnmarshaller();
final SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
final XMLReader reader = sax.newSAXParser().getXMLReader();
final Source er = new SAXSource(reader, new InputSource(new FileReader(xmlFile.toFile())));
return (Foo)um.unmarshal(er);
}catch(...) {[...]}
这个有效!但是,我仍然希望能够自动装配 Unmarshaller,而不需要每次都需要这个丑陋的 conf。
Namesapce 意识是文档的特征 reader/builder/parser 而不是编组器。 XML 来自不同命名空间的元素代表不同的实体 == 对象,因此编组器不能忽略它们。
您正确地关闭了 SAX reader 中的名称空间,正如您所说的那样有效。我不明白你的问题,你的编组器仍然可以注入,区别在于获取输入数据。
文档生成器的相同技巧也应该有效(稍后我将对其进行测试),我怀疑您仍在使用带有 "hardcoded" 命名空间的编组器,但您的文档没有命名空间。
在我的项目中,我使用 XSLT 来解决类似的问题。设置名称空间意识绝对是更简单的解决方案。但是,使用 XSLT 我可以有选择地只删除一些名称空间,而且我的输入 xml 并不总是相同(忽略名称空间),有时我必须重命名几个元素,所以 XSLT 给了我这种额外的灵活性。
要删除命名空间,您可以使用这样的 xslt 模板:
<xsl:stylesheet version="1.0" xmlns:e="http://timet.dom.robust.ed" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="text() | processing-instruction() | comment()">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
然后在 Java 解组之前我转换输入数据:
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
Source source = new DOMSource(xml);
DOMResult result = new DOMResult();
transformer.transform(source, result);
谢谢大家,在这里分享了我的解决方案,它适用于我的代码,我尝试使它通用,每个名称空间都包含“:”我编写代码,如果任何标签有“:”,它将从 xml 中删除, 这用于在使用 jaxb 解组期间跳过命名空间。
public class NamespaceFilter {
private NamespaceFilter() {
}
private static final String COLON = ":";
public static XMLReader nameSpaceFilter() throws SAXException {
XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
private boolean skipNamespace;
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.indexOf(COLON) > -1) {
skipNamespace = true;
} else {
skipNamespace = false;
super.startElement("", localName, qName, atts);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.indexOf(COLON) > -1) {
skipNamespace = true;
} else {
skipNamespace = false;
super.endElement("", localName, qName);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (!skipNamespace) {
super.characters(ch, start, length);
}
}
};
return xr;
}
}
用于解组
XMLReader xr = NamespaceFilter.nameSpaceFilter();
Source src = new SAXSource(xr, new InputSource("C:\Users\binal\Desktop\response.xml"));
StringWriter sw = new StringWriter();
Result res = new StreamResult(sw);
TransformerFactory.newInstance().newTransformer().transform(src, res);
JAXBContext jc = JAXBContext.newInstance(Tab.class);
Unmarshaller u = jc.createUnmarshaller();
String done = sw.getBuffer().toString();
StringReader reader = new StringReader(done);
Tab tab = (Tab) u.unmarshal(reader);
System.out.println(tab);
`
我已经按照 Java 文档处理 xml 中的名称空间,同时解组,它成功了。
JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
Unmarshaller u = jc.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File( "nosferatu.xml"));
//If just a string
//InputSource is = new InputSource(new StringReader(line));
//Document doc = db.parse(is)
Object o = u.unmarshal( doc );
使用了 Java API JAXB 上下文和解组功能。
https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/Unmarshaller.html
我使用 Jaxb2 和 Spring。我正在尝试解组一些由我的 2 个客户发送的 XML。
到目前为止,我只需要处理一个客户发送了一些 xml 这样的:
<foo xmlns="com.acme">
<bar>[...]</bar>
<foo>
像这样绑定到 POJO:
@XmlType(name = "", propOrder = {"bar"})
@XmlRootElement(name = "Foo")
public class Foo {
@XmlElement(name = "Bar")
private String bar;
[...]
}
我发现之前的开发人员在解组器中对名称空间进行了硬编码以使其正常工作。
现在,第二个客户发送相同的 XML 但更改了命名空间!
<foo xmlns="com.xyz">
<bar>[...]</bar>
<foo>
显然,解组器无法解组,因为它期望一些 {com.acme}foo
而不是 {com.xyz}foo
。不幸的是,要求客户更改 XML 不是一个选项。
我试过的:
1) 在 application-context.xml
中,我搜索了一个允许我忽略命名空间但找不到的配置:
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan">
<list>
<value>com.mycompany.mypkg</value>
</list>
</property>
<property name="marshallerProperties">
<map>
<entry key="???"><value type="java.lang.Boolean">false</value></entry>
</map>
</property>
</bean>
似乎唯一可用的选项是 Jaxb2Marshaller 的 Javadoc 中列出的选项:
/**
* Set the JAXB {@code Marshaller} properties. These properties will be set on the
* underlying JAXB {@code Marshaller}, and allow for features such as indentation.
* @param properties the properties
* @see javax.xml.bind.Marshaller#setProperty(String, Object)
* @see javax.xml.bind.Marshaller#JAXB_ENCODING
* @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT
* @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION
* @see javax.xml.bind.Marshaller#JAXB_SCHEMA_LOCATION
*/
public void setMarshallerProperties(Map<String, ?> properties) {
this.marshallerProperties = properties;
}
2) 我还尝试在代码中配置解组器:
try {
jc = JAXBContext.newInstance("com.mycompany.mypkg");
Unmarshaller u = jc.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);//Tried this option.
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlFile.toFile());
u.unmarshal(new DOMSource(doc));
return (Foo)u.unmarshal(new StreamSource(xmlFile.toFile()));
} catch (ParserConfigurationException | SAXException | IOException | JAXBException e) {
LOGGER.error("Erreur Unmarshalling CPL");
}
3) SAXParser 的不同形式:
try {
jc = JAXBContext.newInstance("com.mycompany.mypkg");
Unmarshaller um = jc.createUnmarshaller();
final SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
final XMLReader reader = sax.newSAXParser().getXMLReader();
final Source er = new SAXSource(reader, new InputSource(new FileReader(xmlFile.toFile())));
return (Foo)um.unmarshal(er);
}catch(...) {[...]}
这个有效!但是,我仍然希望能够自动装配 Unmarshaller,而不需要每次都需要这个丑陋的 conf。
Namesapce 意识是文档的特征 reader/builder/parser 而不是编组器。 XML 来自不同命名空间的元素代表不同的实体 == 对象,因此编组器不能忽略它们。
您正确地关闭了 SAX reader 中的名称空间,正如您所说的那样有效。我不明白你的问题,你的编组器仍然可以注入,区别在于获取输入数据。
文档生成器的相同技巧也应该有效(稍后我将对其进行测试),我怀疑您仍在使用带有 "hardcoded" 命名空间的编组器,但您的文档没有命名空间。
在我的项目中,我使用 XSLT 来解决类似的问题。设置名称空间意识绝对是更简单的解决方案。但是,使用 XSLT 我可以有选择地只删除一些名称空间,而且我的输入 xml 并不总是相同(忽略名称空间),有时我必须重命名几个元素,所以 XSLT 给了我这种额外的灵活性。
要删除命名空间,您可以使用这样的 xslt 模板:
<xsl:stylesheet version="1.0" xmlns:e="http://timet.dom.robust.ed" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="text() | processing-instruction() | comment()">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
然后在 Java 解组之前我转换输入数据:
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
Source source = new DOMSource(xml);
DOMResult result = new DOMResult();
transformer.transform(source, result);
谢谢大家,在这里分享了我的解决方案,它适用于我的代码,我尝试使它通用,每个名称空间都包含“:”我编写代码,如果任何标签有“:”,它将从 xml 中删除, 这用于在使用 jaxb 解组期间跳过命名空间。
public class NamespaceFilter {
private NamespaceFilter() {
}
private static final String COLON = ":";
public static XMLReader nameSpaceFilter() throws SAXException {
XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
private boolean skipNamespace;
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.indexOf(COLON) > -1) {
skipNamespace = true;
} else {
skipNamespace = false;
super.startElement("", localName, qName, atts);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.indexOf(COLON) > -1) {
skipNamespace = true;
} else {
skipNamespace = false;
super.endElement("", localName, qName);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (!skipNamespace) {
super.characters(ch, start, length);
}
}
};
return xr;
}
}
用于解组
XMLReader xr = NamespaceFilter.nameSpaceFilter();
Source src = new SAXSource(xr, new InputSource("C:\Users\binal\Desktop\response.xml"));
StringWriter sw = new StringWriter();
Result res = new StreamResult(sw);
TransformerFactory.newInstance().newTransformer().transform(src, res);
JAXBContext jc = JAXBContext.newInstance(Tab.class);
Unmarshaller u = jc.createUnmarshaller();
String done = sw.getBuffer().toString();
StringReader reader = new StringReader(done);
Tab tab = (Tab) u.unmarshal(reader);
System.out.println(tab);
`
我已经按照 Java 文档处理 xml 中的名称空间,同时解组,它成功了。
JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
Unmarshaller u = jc.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File( "nosferatu.xml"));
//If just a string
//InputSource is = new InputSource(new StringReader(line));
//Document doc = db.parse(is)
Object o = u.unmarshal( doc );
使用了 Java API JAXB 上下文和解组功能。 https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/Unmarshaller.html