如果命名空间声明在 SOAP 信封上,如何使用 JAXB 解组 SOAP 响应?
Posted
技术标签:
【中文标题】如果命名空间声明在 SOAP 信封上,如何使用 JAXB 解组 SOAP 响应?【英文标题】:How to unmarshall SOAP response using JAXB if namespace declaration is on SOAP envelope? 【发布时间】:2012-07-13 00:27:18 【问题描述】:只有在肥皂信封已被移除时,JAXB 才能解组 XML。但是,我试图解组的 SOAP 响应在肥皂信封上有其名称空间声明。如果我删除肥皂信封,命名空间声明也将被删除。因此,标签的前缀将指代无。这会导致 JAXB 引发错误。如果在 SOAP 信封上声明了命名空间,我如何使用 JAXB 解组 SOAP 响应?
下面是我需要解组的类似 XML 示例:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://example.com/">
<soapenv:Body>
<ns:getNumberResponse>
<number>123456789</number>
</ns:getNumberResponse>
</soapenv:Body>
</soapenv:Envelope>
如果我取下肥皂信封会发生这种情况:
<ns:getNumberResponse>
<number>123456789</number>
</ns:getNumberResponse>
如果我删除了肥皂封套,命名空间声明也将消失。 JAXB 将无法解组上述 xml,因为缺少前缀“ns”的命名空间定义。因此,它将返回 "The prefix "ns" for element "ns:getNumberResponse" is not bound."
错误消息。我该如何解决?
请注意,我使用的是 JDK 1.5。我不确定在 JDK 1.6 中是否有办法解决这个问题。我能够使用 JAXB,因为我下载了使用 JAXB 所需的 jar。
【问题讨论】:
【参考方案1】:您可以使用 StAX 解析器来解析 SOAP 消息,然后将 XMLStreamReader
推进到 getNumberResponse
元素并使用采用类参数的 unmarshal 方法。 StAX 解析器包含在 Java SE 6 中,但您需要为 Java SE 5 下载一个。Woodstox 是一种流行的 StAX 解析器。
回应
package forum11465653;
public class Response
private long number;
public long getNumber()
return number;
public void setNumber(long number)
this.number = number;
演示
XMLStreamReader
有一个NamespaceContext
。 NamespaceContext
知道当前级别的所有活动命名空间声明。您的 JAXB (JSR-222) 实现将能够利用它来获取必要的信息。
package forum11465653;
import java.io.FileReader;
import javax.xml.bind.*;
import javax.xml.stream.*;
public class Demo
public static void main(String[] args) throws Exception
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("src/forum11465653/input.xml"));
xsr.nextTag(); // Advance to Envelope tag
xsr.nextTag(); // Advance to Body tag
xsr.nextTag(); // Advance to getNumberResponse tag
System.out.println(xsr.getNamespaceContext().getNamespaceURI("ns"));
JAXBContext jc = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<Response> je = unmarshaller.unmarshal(xsr, Response.class);
System.out.println(je.getName());
System.out.println(je.getValue());
输出
http://example.com/
http://example.com/getNumberResponse
forum11465653.Response@781f6226
【讨论】:
嗨布莱斯!谢谢!我在blog.bdoughan.com/2010/12/case-insensitive-unmarshalling.html 上看到了您的教程,并尝试稍微修改您的代码以替换前缀而不是属性,但我收到了Message: could not determine namespace bound to element prefix ns
错误消息。我覆盖的方法是getAttributePrefix(int index)
。
@Arci - 你不需要覆盖任何东西。由于您正在解析整个文档,因此它将包含所有必要的命名空间信息。
对不起。我在那里感到困惑。我认为 StreamReaderDelegate 就像一个在解组 XML 之前调用的拦截器?根据您的示例,您将本地属性名称和属性名称更改为小写,以便在解组时始终相互匹配。所以我想我必须对前缀做同样的事情?如果在 xml 中找到名称空间前缀而没有其匹配的名称空间声明,我将无法解组它,因此我计划完全删除所有前缀。你能在这件事上给我更多的启发吗?
耶!谢谢!现在开始工作了!顺便说一句,我将您的代码从 XMLInputFactory.newFactory()
更改为 XMLInputFactory.newInstance()
。
@Arci - XMLInputFactory.newFactory()
现在是 XMLInputFactory.newInstance()
的首选 API,请参阅:docs.oracle.com/javase/6/docs/api/javax/xml/stream/…。【参考方案2】:
您可以使用 DOM 解析器解析整个 SOAP 消息,该解析器将能够在解析时解析所有名称空间。然后从生成的 DOM 树中提取您需要的元素并将其传递给 unmarshaller。
DomDemo
package forum11465653;
import java.io.File;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.*;
public class DomDemo
public static void main(String[] args) throws Exception
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.parse(new File("src/forum11465653/input.xml"));
Node getNumberResponseElt = d.getElementsByTagNameNS("http://example.com/", "getNumberResponse").item(0);
JAXBContext jc = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<Response> je = unmarshaller.unmarshal(new DOMSource(getNumberResponseElt), Response.class);
System.out.println(je.getName());
System.out.println(je.getValue());
输出
http://example.com/getNumberResponse
forum11465653.Response@19632847
【讨论】:
谢谢伊恩!我认为这只有在只有第一个标签元素使用命名空间前缀时才有效。如果会有很多带有命名空间前缀的内部标签,那么还需要为每个标签使用getElementsByTagName
。这将破坏解组器的目的。
XML 中的命名空间前缀是将正确的命名空间 URI 与正确的元素相关联的语法技巧。对像 JAXB 解组器这样的下游组件而言,唯一重要的是名称空间 URI 和本地名称(冒号后的位)——一旦你有了一个 DOM 树,每个级别的所有元素都完全解析了它们的名称空间,并且前缀映射是不再相关。
基本上 Blaise 和我都建议使用相同的解决方案,但使用不同的 XML API - 使用 XML 解析工具解析完整的信封文档,该工具具有所有可用的命名空间信息,但仅传递一点您对 JAXB 解组器感兴趣的 XML 树。
命名空间前缀映射只与原始解析器有关(DOM、StAX 等)。您解析整个信封并最终得到一个树结构,其中根元素是 http://schemas.xmlsoap.org/soap/envelope/
命名空间中的 Envelope
元素,在 http://schemas.xmlsoap.org/soap/envelope/
命名空间中包含 Body
元素,在http://example.com/
命名空间。这是解组器需要的所有信息,它不关心最初使用了什么前缀。您只需将 getNumberResponse
元素传递给 JAXB。
冒着听起来像一张卡住唱片的风险,不,它不会。如果它从 DOM Node
解组,那么它不关心前缀是什么。前缀映射仅在直接从原始 XML 中解组时才有意义。无论如何,听起来您已经使用 StAX 进行了工作。以上是关于如果命名空间声明在 SOAP 信封上,如何使用 JAXB 解组 SOAP 响应?的主要内容,如果未能解决你的问题,请参考以下文章