Spring-WS 提供了一个客户端 Web 服务 API,允许一致地通过 XML 访问 Web 服务。它也支持使用 marshaller 和 unmarshaller,这样你的服务层代码就可以完全处理 Java 对象。
org.springframework.ws.client.core 包提供了使用客户端访问 API 的核心功能。它包含简化 Web 服务使用的模板类,很像 Spring 核心的 JdbcTemplate
之于 JDBC。Spring 模板类共同的设计原则是提供辅助方法来执行常见操作,对于更复杂的使用,则委托给用户实现的 callback 接口。Web 服务模板遵循相同的设计。这些类提供了发送和接收 XML 消息、在发送前将对象 marshalling 到 XML,以及支持多种传输选项的各种便利方法。
WebServiceTemplate
是 Spring-WS 中客户端 Web 服务访问的核心类。它包含发送 Source
对象,以及接收响应消息作为 Source
或 Result
的方法。此外,它可以在通过传输发送对象之前将对象 marshalling 到 XML,并将任何响应 XML 再次 unmarshalling 为对象。
WebServiceTemplate
类使用 URI 作为消息目的地。你可以在模板本身上设置 defaultUri 属性,或在调用模板方法时显式提供 URI。URI 将被解析为 WebServiceMessageSender
,它负责通过传输层发送 XML 消息。你可以使用 WebServiceTemplate
类的 messageSender 或 messageSenders 属性设置一个或多个消息发送器。
WebServiceMessageSender
接口有两个实现用于通过 HTTP 发送消息。默认实现是 HttpUrlConnectionMessageSender
,它使用 Java 本身提供的功能。另一种是 HttpComponentsMessageSender
,它使用 Apache HttpComponents HttpClient。如果你需要更高级和易于使用的功能(例如身份验证、HTTP 连接池等),请使用后者。
要使用 HTTP 传输,可以将 defaultUri 设置为类似 http://example.com/services
的值,或为其中一个方法提供 uri
参数。
以下示例显示了 HTTP 传输如何使用默认配置
<beans> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> <constructor-arg ref="messageFactory"/> <property name="defaultUri" value="http://example.com/WebService"/> </bean> </beans>
以下示例显示了如何覆盖默认配置,以及如何使用 Apache HttpClient 进行 HTTP 身份验证
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> <constructor-arg ref="messageFactory"/> <property name="messageSender"> <bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender"> <property name="credentials"> <bean class="org.apache.http.auth.UsernamePasswordCredentials"> <constructor-arg value="john:secret"/> </bean> </property> </bean> </property> <property name="defaultUri" value="http://example.com/WebService"/> </bean>
对于通过 JMS 发送消息,Spring Web Services 提供了 JmsMessageSender
。该类使用 Spring 框架的功能将 WebServiceMessage
转换为 JMS Message
,在 Queue
或 Topic
上发送,并接收响应(如果有)。
要使用 JmsMessageSender
,你需要将 defaultUri 或 uri
参数设置为 JMS URI,该 URI 至少包含 jms:
前缀和目的地名称。一些 JMS URI 的示例是:jms:SomeQueue
、jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT
和 jms:RequestQueue?replyToName=ResponseName
。有关此 URI 语法的更多信息,请参考 JmsMessageSender
的类级别 Javadoc。
默认情况下,JmsMessageSender
发送 JMS BytesMessage
,但这可以通过在 JMS URI 上使用 messageType
参数来覆盖以使用 TextMessages
。例如:jms:Queue?messageType=TEXT_MESSAGE
。请注意,BytesMessages
是首选类型,因为 TextMessages
不能可靠地支持附件和字符编码。
以下示例显示了如何将 JMS 传输与 ActiveMQ 连接工厂结合使用
<beans> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="vm://localhost?broker.persistent=false"/> </bean> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> <constructor-arg ref="messageFactory"/> <property name="messageSender"> <bean class="org.springframework.ws.transport.jms.JmsMessageSender"> <property name="connectionFactory" ref="connectionFactory"/> </bean> </property> <property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/> </bean> </beans>
Spring Web Services 还提供了电子邮件传输,可用于通过 SMTP 发送 Web 服务消息,并通过 POP3 或 IMAP 检索它们。客户端电子邮件功能包含在 MailMessageSender
类中。此类从请求 WebServiceMessage
创建电子邮件消息,并通过 SMTP 发送。然后它等待响应消息到达传入的 POP3 或 IMAP 服务器。
要使用 MailMessageSender
,请将 defaultUri 或 uri
参数设置为 mailto
URI。以下是一些 URI 示例:mailto:[email protected]
和 mailto:server@localhost?subject=SOAP%20Test
。确保消息发送器已正确配置 transportUri(指示用于发送请求的服务器,通常是 SMTP 服务器)和 storeUri(指示轮询响应的服务器,通常是 POP3 或 IMAP 服务器)。
以下示例显示了如何使用电子邮件传输
<beans> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> <constructor-arg ref="messageFactory"/> <property name="messageSender"> <bean class="org.springframework.ws.transport.mail.MailMessageSender"> <property name="from" value="Spring-WS SOAP Client <[email protected]>"/> <property name="transportUri" value="smtp://client:[email protected]"/> <property name="storeUri" value="imap://client:[email protected]/INBOX"/> </bean> </property> <property name="defaultUri" value="mailto:[email protected]?subject=SOAP%20Test"/> </bean> </beans>
Spring Web Services 2.0 引入了 XMPP (Jabber) 传输,可用于通过 XMPP 发送和接收 Web 服务消息。客户端 XMPP 功能包含在 XmppMessageSender
类中。此类从请求 WebServiceMessage
创建 XMPP 消息,并通过 XMPP 发送。然后它侦听响应消息的到来。
要使用 XmppMessageSender
,请将 defaultUri 或 uri
参数设置为 xmpp
URI,例如 xmpp:[email protected]
。发送器还需要 XMPPConnection
才能工作,这可以使用 org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean
方便地创建。
以下示例显示了如何使用 xmpp 传输
<beans> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean"> <property name="host" value="jabber.org"/> <property name="username" value="username"/> <property name="password" value="password"/> </bean> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> <constructor-arg ref="messageFactory"/> <property name="messageSender"> <bean class="org.springframework.ws.transport.xmpp.XmppMessageSender"> <property name="connection" ref="connection"/> </bean> </property> <property name="defaultUri" value="xmpp:[email protected]"/> </bean> </beans>
WebServiceTemplate
包含许多发送和接收 Web 服务消息的便利方法。有些方法接受并返回 Source
,有些则返回 Result
。此外,还有将对象 marshalling 到 XML 和 unmarshalling 对象的方法。以下是一个向 Web 服务发送简单 XML 消息的示例。
import java.io.StringReader; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.springframework.ws.WebServiceMessageFactory; import org.springframework.ws.client.core.WebServiceTemplate; import org.springframework.ws.transport.WebServiceMessageSender; public class WebServiceClient { private static final String MESSAGE = "<message xmlns=\"http://tempuri.org\">Hello Web Service World</message>"; private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); public void setDefaultUri(String defaultUri) { webServiceTemplate.setDefaultUri(defaultUri); } // send to the configured default URI public void simpleSendAndReceive() { StreamSource source = new StreamSource(new StringReader(MESSAGE)); StreamResult result = new StreamResult(System.out); webServiceTemplate.sendSourceAndReceiveToResult(source, result); } // send to an explicit URI public void customSendAndReceive() { StreamSource source = new StreamSource(new StringReader(MESSAGE)); StreamResult result = new StreamResult(System.out); webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/AnotherWebService", source, result); } }
<beans xmlns="http://www.springframework.org/schema/beans"> <bean id="webServiceClient" class="WebServiceClient"> <property name="defaultUri" value="http://localhost:8080/WebService"/> </bean> </beans>
上述示例使用 WebServiceTemplate
向位于 http://localhost:8080/WebService
的 Web 服务发送一个 hello world 消息(在 simpleSendAndReceive()
方法的情况下),并将结果写入控制台。WebServiceTemplate
被注入了默认 URI,该 URI 被使用是因为在 Java 代码中没有显式提供 URI。
请注意,WebServiceTemplate
类一旦配置完成就是线程安全的(假设它的所有依赖项也是线程安全的,Spring-WS 附带的所有依赖项都是如此),因此如果需要,多个对象可以使用同一个共享的 WebServiceTemplate
实例。WebServiceTemplate
暴露了一个无参构造函数和 messageFactory/messageSender bean 属性,可用于构造实例(使用 Spring 容器或普通 Java 代码)。或者,考虑从 Spring-WS 的 WebServiceGatewaySupport
便利基类派生,它暴露了便利的 bean 属性以方便配置。(你不必继承这个基类...它仅作为便利类提供。)
为了方便发送普通 Java 对象,WebServiceTemplate
有许多 send(..)
方法,这些方法接受 Object
作为消息数据内容的参数。WebServiceTemplate
类中的 marshalSendAndReceive(..)
方法将请求对象到 XML 的转换委托给 Marshaller
,并将响应 XML 到对象的转换委托给 Unmarshaller
。(有关 marshalling 和 unmarshaller 的更多信息,请参考 Spring 文档。)通过使用 marshaller,你的应用程序代码可以专注于正在发送或接收的业务对象,而无需关心它如何表示为 XML 的细节。为了使用 marshalling 功能,你必须使用 WebServiceTemplate
类的 marshaller/unmarshaller 属性设置 marshaller 和 unmarshaller。
为了适应在消息上设置 SOAP 头和其他设置,WebServiceMessageCallback
接口允许你在消息创建之后、发送之前访问消息。下面的示例演示了如何在一个通过 marshalling 对象创建的消息上设置 SOAP Action 头。
public void marshalWithSoapActionHeader(MyObject o) { webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() { public void doWithMessage(WebServiceMessage message) { ((SoapMessage)message).setSoapAction("http://tempuri.org/Action"); } }); }
注意,你也可以使用 org.springframework.ws.soap.client.core.SoapActionCallback
来设置 SOAP Action 头。
除了 服务器端 WS-Addressing 支持之外,Spring Web Services 在客户端也支持此规范。
对于在客户端设置 WS-Addressing 头,你可以使用 org.springframework.ws.soap.addressing.client.ActionCallback
。这个 callback 将所需的 Action 头作为参数。它还有构造函数用于指定 WS-Addressing 版本和 To
头。如果未指定,To
头将默认为正在进行的连接的 URL。
以下是一个将 Action
头设置为 http://samples/RequestOrder
的示例
webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));
WebServiceMessageExtractor
接口是一个低级 callback 接口,允许你完全控制从接收到的 WebServiceMessage
中提取 Object
的过程。WebServiceTemplate
将在所提供的 WebServiceMessageExtractor
上调用 extractData(..)
方法,同时与服务资源的底层连接仍然打开。以下示例说明了 WebServiceMessageExtractor
的实际应用
public void marshalWithSoapActionHeader(final Source s) { final Transformer transformer = transformerFactory.newTransformer(); webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() { public void doWithMessage(WebServiceMessage message) { transformer.transform(s, message.getPayloadResult()); }, new WebServiceMessageExtractor() { public Object extractData(WebServiceMessage message) throws IOException // do your own transforms with message.getPayloadResult() // or message.getPayloadSource() } }); }
当涉及测试 Web 服务客户端(即使用 WebServiceTemplate
访问 Web 服务的类)时,有两种可能的方法
编写单元测试,简单地模拟 (mock) 掉 WebServiceTemplate
类、WebServiceOperations
接口或整个客户端类。
这种方法的优点是相当容易实现;缺点是不能真正测试在线路上发送的 XML 消息的确切内容,尤其是在模拟掉整个客户端类时。
编写集成测试,它确实测试消息的内容。
第一种方法可以使用 EasyMock、JMock 等模拟框架轻松实现。下一节将重点介绍如何编写集成测试,使用 Spring Web Services 2.0 中引入的测试功能。
Spring Web Services 2.0 引入了创建 Web 服务客户端集成测试的支持。在此上下文中,客户端是使用 WebServiceTemplate
访问 Web 服务的类。
集成测试支持位于 org.springframework.ws.test.client 包中。该包中的核心类是 MockWebServiceServer
。其基本思想是 Web 服务模板连接到此模拟服务器,向其发送请求消息,然后模拟服务器根据已注册的期望进行验证。如果满足期望,模拟服务器然后准备一个响应消息,并将其发送回模板。
MockWebServiceServer
的典型用法是
通过调用 MockWebServiceServer.createServer(WebServiceTemplate)
、MockWebServiceServer.createServer(WebServiceGatewaySupport)
或 MockWebServiceServer.createServer(ApplicationContext)
创建 MockWebServiceServer
实例。
通过调用 expect(RequestMatcher)
设置请求期望,可能使用 RequestMatchers
中提供的默认 RequestMatcher
实现(可以静态导入)。可以通过链式调用 andExpect(RequestMatcher)
来设置多个期望。
通过调用 andRespond(ResponseCreator)
创建适当的响应消息,可能使用 ResponseCreators
中提供的默认 ResponseCreator
实现(可以静态导入)。
正常使用 WebServiceTemplate
,可以直接使用或通过客户端代码使用。
调用 MockWebServiceServer.verify()
以确保所有期望都已满足。
请注意,MockWebServiceServer
(及相关类)提供了一个“流畅”的 API,因此你通常可以使用 IDE 中的代码完成功能(即 ctrl-space)来指导你完成模拟服务器的设置过程。
另请注意,在单元测试中,你依赖 Spring Web Services 中可用的标准日志记录功能。有时检查请求或响应消息以找出特定测试失败的原因可能会很有用。有关更多信息,请参阅第 4.4 节,“消息日志记录和跟踪”。
例如,考虑这个 Web 服务客户端类
import org.springframework.ws.client.core.support.WebServiceGatewaySupport; public class CustomerClient extends WebServiceGatewaySupport {public int getCustomerCount() { CustomerCountRequest request = new CustomerCountRequest();
request.setCustomerName("John Doe"); CustomerCountResponse response = (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request);
return response.getCustomerCount(); } }
| |
| |
|
CustomerClient
的典型测试如下所示
import javax.xml.transform.Source; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.xml.transform.StringSource; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import org.springframework.ws.test.client.MockWebServiceServer;import static org.springframework.ws.test.client.RequestMatchers.*;
import static org.springframework.ws.test.client.ResponseCreators.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("integration-test.xml")
public class CustomerClientIntegrationTest { @Autowired private CustomerClient client;
private MockWebServiceServer mockServer;
@Before public void createServer() throws Exception { mockServer = MockWebServiceServer.createServer(client); } @Test public void customerClient() throws Exception { Source requestPayload = new StringSource( "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" + "<customerName>John Doe</customerName>" + "</customerCountRequest>"); Source responsePayload = new StringSource( "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" + "<customerCount>10</customerCount>" + "</customerCountResponse>"); mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));
int result = client.getCustomerCount();
assertEquals(10, result);
mockServer.verify();
} }
| |
此测试使用 Spring Framework 中提供的标准测试设施。这不是必需的,但这通常是设置测试最简单的方法。 | |
| |
在 | |
我们通过调用 我们还通过调用 测试的这部分可能看起来有点令人困惑,但 IDE 的代码完成功能非常有帮助。输入 | |
我们在 | |
我们在 |
为了验证请求消息是否满足某些期望,MockWebServiceServer
使用 RequestMatcher
策略接口。此接口定义的契约非常简单
public interface RequestMatcher { void match(URI uri, WebServiceMessage request) throws IOException, AssertionError; }
你可以编写此接口的自己的实现,在消息不符合你的期望时抛出 AssertionError
,但你当然不必这样做。RequestMatchers
类提供了标准的 RequestMatcher
实现供你在测试中使用。你通常会静态导入这个类。
RequestMatchers
类提供以下请求匹配器
RequestMatchers 方法 | 描述 |
---|---|
anything() | 期望任何类型的请求。 |
payload() | 期望给定的请求有效载荷 (payload)。 |
validPayload() | 期望请求有效载荷 (payload) 根据给定的 XSD schema 进行验证。 |
xpath() | 期望给定的 XPath 表达式存在、不存在或计算为给定值。 |
soapHeader() | 期望请求消息中存在给定的 SOAP 头。 |
connectionTo() | 期望连接到给定的 URL。 |
你可以通过链式调用 andExpect()
来设置多个请求期望,如下所示
mockServer.expect(connectionTo("http://example.com")). andExpect(payload(expectedRequestPayload)). andExpect(validPayload(schemaResource)). andRespond(...);
有关 RequestMatchers
提供的请求匹配器的更多信息,请参考类级别 Javadoc。
当请求消息已验证并满足定义的期望时,MockWebServiceServer
将创建一个供 WebServiceTemplate
使用的响应消息。服务器为此目的使用 ResponseCreator
策略接口
public interface ResponseCreator { WebServiceMessage createResponse(URI uri, WebServiceMessage request, WebServiceMessageFactory messageFactory) throws IOException; }
同样,你可以编写此接口的自己的实现,使用消息工厂创建响应消息,但你当然不必这样做,因为 ResponseCreators
类提供了标准的 ResponseCreator
实现供你在测试中使用。你通常会静态导入这个类。
ResponseCreators
类提供以下响应
ResponseCreators 方法 | 描述 |
---|---|
withPayload() | 创建具有给定有效载荷 (payload) 的响应消息。 |
withError() | 在响应连接中创建错误。此方法让你有机会测试错误处理。 |
withException() | 从响应连接读取时抛出异常。此方法让你有机会测试异常处理。 |
withMustUnderstandFault() , withClientOrSenderFault() , withServerOrReceiverFault() 和 withVersionMismatchFault() | 创建具有给定 SOAP 错误的响应消息。此方法让你有机会测试错误处理。 |
有关 RequestMatchers
提供的请求匹配器的更多信息,请参考类级别 Javadoc。