在本章中,我们将探讨在客户端和服务器端 Spring-WS 开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此理解它们的作用非常重要,即使你可能不直接使用它们。
Spring Web Services 的核心接口之一是 WebServiceMessage
。此接口代表协议无关的 XML 消息。接口包含提供访问消息负载的方法,形式为 javax.xml.transform.Source
或 javax.xml.transform.Result
。Source
和 Result
是标记接口,它们表示 XML 输入和输出的抽象。具体实现包装了各种 XML 表示形式,如下表所示。
Source/Result 实现 | 包装的 XML 表示形式 |
---|---|
javax.xml.transform.dom.DOMSource | org.w3c.dom.Node |
javax.xml.transform.dom.DOMResult | org.w3c.dom.Node |
javax.xml.transform.sax.SAXSource | org.xml.sax.InputSource 和 org.xml.sax.XMLReader |
javax.xml.transform.sax.SAXResult | org.xml.sax.ContentHandler |
javax.xml.transform.stream.StreamSource |
java.io.File 、java.io.InputStream 或 java.io.Reader |
javax.xml.transform.stream.StreamResult
|
java.io.File 、java.io.OutputStream 或 java.io.Writer |
除了从负载读取和写入负载外,Web Service 消息还可以将自身写入输出流。
SoapMessage
是 WebServiceMessage
的子类。它包含 SOAP 特有的方法,例如获取 SOAP 头、SOAP 错误等。通常,你的代码不应依赖于 SoapMessage
,因为 SOAP Body 的内容(即消息的负载)可以通过 WebServiceMessage
中的 getPayloadSource()
和 getPayloadResult()
方法获得。只有在需要执行 SOAP 特定操作(例如添加头、获取附件等)时,才需要将 WebServiceMessage
转换为 SoapMessage
。
具体的消息实现由 WebServiceMessageFactory
创建。此工厂可以创建空消息,或基于输入流读取消息。有 WebServiceMessageFactory
的两个具体实现;一个基于 SAAJ(SOAP with Attachments API for Java),另一个基于 Axis 2 的 AXIOM(AXis Object Model)。
SaajSoapMessageFactory
使用 SOAP with Attachments API for Java 来创建 SoapMessage
实现。SAAJ是J2EE1.4 的一部分,因此它应该在大多数现代应用服务器下受到支持。以下是SAAJ常见应用服务器提供的版本概览
应用服务器 | SAAJ版本 |
---|---|
BEA WebLogic 8 | 1.1 |
BEA WebLogic 9 | 1.1/1.2[a] |
IBM WebSphere 6 | 1.2 |
SUN Glassfish 1 | 1.3 |
[a] Weblogic 9 在 1.2 实现中存在一个已知 bug:它实现了所有 1.2 接口,但在调用时会抛出 |
此外,Java SE 6 包含SAAJ1.3。你可以像这样配置一个 SaajSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJSAAJ 基于 DOM(Document Object Model)。这意味着所有 SOAP 消息都 存储在内存中。对于较大的 SOAP 消息,这可能性能不高。在这种情况下,AxiomSoapMessageFactory
可能更适用。
AxiomSoapMessageFactory
使用 AXis 2 Object Model 来创建 SoapMessage
实现。AXIOM基于StAX(Streaming API for XML)。StAX 提供了一种基于拉取(pull-based)机制来读取 XML 消息,这对于较大的消息来说效率更高。
为了提高 AxiomSoapMessageFactory
的读取性能,你可以将 payloadCaching 属性设置为 false(默认为 true)。这将直接从 socket 流中读取 SOAP body 的内容。启用此设置后,负载只能读取一次。这意味着你必须确保消息的任何预处理(日志记录等)不会消耗掉它。
你可以按如下方式使用 AxiomSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory"> <property name="payloadCaching" value="true"/> </bean>
除了负载缓存,AXIOMAXIOM 还支持完整的流式消息,如 StreamingWebServiceMessage
中所定义。这意味着负载可以直接设置在响应消息上,而不是写入 DOM 树或缓冲区。
对于AXIOMAXIOM 的完整流式处理,当处理方法返回一个JAXB2支持的对象时使用。它会自动将此经过 marshal 的对象设置到响应消息中,并在响应发出时将其写入输出 socket 流。
有关完整流式处理的更多信息,请参阅 StreamingWebServiceMessage
和 StreamingPayload
的类级 Javadoc。
SaajSoapMessageFactory
和 AxiomSoapMessageFactory
都有一个 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>
在上面的示例中,我们定义了一个只接受SOAPSOAP 1.2 消息的 SaajSoapMessageFactory
。
尽管SOAPSOAP 的两个版本格式非常相似,但 1.2 版本与 1.1 不向后兼容,因为它使用了不同的 XML 命名空间。SOAPSOAP1.1 和 1.2 之间的其他主要区别包括 Fault 的结构不同,以及 SOAPAction
HTTP 头实际上已被弃用(尽管它们仍然有效)。
关于SOAPSOAP 版本号,或更笼统地说,关于 WS-* 规范版本号,需要注意的一点是,规范的最新版本通常不是最流行的版本。对于SOAPSOAP,这意味着目前最好的版本是 1.1。将来 1.2 版本可能会变得更流行,但目前 1.1 是最安全的选择。
通常,消息成对出现:请求和响应。请求在客户端创建,通过某种传输发送到服务器端,服务器端生成响应。此响应被发送回客户端,并在客户端读取。
在 Spring Web Services 中,此类对话包含在 MessageContext
中,它具有获取请求和响应消息的属性。在客户端,消息上下文由 WebServiceTemplate
创建。在服务器端,消息上下文从特定于传输的输入流中读取。例如,在 HTTP 中,它从 HttpServletRequest
读取,并将响应写回 HttpServletResponse
。
SOAP 协议的一个关键特性是它试图实现传输无关。这就是为什么 Spring-WS 不通过 HTTP 请求 URL 映射消息到端点,而是通过消息内容进行映射的原因。
然而,有时需要访问底层传输,无论是在客户端还是服务器端。为此,Spring Web Services 提供了 TransportContext
。传输上下文允许访问底层的 WebServiceConnection
,这通常在服务器端是 HttpServletConnection
;或在客户端是 HttpUrlConnection
或 CommonsHttpConnection
。例如,你可以在服务器端端点或拦截器中像这样获取当前请求的 IP 地址:
TransportContext context = TransportContextHolder.getTransportContext(); HttpServletConnection connection = (HttpServletConnection )context.getConnection(); HttpServletRequest request = connection.getHttpServletRequest(); String ipAddress = request.getRemoteAddr();
处理 XML 的最佳方法之一是使用 XPath。引用 [effective-xml],第 35 条:
XPath 是一种第四代声明性语言,它允许您指定要处理哪些节点,而无需指定处理器应如何准确导航到这些节点。XPath 的数据模型设计得非常好,能够支持几乎所有开发人员对 XML 所期望的内容。例如,它合并所有相邻文本(包括 CDATA 段中的文本),允许计算跳过注释和处理指令的值,并包括来自子元素和后代元素的文本,并要求解析所有外部实体引用。实际上,XPath 表达式往往对输入文档中意外但可能不重要的更改更加健壮。 | ||
--Elliotte Rusty Harold |
Spring Web Services 有两种在应用程序中使用 XPath 的方式:更快的 XPathExpression
或更灵活的 XPathTemplate
。
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
对象,我们稍后会使用它。
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
}
}
在开发或调试 Web service 时,查看 (SOAP) 消息到达时或发送前的内容非常有用。Spring Web Services 通过标准的 Commons Logging 接口提供了此功能。
请确保使用 Commons Logging 1.1 或更高版本。早期版本存在类加载问题,并且不与 Log4J TRACE 级别集成。
要记录所有服务器端消息,只需将 org.springframework.ws.server.MessageTracing
logger 设置为 DEBUG 或 TRACE 级别。在 DEBUG 级别,只记录负载根元素;在 TRACE 级别,记录整个消息内容。如果你只想记录已发送消息,请使用 org.springframework.ws.server.MessageTracing.sent
logger;或使用 org.springframework.ws.server.MessageTracing.received
来记录已接收消息。
在客户端,存在类似的 logger:org.springframework.ws.client.MessageTracing.sent
和 org.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] ...