在本章中,我们将探讨在客户端和服务器端 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 服务消息还可以将自身写入输出流。
SoapMessage 是 WebServiceMessage 的子类。它包含 SOAP 特定的方法,例如获取 SOAP Headers、SOAP Faults 等。通常,您的代码不应依赖于 SoapMessage,因为可以通过 WebServiceMessage 中的 getPayloadSource() 和 getPayloadResult() 来获取 SOAP Body(消息负载)的内容。只有在需要执行 SOAP 特定的操作时,例如添加标头、获取附件等,才需要将 WebServiceMessage 强制转换为 SoapMessage。
具体的实现消息是由 WebServiceMessageFactory 创建的。此工厂可以创建一个空消息,或根据输入流读取消息。WebServiceMessageFactory 有两个具体的实现;一个基于 SAAJ,即 Java 的 SOAP with Attachments API,另一个基于 Axis 2 的 AXIOM,即 AXis Object Model。
SaajSoapMessageFactory 使用 Java 的 SOAP with Attachments API 创建 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 在SAAJ1.2 实现中有一个已知的 bug:它实现了所有 1.2 接口,但在调用时会抛出 | |
此外,Java SE 6 包括SAAJ1.3。您可以通过以下方式配置 SaajSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ基于 DOM,即 Document Object Model。这意味着所有 SOAP 消息都存储在内存中。对于较大的 SOAP 消息,这可能不太高效。在这种情况下,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-支持的对象时使用。它将自动将此已编组的对象设置到响应消息中,并在响应外出时将其写入出站套接字流。
有关完整流式处理的更多信息,请参阅 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>
在上面的示例中,我们定义了一个只接受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 是最安全的选择。
通常,消息成对出现:请求和响应。请求在客户端创建,通过某种传输发送到服务器端,然后在服务器端生成响应。此响应发送回客户端,并在客户端读取。
在 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 服务时,查看消息到达时或发送之前的内容可能非常有用。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.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] ...