第 4 章共享组件

在本章中,我们将探讨在客户端和服务器端 Spring-WS 开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此了解它们的作用很重要,即使您不直接使用它们。

4.1. Web 服务消息

4.1.1.WebServiceMessage

Spring Web Services 的核心接口之一是 WebServiceMessage。此接口表示与协议无关的 XML 消息。此接口包含以 javax.xml.transform.Sourcejavax.xml.transform.Result 形式提供对消息有效负载的访问的方法。SourceResult 是表示 XML 输入和输出抽象的标记接口。具体实现封装了各种 XML 表示形式,如下表所示。

Source/Result 实现封装 XML 表示形式
javax.xml.transform.dom.DOMSourceorg.w3c.dom.Node
javax.xml.transform.dom.DOMResultorg.w3c.dom.Node
javax.xml.transform.sax.SAXSourceorg.xml.sax.InputSourceorg.xml.sax.XMLReader
javax.xml.transform.sax.SAXResultorg.xml.sax.ContentHandler
javax.xml.transform.stream.StreamSource java.io.Filejava.io.InputStreamjava.io.Reader
javax.xml.transform.stream.StreamResult java.io.Filejava.io.OutputStreamjava.io.Writer

除了读取和写入有效负载之外,Web 服务消息还可以将自身写入输出流。

4.1.2.SoapMessage

SoapMessageWebServiceMessage 的子类。它包含 SOAP 特定的方法,例如获取 SOAP 头、SOAP 故障等。通常,您的代码不应依赖于 SoapMessage,因为可以通过 WebServiceMessage 中的 getPayloadSource()getPayloadResult() 获取 SOAP 正文(消息的有效负载)的内容。只有在需要执行 SOAP 特定的操作(例如添加头、获取附件等)时,才需要将 WebServiceMessage 转换为 SoapMessage

4.1.3. 消息工厂

具体消息实现由 WebServiceMessageFactory 创建。此工厂可以创建一个空消息或根据输入流读取消息。有两种 WebServiceMessageFactory 的具体实现;一种基于 SAAJ(Java 的 SOAP with Attachments API),另一种基于 Axis 2 的 AXIOM(AXis 对象模型)。

4.1.3.1. SaajSoapMessageFactory

SaajSoapMessageFactory 使用 SOAP with Attachments API for Java 来创建 SoapMessage 实现。SAAJJ2EE 1.4 的一部分,因此它应该受到大多数现代应用程序服务器的支持。以下是常见应用程序服务器提供的 SAAJ 版本概述

应用程序服务器SAAJ 版本
BEA WebLogic 81.1
BEA WebLogic 91.1/1.2[a]
IBM WebSphere 61.2
SUN Glassfish 11.3

[a] Weblogic 9 在 SAAJ 1.2 实现中存在已知错误:它实现了所有 1.2 接口,但在调用时会抛出 UnsupportedOperationException。Spring Web Services 有一个解决方法:在 WebLogic 9 上操作时,它使用 SAAJ 1.1。

此外,Java SE 6 包括 SAAJ 1.3。您可以像这样连接 SaajSoapMessageFactory

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />

注意

SAAJ 基于 DOM(文档对象模型)。这意味着所有 SOAP 消息都存储在 内存 中。对于较大的 SOAP 消息,这可能不太高效。在这种情况下,AxiomSoapMessageFactory 可能更适用。

4.1.3.2. AxiomSoapMessageFactory

AxiomSoapMessageFactory 使用 AXis 2 对象模型来创建 SoapMessage 实现。AXIOM 基于 StAX(XML 流式 API)。StAX 提供了一种基于拉取的机制来读取 XML 消息,这对于较大的消息可能更有效。

要提高 AxiomSoapMessageFactory 上的读取性能,您可以将 payloadCaching 属性设置为 false(默认值为 true)。这将直接从套接字流中读取 SOAP 正文的内容。启用此设置后,只能读取一次有效负载。这意味着您必须确保消息的任何预处理(日志记录等)不会消耗它。

您使用 AxiomSoapMessageFactory 如下

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

除了有效负载缓存外,AXIOM 还支持 StreamingWebServiceMessage 中定义的完整流消息。这意味着可以将有效负载直接设置在响应消息上,而不是写入 DOM 树或缓冲区。

当处理程序方法返回 JAXB2 支持的对象时,将使用 AXIOM 的完整流。它会自动将此编组对象设置到响应消息中,并在响应发出时将其写入到传出套接字流中。

有关完整流的更多信息,请参阅 StreamingWebServiceMessageStreamingPayload 的类级 Javadoc。

4.1.3.3. SOAP 1.1 或 1.2

SaajSoapMessageFactoryAxiomSoapMessageFactory 都具有 soapVersion 属性,您可以在其中注入 SoapVersion 常量。默认情况下,版本为 1.1,但您可以将其设置为 1.2,如下所示

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd">

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

在上面的示例中,我们定义了一个 SaajSoapMessageFactory,它只接受 SOAP 1.2 消息。

注意

尽管两个版本的 SOAP 在格式上非常相似,但 1.2 版本与 1.1 不向后兼容,因为它使用不同的 XML 命名空间。 SOAP 1.1 和 1.2 之间的其他主要区别包括错误的不同结构,以及 SOAPAction HTTP 标头实际上已弃用,尽管它们仍然有效。

需要注意的关于 SOAP 版本号或 WS-* 规范版本号的一件重要事情是,规范的最新版本通常不是最流行的版本。对于 SOAP,这意味着目前最佳版本是 1.1。1.2 版本将来可能变得更流行,但目前 1.1 是最安全的。

4.1.4. MessageContext

通常,消息成对出现:请求和响应。请求在客户端创建,通过某种传输发送到服务器端,在服务器端生成响应。此响应被发回客户端,在客户端中读取。

在 Spring Web Services 中,此类对话包含在 MessageContext 中,该上下文具有获取请求和响应消息的属性。在客户端,消息上下文由 WebServiceTemplate 创建。在服务器端,消息上下文从特定于传输的输入流中读取。例如,在 HTTP 中,它从 HttpServletRequest 中读取,并将响应写回 HttpServletResponse

4.2. TransportContext

SOAP 协议的一个关键属性是它尝试与传输无关。这就是 Spring-WS 不支持通过 HTTP 请求 URL 将消息映射到端点,而是通过消息内容映射的原因。

但是,有时需要在客户端或服务器端访问底层传输。为此,Spring Web Services 具有 TransportContext。传输上下文允许访问底层 WebServiceConnection,它通常是服务器端的 HttpServletConnection;或客户端端的 HttpUrlConnectionCommonsHttpConnection。例如,您可以在服务器端端点或拦截器中获取当前请求的 IP 地址,如下所示

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.3. 使用 XPath 处理 XML

处理 XML 的最佳方法之一是使用 XPath。引用 [effective-xml],第 35 项

 

XPath 是一种第四代声明语言,它允许您指定要处理的节点,而无需确切指定处理器应该如何导航到这些节点。XPath 的数据模型经过精心设计,可以准确支持几乎所有开发人员对 XML 的需求。例如,它合并所有相邻文本(包括 CDATA 部分中的文本),允许计算跳过注释和处理指令的值,并包含子元素和后代元素的文本,并且要求解析所有外部实体引用。在实践中,XPath 表达式往往对输入文档中意外但可能无关紧要的更改具有更强的鲁棒性。

 
 --Elliotte Rusty Harold

Spring Web Services 有两种方法可以在您的应用程序中使用 XPath:更快的 XPathExpression 或更灵活的 XPathTemplate

4.3.1. XPathExpression

XPathExpression 是对已编译 XPath 表达式的抽象,例如 Java 5 javax.xml.xpath.XPathExpression 或 Jaxen XPath 类。要在应用程序上下文中构造表达式,可以使用 XPathExpressionFactoryBean。这是一个使用此工厂 bean 的示例

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

上面的表达式不使用命名空间,但我们可以使用工厂 bean 的 namespaces 属性设置命名空间。可以在代码中使用表达式,如下所示

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

对于更灵活的方法,您可以使用 NodeMapper,它类似于 Spring 的 JDBC 支持中的 RowMapper。以下示例显示了如何使用它

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      // do something with list of Contact objects
   }
}

类似于在 Spring JDBC 的 RowMapper 中映射行,每个结果节点都使用匿名内部类进行映射。在这种情况下,我们创建一个 Contact 对象,我们稍后会使用它。

4.3.2. XPathTemplate

XPathExpression 只允许您计算单个预编译表达式。更灵活但更慢的替代方法是 XpathTemplate。此类遵循 Spring 中使用的通用模板模式(JdbcTemplate、JmsTemplate 等)。这是一个示例

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.4. 消息记录和跟踪

在开发或调试 Web 服务时,查看 (SOAP) 消息在到达时或在发送之前的内容非常有用。Spring Web Services 通过标准 Commons Logging 接口提供此功能。

注意

确保使用 Commons Logging 1.1 或更高版本。早期版本有类加载问题,并且不与 Log4J TRACE 级别集成。

要记录所有服务器端消息,只需将 org.springframework.ws.server.MessageTracing 记录器设置为 DEBUG 或 TRACE 级别。在调试级别,只记录有效负载根元素;在 TRACE 级别,记录整个消息内容。如果您只想记录已发送的消息,请使用 org.springframework.ws.server.MessageTracing.sent 记录器;或 org.springframework.ws.server.MessageTracing.received 记录器记录接收的消息。

在客户端,存在类似的记录器:org.springframework.ws.client.MessageTracing.sentorg.springframework.ws.client.MessageTracing.received

这是一个示例 log4j.properties 配置,它记录客户端上发送的消息的全部内容,而对于客户端接收的消息,只记录有效负载根元素。在服务器端,对于发送和接收的消息,都会记录有效负载根

log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG

log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n

使用此配置,典型的输出将是

TRACE [client.MessageTracing.sent] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ...
DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ...
DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...