7

(跟进这个问题:Getting raw XML response from Java web service client

我有一个 SOAP 消息处理程序,它能够获取 Web 服务响应的原始 XML。我需要将此 XML 放入 Web 服务客户端,以便在发送响应之前对响应执行一些 XSL 转换。我无法找到一种从 SOAP 处理程序获取数据的好方法,该处理程序捕获传入的消息,并使原始 XML 可用于生成的(来自 WSDL)Web 服务客户端。如果这甚至可行,有什么想法吗?


我想出了这样的事情:

public class CustomSOAPHandler implements javax.xml.ws.handler.soap.SOAPHandler<javax.xml.ws.handler.soap.SOAPMessageContext>
{
    private String myXML;
    public String getMyXML()
    {
        return myXML;
    }
    ...
    public boolean handleMessage(SOAPMessageContext context)
    {
        ...
        myXML = this.getRawXML(context.getMessage());
    }

    //elsewhere in the application:
    ...
    myService.doSomething(someRequest);
    for (Handler h: ((BindingProvider)myService).getBinding().getHandlerChain())
    {
        if (h instanceof CustomSOAPHandler )
        {
            System.out.println("HandlerResult: "+ ((CustomSOAPHandler )h).getMyXML());
        }
    }

在非常简单的测试中,这似乎有效。但这个解决方案有点像廉价的黑客攻击。我不喜欢将原始 XML 设置为链处理程序的成员,而且我直觉这违反了许多其他最佳实践。有没有人有更优雅的方式来做到这一点?

4

4 回答 4

4

这两个似乎对我有用的选择都记录在这里。我还没有收到关于使用 ThreadLocal 是否可以的回复,但我不明白为什么不应该这样。

我添加到原始问题的第二种方法是走处理程序的路线。在调试 WS 标注时,我注意到 invocationProperties 映射将 SOAP 响应作为 responseContext 对象中内部数据包结构的一部分,但似乎无法访问它。ResponseContext 是一组名称值对。但是,当我在这个位置阅读 ResponseContext 的源代码时,我看到get方法的代码有一个注释,如果找不到Application Scoped属性,则返回null,否则,它会从数据包invocationProperties中读取它,这似乎是我想要的。因此,我搜索了如何在键/值对上设置范围(Google:为 jaxws 设置应用程序范围属性),上下文正在低调地引入它,它在我引用的 jax-ws 规范中另一个线程。

我还阅读了一些关于数据包的信息,https://jax-ws.java.net/nonav/jax-ws-20-fcs/arch/com/sun/xml/ws/api/message/Packet.html

我希望这对你有一些意义。我担心如果 Web 服务调用导致 Soap FAULT,那么三个将无法使用 JAXB,并且我真的很想记录这个数据包,因为它是从支付网关返回的,而该网关迄今为止有一个数字未记录的结果。

祝你好运。

于 2013-05-23T15:46:26.217 回答
2

解决方案是使用 JAXB 将对象转换回 XML。我真的不想这样做,因为让 web 服务客户端接收 XML,将其转换为 POJO,只是将 POJO 转换回 XML 似乎是多余的,但它可以工作。

于 2012-08-24T15:56:30.260 回答
2

传递请求/响应消息体的处理程序示例:

public class MsgLogger implements SOAPHandler<SOAPMessageContext> {

    public static String REQEST_BODY = "com.evil.request";
    public static String RESPONSE_BODY = "com.evil.response";

    @Override
    public Set<QName> getHeaders() {
        return null;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        SOAPMessage msg = context.getMessage();
        Boolean beforeRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(32_000);
            context.getMessage().writeTo(baos);
            String key = beforeRequest ? REQEST_BODY : RESPONSE_BODY;
            context.put(key, baos.toString("UTF-8"));
            context.setScope(key, MessageContext.Scope.APPLICATION);
        } catch (SOAPException | IOException e) { }
        return true;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        return true;
    }

    @Override
    public void close(MessageContext context) { }
}

要注册处理程序并使用保留的属性:

BindingProvider provider = (BindingProvider) port;
List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
handlerChain.add(new MsgLogger());
bindingProvider.getBinding().setHandlerChain(handlerChain);

Req req = ...;
Rsp rsp = port.serviceCall(req); // call WS Port

// Access saved message bodies:
Map<String, Object> responseContext = provider.getResponseContext();
String reqBody = (String) responseContext.get(MsgLogger.REQEST_BODY);
String rspBody = (String) responseContext.get(MsgLogger.RESPONSE_BODY);

TL;博士

Metro JAX WS RI 文档说关于MessageContext.Scope.APPLICATION属性:

消息上下文对象还可以保存客户端或提供者设置的属性。例如,端口代理和调度对象都扩展了BindingProvider. 可以从两者中获取消息上下文对象来表示请求或响应上下文。处理程序可以读取请求上下文中设置的属性,并且处理程序可以在传递给它们的消息上下文对象上设置属性。如果这些属性与范围一起设置,MessageContext.Scope.APPLICATION那么它们将在客户端的响应上下文中可用。在服务器端,一个上下文对象被传递到一个Provider.

metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/api/message/Packet.java包含属性:

/**
 * Lazily created set of handler-scope property names.
 *
 * <p>
 * We expect that this is only used when handlers are present
 * and they explicitly set some handler-scope values.
 *
 * @see #getHandlerScopePropertyNames(boolean)
 */
private Set<String> handlerScopePropertyNames;

另一方面metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/client/ResponseContext.javaMapwith 的实现:

public boolean containsKey(Object key) {
    if(packet.supports(key))
        return packet.containsKey(key);    // strongly typed

    if(packet.invocationProperties.containsKey(key))
        // if handler-scope, hide it
        return !packet.getHandlerScopePropertyNames(true).contains(key);

    return false;
}

SOAPHandler我们可以将属性标记为APPLICATION而不是默认值MessageContext.Scope.HANDLER

/**
 * Property scope. Properties scoped as <code>APPLICATION</code> are
 * visible to handlers,
 * client applications and service endpoints; properties scoped as
 * <code>HANDLER</code>
 * are only normally visible to handlers.
 */
public enum Scope {APPLICATION, HANDLER};

经过:

/**
 * Sets the scope of a property.
 *
 * @param name Name of the property associated with the
 *             <code>MessageContext</code>
 * @param scope Desired scope of the property
 * @throws java.lang.IllegalArgumentException if an illegal
 *             property name is specified
 */
public void setScope(String name,  Scope scope);
于 2019-03-12T23:27:24.493 回答
1

作为替代方案,您可以将其放入ThreadLocal. 因此,您需要SOAPHandler@gavenkoa 描述的 (ty),但将其添加到ThreadLocal实例中,而不是肥皂上下文中。

于 2019-09-24T17:16:05.067 回答