如果命名空间声明在 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 有一个NamespaceContextNamespaceContext 知道当前级别的所有活动命名空间声明。您的 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 响应?的主要内容,如果未能解决你的问题,请参考以下文章

使用soap4r时出现问题,无法将命名空间、编码样式添加到soap信封

可能的SOAP版本不匹配

我对 SOAP 命名空间感到困惑

更改前缀命名空间 WCF 信封请求

如何自定义 SOAP Web 服务命名空间

如何删除Zend_Soap中的命名空间?