第 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 Headers、SOAP Faults 等。通常,您的代码不应依赖于 SoapMessage,因为可以通过 WebServiceMessage 中的 getPayloadSource()getPayloadResult() 来获取 SOAP Body(消息负载)的内容。只有在需要执行 SOAP 特定的操作时,例如添加标头、获取附件等,才需要将 WebServiceMessage 强制转换为 SoapMessage

4.1.3。消息工厂

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

4.1.3.1。SaajSoapMessageFactory

SaajSoapMessageFactory 使用 Java 的 SOAP with Attachments API 创建 SoapMessage 实现。SAAJJ2EE1.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 在SAAJ1.2 实现中有一个已知的 bug:它实现了所有 1.2 接口,但在调用时会抛出 UnsupportedOperationException。Spring Web Services 有一个解决方法:它在 WebLogic 9 上运行时使用SAAJ1.1。

此外,Java SE 6 包括SAAJ1.3。您可以通过以下方式配置 SaajSoapMessageFactory

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

备注

SAAJ基于 DOM,即 Document Object Model。这意味着所有 SOAP 消息都存储在内存中。对于较大的 SOAP 消息,这可能不太高效。在这种情况下,AxiomSoapMessageFactory 可能更适用。

4.1.3.2。AxiomSoapMessageFactory

AxiomSoapMessageFactory 使用 AXis 2 Object Model 创建 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 树或缓冲区。

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

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

4.1.3.3. SOAP1.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>

在上面的示例中,我们定义了一个只接受SOAP1.2 消息的 SaajSoapMessageFactory

注意

尽管SOAP的两个版本在格式上非常相似,但 1.2 版本与 1.1 不向后兼容,因为它使用了不同的 XML 命名空间。其他主要区别包括SOAP1.1 和 1.2 的 Fault 结构不同,以及 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 服务时,查看消息到达时或发送之前的内容可能非常有用。Spring Web Services 通过标准的 Commons Logging 接口提供此功能。

注意

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

要记录所有服务器端消息,只需将 org.springframework.ws.server.MessageTracing 日志记录器设置为 DEBUG 或 TRACE 级别。在 debug 级别,只记录负载的根元素;在 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] ...
© . This site is unofficial and not affiliated with VMware.