为什么在反序列化 SOAP 响应时总是得到一个不完整的对象?

Why do I consistently get an incomplete object when deserializing a SOAP response?

提问人:Nate Riche 提问时间:11/1/2023 最后编辑:Nate Riche 更新时间:11/2/2023 访问量:41

问:

我有一个 Java 8 Spring Boot,以前称为 WebLogic,它使用 Apache CXF 和 Apache Tomcat。它有几个 SOAP 端点,我想编写 Cucumber 场景来执行集成测试。这些场景使用 Spring 的 RestTemplate 对特定端点进行 HTTP 调用,以验证标头以及响应的部分。为此,我想将服务器的 XML 响应反序列化为 POJO,但我无法让对象正确反序列化。

到目前为止,我能得到的最好的是一个带有正文(反序列化的服务器响应对象)的 ResponseEntity,它只包含我期望的响应的一小部分。例如,我有 POJO(与端点返回的类相同),如下所示:

    public class MyServerReply implements Serializable {
    private static final long serialVersionUID = 1L;

    private ReplyInfo replyInfo;
    private String transactionId;
    private String version;
    private String environment;
    private ErrorStatus errorStatus;

    public MyServerReply() {
        // constructor stuff
    }

    // getters and setters go here
    }

始终如一地,我将按预期填充一个字段,而其他每个字段要么为空,要么为 0。使用具有相同请求正文的 Insomnia 或 Postman 等 HTTP 客户端,我可以看出服务器没有返回这个不完整的响应。同样,考虑我用于进行 HTTP 调用的代码:

    public ResponseEntity<MyServerReply> call_getReplyInfo(String arg1, String arg2) {
        String soapRequest = String.format(SampleRequestBodies.getReplyInfo, arg1, arg2);

        HttpEntity<MyServerReply> requestEntity = new HttpEntity<>(soapRequest, headers);
        return restTemplate.exchange(myServerUrl, HttpMethod.POST, requestEntity, MyServerReply.class);
    }

如果我修改此方法以返回 ResponseEntity,我可以获得预期的响应,但显然,这给了我一个 String 正文而不是我需要的 POJO。

此特定示例的终结点如下所示:

    @WebMethod(operationName = "getReplyInfo")
    @WebResult(name = "getReplyInfoMessage", targetNamespace = "http://myNameSpace.com/myApp")
    public MyServerReply getReplyInfo(InfoRequest reqObj) {
    try { 
        return myApi.getReplyInfo(reqObj); 
    } catch (Exception e) {
        // exception handling goes here
    }
    }

服务器响应(省略字段值)如下:

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:getReplyInfoResponse
            xmlns:ns2="http://myNameSpace.com/myApp">
            <ns2:getReplyInfoMessage>
                <replyInfo>
                    // bunch of response stuff
                </replyInfo>
                <transId></transId>
                <version>1.0.0</version>
                <environment>local</environment>
                <errorStatus>
                    // error status fields go here
                    <uuid>97983440-6c7b-4ca9-a139-328f2de7da1c</uuid> <--bizarrely, this is consistently the only field that is populated when deserializing
                </errorStatus>
            </ns2:getReplyInfoMessage>
        </ns2:getReplyInfoResponse>
    </soap:Body>
</soap:Envelope>

请注意,此应用程序的原始 WSDL 文件已丢失。

我已经尝试了几个修复程序。我尝试添加各种javax Xml注释,包括@XmlRootElement,@XmlType以及两者的组合,如下所示:

    import javax.xml.bind.annotation.XmlType;


    @XmlType(name = "getReplyInfoMessage")
    public class MyServerReply {

我还尝试用 @XmlElement 注释上述 POJO 的所有字段,以及没有成功的 getter。这个过程也对 ErrorStatus 和 ReplyInfo 类进行了,但无济于事。

我尝试将 MessageConverter,特别是 jackson-dataformat-xml 中的 MappingJackson2XmlHttpMessageConverter 添加到 restTemplate 并得到相同的不完整结果。

虽然我更希望 ResponseEntity 与我尝试反序列化的 POJO 类型相同,但一般来说,我也无法成功反序列化响应。使用来自同一 jackson 库的 XMLMapper 进行以下设置,我仍然得到一个不完整的对象:

    ResponseEntity<String> response = restTemplate.exchange(myServerUrl, HttpMethod.POST, requestEntity, String.class);

    XmlMapper xmlMapper = new XmlMapper();
    // This is required, since the response includes the outermost Envelope and Body elements and these elements are a part of the POJO I am deserializing to
    xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    // reply is not null, but it is mostly full of empty fields, excluding the uuid field for whatever reason
    reply = xmlMapper.readValue(response.getBody(), MyServerReply.class);

最后,虽然我不想使用这种方法,但我也尝试过使用 JAXB 进行编组/取消编组。但是,在这种情况下,我的请求对象不被服务器理解。我可以根据要求在这篇文章中包含该尝试的详细信息,但最初会省略它,因为它是一种不同类型的错误。

Java XML Spring SOAP

评论


答:

0赞 AztecCodes 11/1/2023 #1

修复 POJO 结构:
确保您的 ) 结构与您收到的结构准确对应。在您的情况下,它包含在信封和正文中,它包括特定的命名空间。
Plain Old Java Object (POJOXML responseXML responseSOAP

您可能需要创建一个充当容器的类来表示完整的 ,涵盖信封、正文和实际数据有效负载。概念:SOAP response

@XmlRootElement(name = "Envelope", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class SOAPEnvelope {

  @XmlElement(name = "Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
  private SOAPBody body;

  // Getters and Setters
}

@XmlAccessorType(XmlAccessType.FIELD)
public class SOAPBody {

  @XmlElement(name = "getReplyInfoResponse", namespace = "http://myNameSpace.com/myApp")
  private GetReplyInfoResponse getReplyInfoResponse;

  // Getters and Setters
}

@XmlAccessorType(XmlAccessType.FIELD)
public class GetReplyInfoResponse {

  @XmlElement(name = "getReplyInfoMessage", namespace = "http://myNameSpace.com/myApp")
  private MyServerReply getReplyInfoMessage;

  // Getters and Setters
}

接下来,您应该能够将完整的 SOAP 响应转换为类的实例。之后,您将能够检索从中命名的有效负载。SOAPEnvelopeMyServerReply

使用 JAX-RS 客户机进行 SOAP 调用

由于您正在处理服务,因此使用客户端可能更直接,这些客户端比 Spring 的 .SOAPJAX-RSSOAP-friendlyRestTemplate

您可以使用 with 来做出和处理响应。下面是一个示例:JAX-RS clientCXFSOAP calls

WebClient client = WebClient.create("http://your-soap-service-url");
client.accept("text/xml");
Response response = client.post(yourSOAPRequest);

SOAPEnvelope envelope = response.readEntity(SOAPEnvelope.class);

评论

0赞 Nate Riche 11/2/2023
你好!谢谢你的帮助。我能够使用您的建议成功反序列化。也就是说,我有大约六个端点,它们具有独特的响应,我需要反序列化。有没有办法在不创建一堆额外类的情况下推广这个过程?据我所知,除了其他元素之外,每个响应都需要一个独特的包络和主体类。
0赞 AztecCodes 11/6/2023
@NateRiche 不确定,对不起,但是只要合适,我看不出额外的课程有什么不好的。