Spring-WebService2-秘籍-全-

Spring WebService2 秘籍(全)

原文:zh.annas-archive.org/md5/1F0369E05A9E0B8B44E275BC989E8AD8

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Spring Web 服务(Spring-WS)由 SpringSource 社区(www.springsource.org/)介绍,旨在创建基于契约的 SOAP Web 服务,其中主要需要 WSDL 或 XSD 来创建 Web 服务。由于 Spring-WS 是基于 Spring 的产品,它利用了 Spring 的概念,如控制反转(IOC)和依赖注入。Spring-WS 的一些关键特性包括:

  • 强大的端点映射:根据负载、SOAP 操作和 XPath 表达式,传入的 XML 请求可以转发到任何处理程序对象

  • 丰富的 XML API 支持:可以使用各种 Java 的 XML API(如 DOM、JDOM、dom4j 等)读取传入的 XML 消息

  • 由 Maven 构建:Spring-WS 可以轻松集成到您的 Maven 项目中

  • 支持编组技术:可以使用多种 OXM 技术,如 JAXB、XMLBean、XStream 和 JiBX,用于将 XML 消息转换为对象/从对象转换为 XML

  • 安全支持:安全操作,如加密/解密、签名和认证

覆盖 Spring-WS 2.x 的所有关键特性一直是本书的主要目标。

然而,在最后两章中,详细介绍了使用 REST 风格和使用 Spring 远程调用功能进行基于契约的 Web 服务开发的不同方法。

本书内容

第一章,构建 SOAP Web 服务: 本章涵盖了在 HTTP、JMS、XMPP 和电子邮件协议上设置 SOAP Web 服务。它还涵盖了使用 DOM、JDOM、XPath 和 Marshaller 等技术来实现 Web 服务端点的不同实现。

第二章,为 SOAP Web 服务构建客户端: 本章涵盖了使用 Spring-WS 模板类在 HTTP、JMS、XMPP 和电子邮件协议上构建 SOAP Web 服务客户端。

第三章,测试和监控 Web 服务: 本章解释了如何使用 Spring-WS 的最新功能测试 Web 服务,并使用诸如 soapUI 和 TCPMon 之类的工具监控 Web 服务。

第四章,异常/SOAP 故障处理: 本章解释了在应用程序/系统故障的情况下的异常处理。

第五章,SOAP 消息的日志记录和跟踪: 在本章中,我们将看到如何记录重要事件并跟踪 Web 服务。

第六章,编组和对象-XML 映射(OXM): 我们将在本章中讨论编组/解组技术以及创建自定义编组器。

第七章,使用 XWSS 库保护 SOAP Web 服务: 本章涵盖了安全主题,如加密、解密、数字签名认证和使用基于 XWSS 的 Spring-WS 功能进行授权,并介绍了创建密钥库的方法。

第八章,使用 WSS4J 库保护 SOAP Web 服务: 在本章中,我们将看到安全主题,如加密、解密、数字签名认证和使用基于 WSS4J 包的 Spring-WS 功能进行授权。

第九章,RESTful Web 服务: 本章解释了如何使用 Spring 中的 RESTful 支持开发 REST Web 服务。

第十章,Spring 远程调用: 我们将讨论使用 Spring 远程调用功能进行基于契约的 Web 服务开发,以将本地业务服务公开为使用 Hessian/Burlap、JAX-WS、JMS 的 Web 服务,并使用 JAX-WS API 通过 Apache CXF 设置 Web 服务的方法。

您需要什么来阅读本书

Java 知识以及基本的 Maven 知识是必备的。具有 Web 服务经验可以使您更容易地在开发环境中使用配方。本书中的基本配方帮助初学者快速学习 Web 服务主题。

这本书适合谁

这本书适用于那些具有 Web 服务经验或初学者的 Java/J2EE 开发人员。由于本书涵盖了 Web 服务开发的各种主题,因此那些已经熟悉 Web 服务的人可以将本书作为参考。初学者可以使用本书快速获得 Web 服务开发的实际经验。

约定

在本书中,您会发现一些区分不同信息类型的文本样式。以下是一些这些样式的示例,以及它们的含义解释。

文本中的代码词显示如下:“MessageDispatcherServlet 是 Spring-WS 的核心组件。”

代码块设置如下:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>

当我们希望引起您对代码块的特定部分的注意时,相关行或项目会以粗体显示:

<tns:placeOrderRequest ...> 
<tns:order>
......
</tns:order>
</tns:placeOrderRequest>

任何命令行输入或输出都以以下形式书写:

mvn clean package tomcat:run 

新术语重要单词以粗体显示。例如,屏幕上看到的单词,菜单或对话框中的单词会以这种方式出现在文本中:“您可以单击JUnit标签,相邻于Console标签,以查看测试用例是否成功”。

注意

警告或重要说明会显示在这样的框中。

注意

提示和技巧会显示为这样。

第一章:构建 SOAP Web 服务

在本章中,我们将涵盖:

  • 使用 Maven 构建和运行 Spring-WS 项目

  • 创建数据合同

  • 使用DispatcherServlet设置 Web 服务

  • 使用MessageDispatcherServlet简化 Web 服务的创建

  • 在 JMS 传输上设置 Web 服务

  • 在电子邮件传输上设置 Web 服务

  • 在嵌入式 HTTP 服务器传输上设置 Web 服务

  • 在 XMPP 传输上设置 Web 服务

  • 为 Web 服务设置简单的端点映射

  • 设置基于契约的 Web 服务

  • 通过注释 payload-root 设置端点

  • 设置一个与传输无关的 WS-Addressing 端点

  • 使用 XPath 表达式设置端点

  • 使用 DOM 处理传入的 XML 消息

  • 使用 JDOM 处理传入的 XML 消息

  • 使用 JAXB2 处理传入的 XML 消息

  • 使用拦截器在服务器端验证 XML 消息

介绍

SOAP(Simple Object Access Protocol)旨在成为语言、传输和平台无关的,这是一种替代传统中间件技术(如 CORBA 和 DCOM)的选择。SOAP 也被设计为可扩展的。所谓的 WS-*标准——WS-Addressing、WS-Policy、WS-Security 等都是基于 SOAP 协议构建的。

使用 SOAP 的 Web 服务,以及 WSDL 和 XML 模式,已经成为交换基于 XML 的消息的标准。Spring Web 服务通过提供一套全面的 API 和配置,为灵活的 Web 服务的创建提供了便利。下图显示了 Spring-WS 在接收到传入消息时的工作方式(图表以抽象形式呈现):

Introduction

MessageDispatcher是 Spring Web 服务的中心点,将 Web 服务消息分派到注册的端点。在 Spring-WS 中,请求/响应消息被包装在MessageContext对象中,并且MessageContext将被传递给MessageDispatcher(在调用端点后,响应将被设置到MessageContext中)。当消息到达时,MessageDispatcher使用请求对象获取端点。(将请求映射到端点称为端点映射,可以通过使用应用程序上下文中的 bean 注册数据、扫描和自动检测注释来完成)。然后,MessageDispatcher通过使用端点,获取端点的拦截器(从零到多个),并在它们上调用handleRequest方法。

拦截器(这里是EndpointInterceptor)拦截请求/响应,在调用端点之前/之后执行一些操作。这个EndpointInterceptor在调用适当的端点之前/之后被调用,执行诸如日志记录、验证、安全等多个处理方面。接下来,MessageDispatcher获取适当的端点适配器,用于调用端点方法。每个适配器都专门用于调用具有特定方法参数和返回类型的方法。

最后,EndpointAdapter调用端点的方法,并将响应转换为所需的形式,并将其设置到MessageContext对象中。现在,最初传递给MessageDispatcher的消息上下文包含了响应对象,该对象将被转发给客户端(由MessageDispatcher的调用者)。

Spring-WS 只支持基于契约的开发风格,其中创建契约(XSD 或 WSDL)是第一步。使用 Spring-WS 构建基于契约的 Web 服务的必要步骤如下:

  1. 合同定义(XSD 或 WSDL)

  2. 创建端点:接收和处理传入消息的类。

  3. Spring bean 和端点的配置。

有两种类型的端点,即有效载荷端点和消息端点。虽然消息端点可以访问整个 XML SOAP 包络,有效载荷端点只能访问 SOAP 包络的有效载荷部分,也就是 SOAP 包络的主体。在本书中,重点是创建有效载荷端点。

在本章中,在解释从一组 XML 消息创建合同的教程之后,主要关注点将放在实现端点及其相关配置上。

为了说明 Web 服务的构建过程,本书使用了一个虚构的餐厅 Live Restaurant 的简单业务场景,该餐厅需要接受来自客户的在线订单。Live Restaurant 决定将其OrderService组件发布为 Web 服务。为简单起见,OrderService(Java 接口)只考虑了两个操作。

Introduction

项目将遵循以下领域模型:

Introduction

本书中的每个教程都将逐步构建项目的各个部分,使其成为完整的 Web 服务应用程序。Java 项目名称为LiveRestaurant,每个教程将使用项目的略有不同的版本,带有扩展名_R-x.x。例如,本章的第一个教程将使用LiveRestaurant_R-1.1作为 Web 服务服务器,LiveRestaurant_R-1.1-Client作为客户端的项目名称。

注意

本章的目标是设置 Web 服务,因此更多的重点是在服务器端代码和设置的解释上。本章中使用客户端代码来检查服务器的功能。有关客户端代码、设置和测试的更多内容将在接下来的章节中讨论。

使用 Maven 构建和运行 Spring-WS 项目

基于企业级开源技术的最新现代软件开发需要一代新的构建和项目管理工具。这些工具可以为构建、管理和部署小规模到大规模应用程序提供标准的方式。

Maven 是由 Apache 软件基金会托管的项目管理和自动化构建和部署工具。Maven 建立在 Ant 的功能基础之上,并添加了诸如特性依赖和项目管理等功能。Maven 最初用于 Java 编程,但也可以用于构建和管理其他编程语言编写的项目。近年来,Maven 已被用于自动化构建、管理和测试主要开源项目的部署过程。

本教程详细介绍了设置 Maven 用于构建、测试和部署本书中使用的项目所需的步骤。

准备工作

本教程需要安装以下软件或工具:

  1. Java 6 或更高版本和 Maven 3.0.2:有关下载和安装,请参阅maven.apache.org/www.oracle.com/technetwork/java/javase/downloads/index.html

  2. 将您的自定义存储库添加到MAVEN_HOME/conf.m2文件夹下的settings.xml中(MAVEN_HOME是安装 Maven 的文件夹,.m2是 Maven 下载其构件的文件夹)。

稍后,您可以向自定义存储库添加额外的存储库。您可以通过将activeByDefault设置为false来禁用此存储库(包含存储库的文件位于resources文件夹中):

<profile>
<id>my-repository</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<!-- list of standard repository -->
<repositories>
...
...
<repository>
<id>maven2-repository.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2</url>
</repository>
....
<repository>
<id>maven1-repository.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/1</url>
</repository>
</repositories>
</profile>

将 Maven 存储库包含到 Maven 构建中的另一种方法是直接在 POM 文件中包含存储库数据。本章的资源包的Using Maven文件夹中包含了包含存储库的两种方式的示例。

如何做...

  1. 构建和部署项目。
mvn clean package tomcat:run

  1. 浏览以下 Web 服务 WSDL 文件:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 浏览器的输出如下:
<wsdl:definitions
targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<wsdl:types>
<schema elementFormDefault="qualified" targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<element name="placeOrderRequest">
<complexType>
<sequence>
<element name="order" type="QOrder:Order" />
</sequence>
</complexType>
........
</schema>
</wsdl:types>
.......
<wsdl:binding name="OrderServiceSoap11" type="tns:OrderService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="placeOrder">
<soap:operation soapAction="" />
<wsdl:input name="placeOrderRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="placeOrderResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="cancelOrder">
<soap:operation soapAction="" />
<wsdl:input name="cancelOrderRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="cancelOrderResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="OrderServiceService">
<wsdl:port binding="tns:OrderServiceSoap11" name="OrderServiceSoap11">
<soap:address
location="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

  1. 以下是 Maven 命令的输出:
...........
[INFO] Building war: C:\...\LiveRestaurant.war
.......
[INFO] --- tomcat-maven-plugin:1.1:run ...@ LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration ...
Oct 15,...org.apache.catalina.startup.Embedded start
INFO: Starting tomcat server
Oct 15...org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
org.apache.catalina.core.ApplicationContext log
...Set web app root ..: 'webapp.root' = [...src\main\webapp\]
INFO: Initializing log4j from..WEB-INF\log4j.properties]
...
INFO: Initializing Spring FrameworkServlet 'spring-ws'
......
INFO .. - FrameworkServlet 'spring-ws': initialization ..
Oct .. org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Oct .. org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080 

注意

要将 Maven 项目导入 Eclipse IDE,可以选择以下方法:

转到项目的根目录(\chapterOne\LiveRestaurant_R-1.1)并执行:

mvn eclipse:eclipse -Declipse.projectNameTemplate="LiveRestaurant_R-1.1" 

然后,您可以将 Maven 项目导入 Eclipse 项目。

如果 Maven 找不到一个 JAR 文件,可以使用以下命令使用自定义存储库:

mvn -P my-repository clean package tomcat:run 

工作原理...

mvn clean package将所需的组件安装到本地存储库,并创建项目的 WAR/JAR 文件:

[INFO] Building war: ...LiveRestaurant.war 

mvn tomcat:run在 Tomcat 插件上运行项目的 WAR 文件。mvn jetty:run在 Jetty 插件上运行项目的 WAR 文件:

INFO] --- tomcat-maven-plugin:1.1:... LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration at 

创建数据合同

WSDL 文档,也称为服务合同,提供了 Web 服务客户端和服务器交换数据的标准方式。使用 WSDL,客户端和服务器可以位于不同的应用程序或平台上。XML Schema Definition(XSD),也称为数据合同,描述了 Web 服务服务器和客户端之间交换的数据类型的结构。XSD 描述了类型、字段以及这些字段上的任何验证(如最大/最小或模式等)。虽然 WSDL 是特定于 Web 服务的,描述了 Web 服务的工件,如方法和通过这些方法传递的数据(WSDL 本身使用 XSD 进行描述),URL 等;XSD 只呈现数据的结构。

为了能够设置 Spring Web 服务,我们需要一个合同。有四种不同的方法可以为 XML 定义这样的合同:

  • DTDs

  • XML Schema(XSD)

  • RELAX NG

  • Schematron

DTDs 对命名空间的支持有限,因此不适用于 Web 服务。RELAX NG 和 Schematron 肯定比 XML Schema 更容易。不幸的是,它们在各个平台上的支持并不如此广泛。Spring-WS 使用 XML Schema。

数据合同是 Spring-WS 的中心,服务合同可以从数据合同中生成。创建 XSD 的最简单方法是从样本文档中推断出来。任何良好的 XML 编辑器或 Java IDE 都提供了这种功能。基本上,这些工具使用一些样本 XML 文档,并从中生成一个模式,用于验证它们。在这个配方中,我们将讨论样本 XML 数据消息以及如何将它们转换为单个模式文件。生成的模式文件在本书中用作数据合同。

准备工作

  1. 安装 Java(如第一个配方中所述)。

  2. xmlbeans.apache.org/安装 xmlbeans-2.5.0。

  3. 本配方的资源包含在文件夹 Create Data Contract 中。

如何做...

  1. 将您的 XML 消息(placeOrderRequest.xml、placeOrderResponse、cancelOrderRequest.xml 和 cancelOrderResponse.xml)复制到xmlbeans-2.5.0\bin工作文件夹中。

  2. 运行以下命令:

inst2xsd -design rd -enumerations never placeOrderRequest.xml placeOrderResponse.xml cancelOrderRequest 

  1. 上述命令创建了schema0.xsd模式文件。生成的模式结果肯定需要修改,但这是一个很好的起点。这是最终精心制作的模式(orderService.xsd):
<?xml version="1.0" encoding="UTF-8"?>
......
<schema...">
<element name="placeOrderRequest">
<complexType>
<sequence>
<element name="order" type="QOrder:Order"></element>
</sequence>
</complexType>
</element>
<element name="placeOrderResponse">
<complexType>
<sequence>
<element name="refNumber" type="string"></element>
</sequence>
</complexType>
</element>
.........
data contractdata contractcreating<complexType name="Order">
<sequence>
<element name="refNumber" type="string"></element>
<element name="customer" type="QOrder:Customer"></element>
<element name="dateSubmitted" type="dateTime"></element>
<element name="orderDate" type="dateTime"></element>
<element name="items" type="QOrder:FoodItem"
maxOccurs="unbounded" minOccurs="1">
</element>
</sequence>
</complexType>
<complexType name="Customer">
<sequence>
<element name="addressPrimary" type="QOrder:Address"></element>
<element name="addressSecondary" type="QOrder:Address"></element>
<element name="name" type="QOrder:Name"></element>
</sequence>
</complexType>
....
</schema>

工作原理...

首先需要输入和输出样本消息。在本书中,有四个 XML 消息(placeOrderRequest.xml、placeOrderResponse、cancelOrderRequest.xmlcancelOrderResponse.xml),所有的配方都使用这些消息数据格式进行通信。Inst2xsd从现有的 XML 样本消息生成模式文件。本配方的资源包含在本章的资源包中的Create Data Contract文件夹中。

使用 DispatcherServlet 设置 Web 服务

Spring-WS 提供了在 Java 平台上开发 Web 服务的最简单机制之一。这个配方专注于使用 Spring-WS 提供的 Spring-MVC DispatcherServlet和组件构建一个非常简单的 Web 服务。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.2,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

  1. resources文件夹中复制服务合同(orderService.wsdl)。

  2. 创建一个端点(OrderSeviceMessageReceiverEndpoint)。

  3. 在服务器 Spring 配置文件(Dispatcher-servlet.xml)中配置端点、服务合同、WebServiceMessageReceiverHandlerAdapter, MessageDispatcherWsdlDefinitionHandlerAdapter

  4. web.xml文件中配置DispatcherServlet

  5. 使用以下命令运行服务器:

mvn clean package tomcat:run 

  • 以下是输出:
...........................
[INFO] Running war on http://localhost:8080/LiveRestaurant
...................................
18-Oct-2011 10:23:02.....ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'Dispatcher'
18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080 

  1. 要浏览您的服务 WSDL,请在浏览器中打开以下链接:
http://localhost:8080/LiveRestaurant/Dispatcher/OrderService.wsdl

  1. 要进行测试,打开一个新的命令窗口,转到文件夹LiveRestaurant_R-1.2-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端输出:
Inside method, OrderSeviceMethodEndpoint.receive - message content = <?xml version="1.0" encoding="UTF-8"?><tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

它是如何工作的...

DispatcherServlet接收所有传入的请求,并根据请求上下文将请求转发到端点(请求 URL 的一般形式为http://<host>:<port>/<appcontext>/<requestcontext>(这里appcontext是 Liverestaurant,requestcontext应以/Dispatcher/开头)。以/OrderService结尾的请求上下文转到OrderSeviceMessageReceiverEndpoint,以*.wsdl结尾的请求转到SimpleWsdl11Definition)。

web.xml中配置的DispatcherServlet负责接收所有具有 URL 映射[/Dispatcher/*]的请求。

<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/Dispatcher/*</url-pattern>
</servlet-mapping>

您可以更改 URL 模式以满足您的需求。

DispatcherServlet在拦截 HTTP 请求并加载 Spring bean 配置文件方面起着重要作用。默认情况下,它通过名称<servlet-name>-servlet.xml来检测 bean 配置文件。由于我们在web.xml文件中将DispatcherServlet命名为Dispatcher,服务器会寻找Dispatcher-servlet.xml作为应用程序上下文文件名。您可以使用web.xml中的以下上下文param来配置另一个文件:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>

DispatcherServlet需要WebServiceMessageReceiverHandlerAdapter, MessageDispatcherWsdlDefinitionHandlerAdapter的单独实例,这些实例在Dispatcher-servlet.xml中进行了配置。默认情况下,DispatcherServlet委托给控制器处理请求,但在配置文件中,它被配置为委托给MessageDispatcherWebServiceMessageReceiverHandlerAdapter)。SaajSoapMessageFactory是 Spring-WS 中用于消息创建的特定消息工厂。

<beans ...">
<bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter">
<property name="messageFactory">
<bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"></bean>
</property>
</bean>
.......

DispatcherServlet处理 WSDL 合同,需要在配置文件中注册WsdlDefinitionHandlerAdapter;它使用WsdlDefinition实现(SimpleWsdl11Definition)来读取 WSDL 文件源,并将其作为结果写入HttpServletResponse

SimpleUrlHandlerMapping用于使用 URL 模式将客户端请求重定向到适当的端点。在这里,以*.wsdl结尾的请求 URL 将被重定向到sampleServiceDefinition(即使用OrderService.wsdl生成响应的SimpleWsdl11Definition),如果请求 URL 包含/OrderService,它将被重定向到OrderSeviceMessageReceiverEndpoint。SOAPMessageDispatcher用于将 SOAP 消息分派到已注册的端点(OrderSeviceMessageReceiverEndpoint)。

.......
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="*.wsdl">sampleServiceDefinition</prop>
<prop key="/OrderService">OrderServiceEndpoint</prop>
</props>
</property>
<property name="defaultHandler" ref="messageDispatcher"/>
</bean>
<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMessageReceiverEndpoint"/>
<bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>
<bean id="sampleServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
<property name="wsdl" value="/WEB-INF/OrderService.wsdl"/>
</bean>
</beans>
OrderSeviceMessageReceiverEndpoint is a very basic endpoint that get incoming message (messageContext.getRequest().getPayloadSource()) and prin it out:
........
public class OrderSeviceMessageReceiverEndpoint implements
WebServiceMessageReceiver {
public OrderSeviceMessageReceiverEndpoint() {
}
public void receive(MessageContext messageContext) throws Exception {
System.out
.println("Inside method, OrderSeviceMethodEndpoint.receive - message content = "
+ xmlToString(messageContext.getRequest().getPayloadSource()));
}

提示

您可以更改 URL 模式以满足您的需求。

private String xmlToString(Source source) {
try {
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return stringWriter.getBuffer().toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
return null;
}
}

另请参阅

本章中的使用 MessageDispatcherServlet 设置 Web 服务食谱。

简化使用 MessageDispatcherServlet 创建 Web 服务

MessageDispatcherServlet是 Spring-WS 的核心组件。通过简单的配置,可以在几分钟内设置 Web 服务。这个 servlet 作为配置 Spring-MVCDispatcherServlet的替代方案而出现。与第二个食谱中使用 DispatcherServlet 设置 Web 服务一样,DispatcherServlet需要单独的WebServiceMessageReceiverHandlerAdapter, MessageDispatcherWsdlDefinitionHandlerAdapter的实例。然而,MessageDispatcherServlet可以通过在应用程序上下文中设置来动态检测EndpointAdapters, EndpointMappings, EndpointExceptionResolversWsdlDefinition

由于这是配置 Spring Web 服务的默认方法,因此将在后续配方中使用。在此配方中,详细介绍了设置 Spring-WS 的基本实现。更高级的实现将在后面的配方设置基于契约的 Web 服务中解释。

准备就绪

在此配方中,项目名称为 LiveRestaurant_R-1.3,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

  1. 从资源文件夹(orderService.wsdl)复制服务合同。

  2. 创建端点(OrderSeviceMethodEndpoint)。

  3. 配置端点。服务合同在服务器 Spring 配置文件(spring-ws-servlet.xml)中。

  4. 在 web.xml 文件中配置 MessageDispatcherServlet。

  5. 使用以下命令运行服务器:

mvn clean package tomcat:run 

  • 成功运行服务器后的输出如下:
...........................
[INFO] >>> tomcat-maven-plugin:1.1:run .. LiveRestaurant >>>
[..............
[INFO] Running war on http://localhost:8080/LiveRestaurant
[I...........
..XmlBeanDefinitionReader.. Loading..spring-ws-servlet.xml]
...
..SimpleMethodEndpointMapping#0, OrderService, OrderServiceEndpoint]; root of factory hierarchy
INFO [main] (SaajSoapMessageFactory.java:135) -..
INFO [main] (FrameworkServlet.java:320) - FrameworkServlet '
........
INFO: Starting Coyote HTTP/1.1 on http-8080 

  1. 要浏览您的服务 WSDL,请在浏览器中打开以下链接:
http://localhost:8080/LiveRestaurant/spring-ws/OrderService.wsdl

  1. 要进行测试,打开一个新的命令窗口,转到文件夹 LiveRestaurant_R-1.3-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response
...
<tns:placeOrderResponse....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
.....
for request
<tns:placeOrderRequest.... >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
.... 

它是如何工作的...

MessageDispatcherServlet 在 web 配置文件 web.xml 中配置:

<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>
org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

提示

下载示例代码

您可以从www.PacktPub.com的帐户中下载您购买的所有 Packt 图书的示例代码文件。如果您在其他地方购买了本书,可以访问www.PacktPub.com/support并注册,以便文件直接通过电子邮件发送给您。

MessageDispatcherServlet 是处理传入 SOAP 请求的中心元素,借助其他组件(EndpointAdapters,EndpointMappings,EndpointExceptionResolvers 和 WsdlDefinition)。它结合了 DispatcherServlet 和 MessageDispatcher 的属性,以将请求分派到适当的端点。这是建议使用 Spring-WS 构建 Web 服务的标准 servlet。

由于 MessageDispatcherServlet 是从 FrameworkServlet 继承的,它会在类路径中查找名为-servlet.xml 的配置文件(您可以使用 web.xml 中的 context-param,contextConfigLocation 设置更改配置文件名,如在配方*使用 DispatcherServlet 设置 Web 服务中所述)。在本例中,由于 web.xml 文件中的 servlet 名称设置为 Spring-WS,因此文件 spring-ws-servlet.xml 是 Web 服务配置文件。

然后,MessageDispatcherServlet 在配置文件中查找端点映射元素,以将客户端请求映射到端点。在这里,<sws:static-wsdl 设置 WSDL 格式的数据合同。这是在 spring-ws-servlet.xml 中配置的元素,用于设置 Web 服务:

<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
<property name="endpoints">
<ref bean="OrderServiceEndpoint"/>
</property>
<property name="methodPrefix" value="handle"></property>
</bean>
<sws:static-wsdl id="OrderService" location="/WEB-INF/orderService.wsdl"/>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint">
</bean>

示例使用 SimpleMethodEndpointMapping 将客户端请求映射到 MethodEnpoints。它将传入请求映射到以消息的 handle+root 元素(handle+placeOrderRequest)开头的方法。在端点类(OrderSeviceMethodEndpoint)中,应定义一个名为 handleplaceOrderRequest 的方法。

在此方法中,参数源包括传入消息和从该参数中提取调用订单服务的输入参数,然后该方法调用 orderService 方法,并将传出消息包装在 StringSource 中,以发送回客户端:

public class OrderSeviceMethodEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public @ResponsePayload
Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName,lName,refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

端点映射将在后面的配方中详细说明。

另请参阅

在本章中讨论的配方使用 DispatcherServlet 设置 Web 服务,为 Web 服务设置简单的端点映射设置基于契约的 Web 服务

在 JMS 传输上设置 Web 服务

HTTP 是最常见的 Web 服务协议。但是,Web 服务目前是建立在多种传输上的,每种传输都有不同的场景。

JMS 在 1999 年由 Sun Microsystems 包含在 Java 2、J2EE 中。使用 JMS,系统能够同步或异步通信,并基于点对点和发布-订阅模型。SOAP over JMS 继承了 JSM 的特性,并满足以下要求:

  • 需要异步消息传递时

  • 当消息消费者比生产者慢时

  • 为了保证消息的传递

  • 要有发布者/订阅者(多个)模型

  • 当发送者/接收者可能断开连接时

Spring Web 服务提供了在 JMS 协议上设置 Web 服务的功能,该功能是建立在 Spring 框架中的 JMS 功能之上的。在这个配方中,介绍了如何在 JMS 上设置 Spring-WS。

准备工作

在这个配方中,项目的名称是 LiveRestaurant_R-1.4,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • spring-jms-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • log4j-1.2.9.jar

  • jms-1.1.jar

  • activemq-core-4.1.1.jar

在这个配方中,使用 Apache ActiveMQ 来设置 JMS 服务器并创建 JMS 服务器相关的对象(这里使用了队列和代理)。Spring-WS 家族的 JAR 提供了设置 Spring-WS 的功能,而 spring-jmsjms JAR 提供了 Spring-WS 在 JMS 上建立的 JMS 功能。

如何做...

  1. 创建端点 (OrderSeviceMethodEndpoint)

  2. 在 Spring 配置文件 (applicationContext.xml) 中配置 MessageListenerContainerMessageListenerconnectionFactory

  3. applicationContext.xml 中配置包括端点映射的 MessageDispatcher

  4. 使用以下命令运行配方项目:

mvn clean package 

  1. 一旦项目成功运行,以下是输出:
INFO [main] (SaajSoapMessageFactory.java:135) -..
INFO [main] (DefaultLifecycleProcessor.java:330) -..
INFO [main] .. - ActiveMQ 4.1.1 JMS Message Broker (localhost)..
..
INFO [JMX connector] ..
INFO [main]..ActiveMQ JMS Message Broker ..started
INFO [main] ..- Connector vm://localhost Started
.....
Received response ....
<tns:placeOrderResponse ..><tns:refNumber>..</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest ....>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
........ 

工作原理...

DefaultMessageListenerContainer 监听 destinationName (RequestQueue) 以接收传入的消息。当消息到达时,此监听器将使用消息工厂 (messageFactory) 提取消息,并使用调度程序 (messageDispatcher) 将消息分派到端点 (SimplePayloadEndpoint).............

在应用程序上下文中,WebServiceMessageListenerMessageListenerContainer 中的监听器。消息容器使用 connectionfactory 连接到目的地(RequestQueue):

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
</bean>
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" value="RequestQueue"/>
<property name="messageListener">
<bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
<property name="messageFactory" ref="messageFactory"/>
<property name="messageReceiver" ref="messageDispatcher"/>
</bean>
</property>
</bean>

此监听器使用 message DispatchermessageFactory 来接收传入的消息并发送传出的 SOAP 消息。在 messageDiapatcher 中,包括端点的映射,设置端点 (SimplePayloadEndpoint) 和端点映射的类型 (PayloadRootQNameEndpointMapping)

<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint">
<bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint">
<property name="orderService">
<bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/>
</property>
</bean>
</property>
</bean>
</property>
</bean>

当请求到达服务器时,端点 (SimplePayloadEndpoint)invoke 方法将被调用,并且将返回响应以发送回客户端:

public class SimplePayloadEndpoint implements PayloadEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public Source invoke(Source request) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

JmsTransportWebServiceIntegrationTest 包含在项目中,用于加载应用程序上下文,设置 JMS 服务器并测试 Web 服务。但这些细节在这里没有讨论。JMS 传输的客户端将在下一章中讨论。

另请参阅

在第二章中讨论了 在 JMS 传输上创建 Web 服务客户端 配方,构建 SOAP Web 服务的客户端 和在第十章中讨论了 使用 JMS 作为底层通信协议暴露 Web 服务 的配方,Spring 远程调用

在电子邮件传输上设置 Web 服务

HTTP 容易理解,因此通常被定义和实现,但在任何情况下都不是 Web 服务的最合适的传输方式。

电子邮件传输的 Web 服务可以利用存储转发消息传递来提供 SOAP 的异步传输。此外,电子邮件上没有防火墙问题,那些能够相互通信的应用程序无需设置 Web 服务器来建立 Web 服务。这允许在 HTTP 不适用的许多场景中使用 SOAP 通过邮件传输。

设置 Web 服务通过 HTTP 不适合的原因,以及电子邮件可能作为传输协议的解决方案如下所列:

  • 如果系统受到防火墙的保护,就无法控制 HTTP 请求/响应,但是电子邮件始终是可访问的。

  • 如果系统不期望传统的请求/响应模型。例如,需要发布/订阅模型。

  • 如果请求花费太长时间来完成。例如,如果服务器必须运行复杂和耗时的服务,客户端将收到 HTTP 超时错误。在这种情况下,通过电子邮件的 Web 服务更合适。

在这个配方中,介绍了通过电子邮件传输设置 Web 服务。为了加载应用程序上下文并测试 Web 服务,使用了一个测试类。这个类还启动和关闭服务器。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.5,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • mail-1.4.1.jar

  • mock-javamail-1.6.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

在使用 JavaMail 进行测试的系统外设置邮件服务器是困难的。模拟 JavaMail 解决了这个问题,并为使用 JavaMail 的系统提供了可插拔的组件。系统可以使用这个组件来针对临时的内存邮箱发送/接收电子邮件。

如何做...

  1. 创建端点(SimplePayloadEndpoint)

  2. applicationContext.xml中配置包含端点映射的MessageReceiverMessageDispatcher

  3. 使用以下命令运行配方项目:

mvn clean package 

  • 以下是输出:
........
INFO [main] ...- Creating SAAJ 1.3 MessageFactory with SOAP 1.1 Protocol
..- Starting mail receiver [imap://server@packtpubtest.com/INBOX]
....
Received response...
<tns:placeOrderResponse >
<tns:refNumber>...</tns:refNumber></tns:placeOrderResponse>
...for request ..
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
...... 

它是如何工作的...

发送到地址的消息将保存在收件箱中。消息接收器(messageReceiver)会在连续的间隔中监视收件箱,一旦检测到新的电子邮件,它就会读取电子邮件,提取消息,并将消息转发给消息调度程序(messageDispatcher)。消息调度程序将调用其默认端点(SamplePayloadEndpoint)内的invoke方法,并在处理程序方法(invoke)内将响应发送回客户端。

当加载应用程序上下文时,MailMessageReceiver启动邮件接收器及其收件箱文件夹(imap://server@packtpubtest.com/INBOX),即临时内存收件箱。加载应用程序上下文后,messageReceiver bean 充当基于可插拔策略(monotoringStrategy)监视INBOX文件夹(imap://server@packtpubtest.com/INBOX)上的新消息的服务器监视器,轮询间隔为 1000 毫秒。storeUri是要监视传入消息的位置(imap://server@packtpubtest.com/INBOX)transportUri是用于发送响应的邮件服务器:

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
<property name="messageFactory" ref="messageFactory"/>
<property name="from" value="server@packtpubtest.com"/>
<property name="storeUri" value="imap://server@packtpubtest.com/INBOX"/>
<property name="transportUri" value="smtp://smtp.packtpubtest.com"/>
<property name="messageReceiver" ref="messageDispatcher"/>
<property name="session" ref="session"/>
<property name="monitoringStrategy">
<bean class="org.springframework.ws.transport.mail.monitor.Pop3PollingMonitoringStrategy">
<property name="pollingInterval" value="1000"/>
</bean>
</property>
</bean>

messageDiapatcher中包含了端点映射,设置了端点(SimplePayloadEndpoint)和端点映射的类型(PayloadRootQNameEndpointMapping)

<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint">
<bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint">
<property name="orderService">
<bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/>
</property>
</bean>
</property>
</bean>
</property>
</bean>

SimplePayloadEndpoint接收请求并使用OrderService返回固定的虚拟响应。当请求到达服务器时,将调用invoke方法,并返回要发送回客户端的响应。

public class SimplePayloadEndpoint implements PayloadEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public Source invoke(Source request) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

为了测试这个配方,使用了webServiceTemplate。我们将在下一章讨论它。

MailTransportWebServiceIntegrationTest包含在项目中,用于加载应用程序上下文,设置邮件服务器并测试 Web 服务。

另请参阅

在第二章中讨论的在电子邮件传输上创建 Web 服务客户端配方,构建 SOAP Web 服务的客户端

在嵌入式 HTTP 传输上设置 Web 服务

外部 HTTP 服务器可能能够提供多种功能,但它们不够轻便,需要配置才能设置。

Spring-WS 提供了一个功能,可以使用嵌入式 Sun 的 JRE 1.6 HTTP 服务器设置基于 HTTP 的 Web 服务。嵌入式 HTTP 服务器是一个轻量级的独立服务器,可以作为外部服务器的替代方案。在传统的外部服务器中,必须配置 Web 服务器的配置(web.xml),而嵌入式 HTTP 服务器不需要任何部署描述符来操作,它唯一的要求是通过应用程序上下文配置服务器的实例。

在这个配方中,介绍了在嵌入式 HTTP 服务器上设置 Spring Web 服务。由于没有外部 HTTP 服务器,因此使用一个 Java 类来加载应用程序上下文并启动服务器。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.6,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

  1. 从资源文件夹中复制服务契约(OrderService.wsdl)

  2. 创建一个服务及其实现,并使用@Service("serviceName")注解其实现(OrderSevice,OrderServiceImpl)

  3. 在应用程序上下文(applicationContext)中配置要自动扫描和检测的服务。

  4. 在应用程序上下文中配置嵌入式 HTTP 服务器。

  5. 添加一个具有主方法的 Java 类,以加载应用程序上下文来设置嵌入式 HTTP 服务器。

  6. 使用以下命令运行服务器:

mvn clean package exec:java 

  1. LiveRestaurant_R-1.6-Client运行以下命令:
mvn clean package exec:java 

  • 当服务器成功运行时,以下是输出:
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
<tns:customer>
.......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

  • 以下是客户端的输出:
<tns:placeOrderResponse ...><refNumber>order-John_Smith_1234</refNumber></tns:placeOrderResponse>>
......
..... 

它是如何工作的...

在应用程序上下文中,SimpleHttpFactoryBean创建一个简单的 HTTP 服务器(来自嵌入式 Sun 的 JRE 1.6),并在初始化时启动 HTTP 服务器,在销毁时停止它。

具有上下文属性的 HTTP 服务器使用服务类(orderServiceImpl)设置 Web 服务端点,并指定上下文内定义的 URL(localhost:3478/OrderService)。此服务接口在上下文属性中注册。

然而,服务实现是使用component-scan自动检测的。HttpInvokerProxyFactoryBean为特定服务器 URL 创建客户端代理。

<context:annotation-config />
<context:component-scan base-package="com.packtpub.liverestaurant.service.endpoint" />
<bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
<property name="contexts">
<util:map>
<entry key="/OrderService">
<bean class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.endpoint.IOrderServiceEndPoint" />
<property name="service" ref="orderServiceImpl" />
</bean>
</entry>
</util:map>
</property>
<property name="port" value="3478" />
<property name="hostname" value="localhost" />
</bean>

IOrderServiceEndPointImplIOrderServiceEndPoint是简单的服务接口和实现类。IOrderServiceEndPointImpl@Service注解(orderServiceImpl),并且将被检测为服务实现。

package com.packtpub.liverestaurant.service.endpoint;
public interface OrderService {
String invoke(String request) throws Exception;
}
package com.packtpub.liverestaurant.service.endpoint;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
@Service("orderServiceImpl")
public class OrderServiceImpl implements OrderService {
static Logger logger = Logger.getLogger(OrderServiceImpl.class);
private static final String responseContent = "<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><refNumber>Order Accepted!</refNumber></tns:placeOrderResponse>";
public String invoke(String request) throws Exception {
logger.info("invoke method request:"+request);
return responseContent;
}
}

ServerStartUp.java用于加载应用程序上下文并启动服务器:

package com.packtpub.liverestaurant.server;
public class ServerStartUp {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
System.out.println(appContext);
char c;
// Create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("Enter any character to quit.");
c = (char) br.read();
appContext.close();
}

在 XMPP 传输上设置 Spring-WS

HTTP 通常用作 Web 服务传输协议。然而,它无法满足异步通信的要求。

XMPP 传输上的 Web 服务能够进行异步通信,客户端无需等待服务端的响应;相反,服务在完成过程后将响应发送给客户端。Spring-WS 2.0 包括 XMPP(Jabber)支持,其中 Web 服务可以通过 XMPP 协议进行通信。在这个配方中,介绍了在 XMPP 传输上设置 Spring-WS 的过程。由于没有外部 HTTP 服务器,因此使用一个测试类来加载应用程序上下文。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.7,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • smack-3.1.0.jar

如何做...

  1. 创建一个端点(SamplePlayLoadEndPoint)

  2. 在应用程序上下文(applicationContext.xml)中配置与 XMPP 服务器的连接。

  3. 在应用程序上下文中配置消息接收器。

  4. 运行以下命令:

mvn clean package 

  • 以下是收到的响应:
<placeOrderRequest ><id>9999</id></placeOrderRequest>
...
for request
...<placeOrderRequest ><id>9999</id></placeOrderRequet>... 

工作原理...

在应用程序上下文中,messageFactory bean 负责创建传入和传出的 SOAP 消息。messageReceiver bean 充当服务器,使用连接(到XMPP 服务器:google talk),并侦听具有用户名和密码的特定服务的主机。

<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="talk.google.com"/>
<property name="username" value="yourUserName@gmail.com"/>
<property name="password" value="yourPassword"/>
<property name="serviceName" value="gmail.com"/>
</bean>
<bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
<property name="messageFactory" ref="messageFactory"/>
<property name="connection" ref="connection"/>
<property name="messageReceiver" ref="messageDispatcher"/>
</bean>

一旦客户端发送消息,它将通过消息分发器转发到端点(SamplePlayLoadEndPoint配置在messageDispatcher中),并将响应返回给客户端:

<bean id="messageDispatcher"
class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint"> <bean class="com.packtpub.liverestaurant.service.endpoint.SamplePlayLoadEndPoint"/>
</property> </bean>
</property>
</bean>

Webservicetemplate在这里用作客户端;将在下一章中讨论。

SamplePlayLoadEndPoint只接收请求并返回响应:

public class SamplePlayLoadEndPoint implements PayloadEndpoint {
static Logger logger = Logger.getLogger(SamplePlayLoadEndPoint.class);
public Source invoke(Source request) throws Exception {
return request;
}

项目中包含一个测试类,用于加载应用程序上下文,设置 XMPP Web 服务服务器,并测试 Web 服务。

另请参阅

在第二章中讨论的在 XMPP 传输上创建 Web 服务客户端教程,SOAP Web 服务的客户端

建立基于合同的 Web 服务

从 Java 代码生成 WSDL 和 XSD 合同并设置 Web 服务称为合同后开发。这种方法的主要缺点是 Web 服务的合同(WSDL 或 XSD)最终可能会发生变化,如果 Java 类发生任何更改。通过这种方式,客户端必须更新客户端类,这并不总是令人满意。合同优先方法被引入作为解决合同后瓶颈的替代方法。在合同优先方法中,合同(WSDL 或模式)是设置 Web 服务的主要工件。

合同优先方法相对于合同后方法的一些优点如下:

  • 性能:在合同后方法中,可能会在客户端和服务器之间交换一些额外的数据,即 Java 代码的序列化,这会降低性能,而合同优先方法精确交换所需的数据并最大化性能。

  • 一致性:在合同后方法中,不同的供应商可能会生成不同的 WSDL,而合同优先方法通过依赖于相同的合同来消除这个问题。

  • 版本控制:更改合同后 Web 服务的版本意味着在客户端和服务器端更改 Java 类,如果有很多客户端调用 Web 服务,这可能最终会很昂贵,而在合同优先方法中,由于合同与实现解耦,版本控制可以通过在同一端点类中添加新的方法实现或使用样式表将旧消息格式转换为新消息格式来简单完成。

  • 维护/增强成本:仅更改合同比在客户端和服务器端的 Java 代码中更改要便宜得多。在本教程中,我们将讨论如何使用 Spring-WS 建立基于合同的 Web 服务。

准备工作

在本教程中,项目名称为LiveRestaurant_R-1.8,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

如何做...

  1. 从资源文件夹复制数据合同(orderService.xsd)。

  2. 创建一个端点(OrderEndpoint)。

  3. 在服务器 Spring 配置文件(spring-ws-servlet.xml)中使用组件扫描配置端点的自动检测。

  4. 配置从数据合同(orderService.xsd)动态生成 WSDL。

  5. 使用以下命令运行服务器:

mvn clean package tomcat:run 

  1. 浏览以下链接以查看 WSDL:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. LiveRestaurant_R-1.8-Client运行客户端:
mvn clean package 

  • 当服务器成功运行时,以下是输出:
Sent response....
<tns:placeOrderResponse ><tns:refNumber>tns:refNumber>order-John_S
mith_9999</tns:refNumber></tns:refNumber></
tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

工作原理...

本配方的步骤与使用 MessageDispatcherServlet 简化创建 Web 服务的配方相同,只是端点处理方法的实现。

此注释作为@Component的特化,允许通过类路径扫描自动检测实现类,这在服务器应用程序上下文文件(spring-ws-servlet.xml)中进行了配置:

<context:component-scan base-package="com.packtpub.liverestaurant.service"/>
<sws:annotation-driven/>

OrderEndpoint是本配方的endPoint@Endpoint注释也与@service相同,允许通过类路径扫描自动检测实现类。具有根元素placeOrderRequestlocalPart = "placeOrderRequest")和命名空间http://www.packtpub.com/liverestaurant/OrderService/schema的请求将被转发到调用相应的方法(handlePlaceOrderRequest)。

@Endpoint
public class OrderEndpoint {
private static final Log logger = LogFactory.getLog(OrderEndpoint.class);
private static final String NAMESPACE_URI = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest")
@ResponsePayload
public Source handlePancelOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception {
String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent();
String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent();
String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent();
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName,lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

有关注释和请求如何映射到端点方法的其他详细信息包含在本章中。

spring-ws-servlet.xml文件中的以下设置会导致应用程序从数据合同(orderService.xsd)自动生成 WSDL 文件。

<sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService"
targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<sws:xsd location="/WEB-INF/orderService.xsd"/>
</sws:dynamic-wsdl>

注意

尽管 WSDL 可以从数据合同(XSD)自动生成,但 Spring-WS 建议出于以下原因避免自动生成 WSDL:

  • 为了保持发布之间的一致性(自动生成的不同版本的 WSDL 可能会有轻微差异)

  • WSDL 的自动生成速度较慢,尽管一旦生成,WSDL 将被缓存并在以后使用。

因此,Spring-WS 建议在开发时通过浏览器自动生成 WSDL 一次并保存,并使用静态 WSDL 来公开服务合同。

另请参阅

本章讨论的配方通过注释有效载荷根设置端点,简化使用 MessageDispatcherServlet 创建 Web 服务,以及第二章中讨论的为 SOAP Web 服务构建客户端配方。

还可以查看第十章中讨论的配方Spring 远程调用,了解如何设置基于契约的 Web 服务。

为 Web 服务设置简单的端点映射

本配方演示了一个非常简单的端点映射,将 Web 服务请求映射到 Java 类方法。

准备就绪

在本配方中,项目名称为LiveRestaurant_R-1.9,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

如何做...

本配方的步骤与前一个配方设置基于契约的 Web 服务相同,只是端点的注册,即方法端点映射,并在spring-ws-servlet.xml中进行配置。

  1. 基于方法映射标准(SimpleMethodEndpointMapping)定义端点(OrderSeviceMethodEndpoint)。

  2. spring-ws-servlet.xml中配置方法端点映射。

  3. 运行mvn clean package tomcat:run命令并浏览以查看 WSDL:

http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口,转到Liverestaurant_R-1.9-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response ..
<tns:placeOrderResponse ><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

工作原理...

SimpleMethodEndpointMapping从请求有效负载的本地名称(placeOrderRequest)映射到 POJO 类的方法。以下是请求有效负载的示例(请注意请求有效负载的本地名称):

<tns:placeOrderRequest ...>
<tns:order>
......
</tns:order>
</tns:placeOrderRequest>

端点 bean 是使用endpoints属性注册的。该属性告诉您endpoint类(OrderServiceEndpoint)中应该有一个以methodPrefix(handle)开头并以请求有效负载本地名称(placeOrderRequest)结尾的方法。这通过在spring-ws-servlet.xml中使用配置增加了端点命名的灵活性:

<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
<property name="endpoints">
<ref bean="OrderServiceEndpoint"/>
</property>
<property name="methodPrefix" value="handle"></property>
<property name="interceptors">
<list>
<bean
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
<property name="logRequest" value="true" />
<property name="logResponse" value="true" />
</bean>
</list>
</property>
</bean>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint">
</bean>

端点方法名称应与handle+request消息根名称匹配(handleplaceOrderRequest)。在方法的主体中,我们应该处理请求,并最终以javax.xml.transform.Source的形式返回响应:

public class OrderSeviceMethodEndpoint {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public @ResponsePayload Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

另请参阅

本章讨论的食谱设置一个与传输无关的 WS-Addressing 端点通过注释 payload-root 设置端点

通过注释 payload-root 设置端点

Spring-WS 通过其注解功能进一步简化了复杂 Web 服务的创建,并减少了 XML 中的代码和配置。

准备工作

在这个食谱中,项目的名称是LiveRestaurant_R-1.10,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

如何做...

这个食谱的步骤与设置一个基于契约的 Web 服务的步骤相同,这里我们想要描述在endpoint类中使用注解的端点映射。

  1. 运行以下命令:
mvn clean package tomcat:run 

  1. 浏览以下链接查看 WSDL:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口,转到LiveRestaurant-1.10-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response ..
<tns:placeOrderResponse ><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

工作原理...

通过在 Spring-WS 配置文件(spring-ws-servlet.xml)中包含组件扫描和注解驱动的设置,Spring 容器将扫描整个包以查找端点、服务和依赖项,以相互注入和自动装配构建 Web 服务块。您在这里看不到适配器和其他处理程序,因为容器会智能地选择正确/默认的适配器,动态地(messageDispatcher 运行适配器的支持方法,从现有适配器列表中选择支持方法返回true的适配器):

<context:component-scan base-package="com.packtpub.liverestaurant.service"/>
<sws:annotation-driven/>
<sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService"
targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<sws:xsd location="/WEB-INF/orderService.xsd"/>
</sws:dynamic-wsdl>

OrderSeviceAnnotationEndpoint@Endpoint注解使其成为一个端点,具有PayloadRootAnnotationMethodEndpointMapping,具有指向方法级别注解的方法端点映射:

@Endpoint
public class OrderSeviceAnnotationEndpoint {
private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderSeviceAnnotationEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleCancelOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
boolean cancelled =true ;
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>");
}

@PayloadRoot帮助MessageDispatcher将请求映射到方法,借助参数注释@RequestPayload的帮助,该注释指定整个 SOAP 消息的确切消息有效负载部分作为方法的参数(它通过请求的根元素等于localPart来找到方法,例如,placeOrderRequestplaceCancelRequest)。@RequestPayload告诉容器,要从 SOAP 消息中提取参数RequestPayload`并在运行时注入到方法中作为参数。

返回类型注释@ResponsePayload指示MessageDispatcherjavax.xml.transform.Source的实例是ResponsePayload。聪明的 Spring-WS 框架在运行时检测这些对象的类型,并委托给适当的PayloadMethodProcessor。在这种情况下,它是SourcePayloadMethodProcessor,因为输入参数和返回值的类型都是javax.xml.transform.Source

另请参阅

本章讨论的食谱设置一个与传输无关的 WS-Addressing 端点为 Web 服务设置一个简单的端点映射

设置一个与传输无关的 WS-Addressing 端点

在 XML 消息中使用 HTTP 传输信息来路由消息到端点,将数据和操作混合在一起,这些消息将被回复给请求的客户端。

WS-Addressing 通过将路由数据与 SOAP 头部中的数据分离并包含在其中来标准化路由机制。WS-Addressing 可能使用自己的元数据,而不是使用 HTTP 传输数据进行端点路由。此外,来自客户端的请求可能会返回到 WS-Addressing 中的不同客户端。例如,考虑来自客户端的以下请求,客户端可以将ReplyTo设置为自己的地址,将FaultTo设置为管理员端点地址。然后,服务器将成功消息发送到客户端,将故障消息发送到管理员地址[<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">

<SOAP-ENV:Header >
<wsa:To>server_uri</wsa:To>
<wsa:Action>action_uri</wsa:Action>
<wsa:From>client_address </wsa:From>
<wsa:ReplyTo>client_address</wsa:ReplyTo>
<wsa:FaultTo>admen_uri </wsa:FaultTo>
<wsa:MessageID>..</wsa:MessageID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest>....</tns:placeOrderReques>
</SOAP-ENV:Body></SOAP-ENV:Envelope>] 

在这个配方中,我们将使用 WS-Addressing 设置 Spring-WS。

准备就绪

在这个配方中,项目的名称是LiveRestaurant_R-1.11,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

操作步骤...

这个配方的步骤与通过注释负载根设置端点的步骤相同,只是端点类不同。因此,请按照上述配方的步骤,并使用 WS-Addressing 标准定义一个新的端点。

  1. 运行以下命令:
mvn clean package tomcat:run 

  1. 要进行测试,打开一个新的命令窗口到Liverestaurant_R-1.11-Client,并运行以下命令:
mvn clean package exec:java

  • 以下是服务器端的输出:
Sent response <SOAP-ENV:Envelope ...><SOAP-ENV:Header...>
<wsa:To ...>http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReqResponse</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:2beaead4-c04f-487c-86fc-caab64ad8461</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...><tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>...
for request <SOAP-ENV:Envelope ..><SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header><SOAP-ENV:Body>
<tns:placeOrderRequest ...>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
...
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope> 

它是如何工作的...

与前一个配方通过注释负载根设置端点相同,传入的 WS-Addressing SOAP 消息将被转发到端点(OrderEndpoint),该端点由@Endpoint自动检测到。从输出中可以看出,头部被添加到 SOAP 信封中,WS-Addressing 用于端点方法的映射和调度目的。

<SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header> 

在这个配方中,服务器应用了AnnotationActionEndpointMapping,它使用@Actionhttp://www.packtpub.com/OrderService/OrdReq)。@Action类似于@PayloadRoot,用于识别端点(OrderEndpoint)中的处理方法(handleOrderRequest)

@Endpoint
public class OrderEndpoint {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
@Action("http://www.packtpub.com/OrderService/OrdReq")
public @ResponsePayload
Source handleOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

另请参阅

在[第二章中讨论的配方为 WS-Addressing 端点创建 Web 服务客户端构建 SOAP Web 服务的客户端,以及在本章中讨论的配方通过注释负载根设置端点

使用 XPath 表达式设置端点

Spring-WS 允许我们使用 XPath 表达式和注释从端点方法的签名中提取传递的参数。例如,在端点方法的handleOrderRequest(@RequestPayload Source source)中,如果要查找源对象中任何元素的值,必须使用 Java API 来提取该值。您可以通过在方法的签名中使用 XPath 来从传入的 XML 数据中提取数据来消除在处理程序方法中使用 Java API,如下所示:handleOrderRequest(@XPathParam("/OrderRequest/message") String message)

这个配方演示了使用注释在端点映射中使用 XPath 表达式。

准备就绪

在这个配方中,项目的名称是LiveRestaurant_R-1.12,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

操作步骤...

这个配方的步骤与通过注释负载根设置端点的步骤相同,只是端点处理方法的实现不同。因此,请按照上述配方的步骤,并使用 XPath 表达式从传入消息中提取数据并创建响应。

  1. LiveRestaurant_R-1.12运行以下命令:
mvn clean package tomcat:run 

  1. 浏览以下链接以查看 Web 服务服务合同:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要进行测试,请打开一个新的命令窗口,转到LiveRestaurant_R-1.12-Client,并运行以下命令:
mvn clean package exec:java s

  • 以下是服务器端的输出:
Sent response ..
<tns:placeOrderResponse xmlns:tns="">
<tns:refNumber>order-John_Smith_9999</tns:refNumber>
</tns:placeOrderResponse>
...
for request ...
<tns:placeOrderRequest >
<order>
<refNumber>9999</refNumber>
<customer>
......
</customer>
<dateSubmitted>2008-09-29T05:49:45</dateSubmitted>
<orderDate>2014-09-19T03:18:33</orderDate>
<items>
<type>Snacks</type>
<name>Pitza</name>
<quantity>2</quantity>
</items>
</order>
</tns:placeOrderRequest>
...
Sent response...
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled>
</tns:cancelOrderResponse>
...
for request ...
<tns:cancelOrderRequest >
<refNumber>9999</refNumber>
</tns:cancelOrderRequest> 

它是如何工作的...

传递方法参数与通过注释有效负载根设置端点的方法相同,只是它使用了@XPathParam,它指定了消息中要作为参数传递给方法的数据的路径。在这里,XpathParamMethodArgumentResolver负责从消息中提取值并将其传递给方法。

注释XpathParam帮助MethodArgumentResolvers(XpathParamMethodArgumentResolver)从 XML 中提取信息并将节点值绑定到方法参数(使用// cause,整个消息被递归搜索,例如,//lName搜索整个placeRequestRequest消息)。相同的实现也用于方法cancelOrderRequest:

@Endpoint
public class OrderEndpoint {
private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleOrderRequest(@XPathParam("//fName") String fName,@XPathParam("//lName") String lName,@XPathParam("//refNumber") String refNumber) throws Exception {
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>" + orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleCancelOrderRequest(@XPathParam("//refNumber") String refNumber) throws Exception {
boolean cancelled = orderService.cancelOrder(refNumber);
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>");
}

方法参数可以是以下任何一种:

  • booleanBoolean

  • doubleDouble

  • String

  • Node

  • NodeList

另请参阅

本章讨论了通过注释有效负载根来设置端点的方法。

使用 DOM 处理传入的 XML 消息

端点的实现要求我们获取传入的 XML 消息并提取其数据。在 Java 中,有各种方法(W3C DOM、SAX、XPath、JAXB、Castor、XMLBeans、JiBX 或 XStream)用于从输入的 XML 消息中提取数据,但大多数方法都不是语言中立的。

DOM 是为了语言中立而创建的,最初用于 JavaScript 操作 HTML 页面。在 Java 中,W3C DOM 库用于与 XML 数据交互。来自 W3C DOM 库的类,例如org.w3c.dom.Document、org.w3c.dom.Element、org.w3c.dom.Nodeorg.w3c.dom.Text,用于从输入的 XML 消息中提取数据。

在这个配方中,W3C DOM 用于从传入的消息中提取数据。

准备就绪

在这个配方中,项目的名称是LiveRestaurant_R-1.13,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

这个配方的步骤与通过注释有效负载根设置端点的配方相同,只是端点处理方法的实现不同。因此,按照所述的配方的步骤,并使用 DOM 从传入的消息中提取数据并创建响应。

  1. 运行命令mvn clean package tomcat:run并浏览到以下链接:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response ....
<placeOrderResponse >
<refNumber>order-John_Smith_1234</refNumber></placeOrderResponse>
...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.... </tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
,

它是如何工作的...

传递方法参数与通过注释有效负载根设置端点的方法相同,只是我们使用了@RequestPayload,它指定了消息中要作为参数传递给方法的 DOM 元素数据。在这里,DomPayloadMethodProcessor负责从消息中提取值并将其传递给方法。由于返回类型也是由@ResponsePayload指定的 DOM 元素类型,因此DomPayloadMethodProcessor被用作返回处理程序。

@PayloadRoot注释告诉 Spring-WShandleCancelOrderRequest方法是 XML 消息的处理方法。此方法可以处理的消息类型由注释值指示(@RequestPayload元素告诉它是 DOM 元素类型)。在这种情况下,它可以处理具有placeOrderRequest本地部分和http://www.packtpub.com/liverestaurant/OrderService/schema命名空间的 XML 元素。

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest")
@ResponsePayload
public Element handlePlaceOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception {
String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent();
String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent();
String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent();

前面的代码从传入的 XML 消息(placeOrderRequest)中提取元素refNumber、fNamelName,通过方法getElementsByTagNameNS找到并返回refNumber、fNamelName元素中第一项的文本内容(通过item(0).getTextContent())。

代码的以下部分通过创建placeOrderResponse元素(使用document.createElementNS)来创建传出的 XML 消息)。然后,它创建子元素refNumber(使用document.createElementNS)并创建此元素的文本(使用createTextNode 和 appendChild)。然后,它将refNumber元素附加到响应元素placeOrderResponse(使用appendChild方法):

Document document = documentBuilder.newDocument();
Element responseElement = document.createElementNS(NAMESPACE_URI,
"placeOrderResponse");
Element canElem=document.createElementNS(NAMESPACE_URI,"refNumber");
Text responseText = document.createTextNode(orderService.placeOrder(fName, lName, refNumber));
canElem.appendChild(responseText);
responseElement.appendChild(canElem);
return responseElement;

另请参阅

在本章讨论的配方通过注释有效负载根设置端点和在第二章中讨论的配方在 HTTP 传输上创建 Web 服务客户端构建 SOAP Web 服务的客户端

使用 JDOM 处理传入的 XML 消息

端点的实现要求我们获取传入的 XML 消息并提取其数据。DOM 可以从 XML 文档中提取数据,但它速度慢,消耗内存,并且具有非常基本的功能。

JDOM 文档不会构建到内存中;它是按需构建的(延迟初始化设计模式)。此外,JDOM 通过提供标准的基于 Java 的集合接口,使得在文档树中导航或操作元素更容易。在这个配方中,JDOM 用于从传入的消息中提取数据。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.14,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

  • log4j-1.2.9.jar

  • jaxen-1.1.jar

  • xalan-2.7.0.jar

如何做...

这个配方的步骤与通过注释有效负载根设置端点配方的步骤相同,只是端点处理方法的实现不同。因此,请按照前述配方的步骤,使用 JDOM 从传入消息中提取数据,并创建响应。

  1. 运行以下命令:
mvn clean package tomcat:run 

  1. 浏览以下链接:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口并运行以下命令:
mvn exec:java exec:java 

  • 以下是服务器端的输出:
Sent response ...
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order> 

它是如何工作的...

它的工作方式与前面的配方中解释的方式相同,只是在其方法实现中使用了JDOM

代码的以下部分通过使用命名空间和 XPath 对象从传入的 XML 消息(placeOrderRequest)中提取值refNumber, fNamelName

Namespace namespace = Namespace.getNamespace("tns", NAMESPACE_URI);
XPath refNumberExpression = XPath.newInstance("//tns:refNumber");
refNumberExpression.addNamespace(namespace);
XPath fNameExpression = XPath.newInstance("//tns:fName");
fNameExpression.addNamespace(namespace);
XPath lNameExpression = XPath.newInstance("//tns:lName");
lNameExpression.addNamespace(namespace);
String refNumber = refNumberExpression.valueOf(placeOrderRequest);
String fName = fNameExpression.valueOf(placeOrderRequest);
String lName = lNameExpression.valueOf(placeOrderRequest);

代码的以下部分通过创建placeOrderResponse元素(使用new Element(...)))来创建传出消息。然后,它创建子元素refNumber(使用new Element(...))并创建此元素的文本(使用setText(...))。然后,它将消息元素附加到响应元素placeOrderResponse(使用addContent方法):

Namespace resNamespace = Namespace.getNamespace("tns", NAMESPACE_URI);
Element root = new Element("placeOrderResponse", resNamespace);
Element message = new Element("refNumber", resNamespace);
message.setText(orderService.placeOrder(fName, lName, refNumber));
root.addContent(message);
Document doc = new Document(root);
return doc.getRootElement();

另请参阅

在本章讨论的配方通过注释有效负载根设置端点使用 DOM 处理传入的 XML 消息

在第二章中讨论的配方在 HTTP 传输上创建 Web 服务客户端构建 SOAP Web 服务的客户端

使用 JAXB2 处理传入的 XML 消息

Java Architecture for XML Binding(JAXB)是用于对象-XML 编组的 Java 标准。JAXB 定义了一个用于从 XML 文档读取和写入 Java 对象的程序员 API。对象-XML 映射通常在类中进行注释。JAXB 提供了一组有用的注释,其中大多数具有默认值,使得这种编组工作变得容易。

这个配方演示了如何以非常简单的方式使用 JAXB 处理 Web 服务中的传入 XML 消息。为了简单起见,并且延续之前的配方,相同的配方被重新使用,稍微改进了将 XML 模式转换为域类的方法,以演示 JAXB 的用法。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-1.15,并且具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

这个食谱的步骤与食谱通过注释 payload-root 设置端点的步骤相同,只是端点处理方法的实现不同。因此,按照前述食谱的步骤,并使用 JAXB Marshaller/Un-Mashaller 将有效载荷转换为 POJO。

  1. 首先,我们定义了一组领域对象,我们需要从数据合同OrderService.xsd(参考食谱使用 JAXB2 进行编组)中编组到 XML/从 XML 中编组(参见第六章)。

  2. 更改端点的实现(OrderEndpoint)以使用 JAXB。

  3. 运行以下命令:

mvn clean package tomcat:run 

  1. 浏览以下链接:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口到Liverestaurant_R-1.15-Client并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response ....
<placeOrderResponse >
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>....
....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

它是如何工作的...

在前面的代码中,XML 在运行时与 Java 类绑定使用JAXB。传入的 XML 被转换为 Java 对象(解组)并在处理对象后,结果对象被编组回 XML 然后返回给调用者:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
PlaceOrderRequest request = (PlaceOrderRequest) unmarshal(source, PlaceOrderRequest.class);
PlaceOrderResponse response = new PlaceOrderResponse();
String refNumber=request.getOrder().getRefNumber();
String fName=request.getOrder().getCustomer().getName().getFName();
String lName=request.getOrder().getCustomer().getName().getLName();
response.setRefNumber(orderService.placeOrder(fName,lName,refNumber));
return marshal(response);
}
private Object unmarshal(Source source, Class clazz) throws JAXBException {
JAXBContext context;
try {
context = JAXBContext.newInstance(clazz);
Unmarshaller um = context.createUnmarshaller();
return um.unmarshal(source);
} catch (JAXBException e) {
e.printStackTrace();
throw e;
}
}
private Source marshal(Object obj) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
return new JAXBSource(context, obj);
}

JAXB上下文通过构造函数传递的 Java 类与类中的注释绑定,指示解组器在运行时将数据从 XML 标记实例化和加载到对象中。现在将对象传递给服务类(OrderServiceImpl)进行处理:

public class OrderServiceImpl implements OrderService {
@Service
public class OrderServiceImpl implements OrderService {
public String placeOrder( String fName,String lName,String refNumber){
return "order-"+fName+"_"+lName+"_"+refNumber;
}
public boolean cancelOrder( String refNumber ){
return true;
}

这种方法允许开发人员使用简单的编组技术与 Java 对象而不是 XML 代码一起工作。

另请参阅

在本章中讨论的食谱通过注释 payload-root 设置端点

在第六章中讨论的食谱使用 JAXB2 进行编组编组和对象-XML 映射(OXM)-使用编组器和解组器将 POJO 转换为 XML 消息/从 XML 消息转换为 POJO

使用拦截器在服务器端验证 XML 消息

数据合同是在设置 Spring-WS 时使用的基本概念。然而,在服务器端/客户端发送/回复 SOAP 消息之前,验证是一个基本要求。

Spring-WS 支持服务器端和客户端消息的验证。在这个食谱中,应用了服务器端验证,当不正确的请求到达服务器或不正确的响应从服务器发送到客户端时,它会抛出异常。

准备好

在这个食谱中,项目的名称是LiveRestaurant_R-1.16,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

这个食谱的步骤与使用 DOM 处理传入的 XML 消息的步骤相同,只是验证请求/响应消息的验证不同。

  1. 修改spring-ws-servlet.xml以包括PayloadValidatingInterceptor

  2. 运行以下命令:

mvn clean package tomcat:run 

  1. 浏览以下链接:
http://localhost:8080/LiveRestaurant/OrderService.wsdl

  1. 要测试,打开一个新的命令窗口到Liverestaurant_R-1.16-Client并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端的输出:
Sent response [...
<placeOrderResponse >
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
<tns:addressPrimary>
.....
</tns:addressPrimary>
.......
</tns:customer>
........
</tns:order>
</tns:placeOrderRequest>
WARN [http-8080-1] (AbstractFaultCreatingValidatingInterceptor.java:156) - XML validation error on request: cvc-complex-type.2.4.a: Invalid content was found s
tarting with element 'tns:address'. One of '{"http://www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.
........ Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http:
//www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>] 

它是如何工作的...

spring-ws-servlet.xml几乎与食谱使用 DOM 处理传入的 XML 消息中描述的相同,只是包括使用模式进行验证的拦截器validateRequestvalidateResponse

<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="schema" value="/WEB-INF/OrderService.xsd"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
</bean>
</sws:interceptors>

当运行客户端时,将向服务器发送两个请求。第一个将被处理并将响应发送回客户端,而第二个包含错误的元素(地址而不是addressPrimary),将发送错误的响应:

Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http: //www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>]
.... 

另请参阅

在本章中讨论的食谱通过注释 payload-root 设置端点

在第二章中讨论的食谱在 HTTP 传输上创建 Web 服务客户端

第二章:为 SOAP Web 服务构建客户端

在本章中,我们将涵盖:

  • 在 Eclipse 中设置 Web 服务客户端开发环境

  • 使用 Maven 设置 Web 服务客户端开发环境

  • 在 HTTP 传输上创建 Web 服务客户端

  • 在 JMS 传输上创建 Web 服务客户端

  • 在 E-mail 传输上创建 Web 服务客户端

  • 在 XMPP 传输上创建 Web 服务客户端

  • 使用 XPath 表达式创建 Web 服务客户端

  • 为 WS-Addressing 端点创建 Web 服务客户端

  • 使用 XSLT 转换 Web 服务消息

介绍

使用 Java API,如SAAJ,可以生成客户端 SOAP 消息,并将其传输到/从 Web 服务。但是,这需要额外的编码和关于 SOAP 消息的知识。

org.springframework.ws.client.core包含了客户端 API 的核心功能,可以简化调用服务器端 Web 服务。

这个包中的 API 提供了像WebServiceTemplate这样的模板类,简化了 Web 服务的使用。使用这些模板,您将能够在各种传输协议(HTTP、JMS、电子邮件、XMPP 等)上创建 Web 服务客户端,并发送/接收 XML 消息,以及在发送之前将对象编组为 XML。Spring 还提供了一些类,如StringSourceResult,简化了在使用WebServiceTemplate时传递和检索 XML 消息。

在本章中,前两个教程解释了如何在 Eclipse 和 Maven 中设置调用 Web 服务客户端的环境。

然后我们将讨论如何使用WebServiceTemplate在各种传输协议(HTTP、JMS、电子邮件、XMPP 等)上创建 Web 服务客户端。除此之外,使用 XPath 表达式设置 Web 服务客户端这个教程解释了如何从 XML 消息中检索数据。最后,在最后一个教程使用 XSLT 转换 Web 服务消息中,介绍了如何在客户端和服务器之间将 XML 消息转换为不同格式。为了设置 Web 服务服务器,使用了第一章中的一些教程,构建 SOAP Web 服务,并创建了一个单独的客户端项目,调用服务器端的 Web 服务。

在 Eclipse 中设置 Web 服务客户端开发环境

最简单的 Web 服务客户端是调用服务器端 Web 服务的 Java 类。在这个教程中,介绍了设置调用服务器端 Web 服务的环境。在这里,客户端的 Java 类以两种形式调用服务器端的 Web 服务。第一种是在类的主方法中调用 Web 服务的 Java 类。第二种是使用 JUnit 测试类调用服务器端的 Web 服务。

准备工作

这个教程类似于第一章中讨论的使用 Maven 构建和运行 Spring-WS这个教程,构建 SOAP Web 服务

  1. 下载并安装 Java EE 开发人员 Helios 的 Eclipse IDE。

  2. 在这个教程中,项目的名称是LiveRestaurant_R-2.1(服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

  • log4j-1.2.9.jar

  • jaxen-1.1.jarb

  • xalan-2.7.0.jar

  1. LiveRestaurant_R-2.1-Client(客户端)具有以下 Maven 依赖项:
  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

  • log4j-1.2.9.jar

  • jaxen-1.1.jar

  • xalan-2.7.0.jar

  • junit-4.7.jar

  1. 运行以下 Maven 命令,以便将客户端项目导入 Eclipse(客户端):
mvn eclipse:eclipse -Declipse.projectNameTemplate="LiveRestaurant_R-2.1-Client" 

如何做...

这个教程使用了第一章中讨论的使用 JDOM 处理传入的 XML 消息这个教程,构建 SOAP Web 服务作为服务器端项目。

  1. 在主方法中运行调用 Web 服务的 Java 类。

  2. 通过转到File | Import | General | Existing projects into workspace | LiveRestaurant_R-2..1-Client,将LiveRestaurant_R-2.1-Client导入 Eclipse 工作区。

  3. 转到命令提示符中的LiveRestaurant_R-2.1文件夹,并使用以下命令运行服务器:

mvn clean package tomcat:run 

  1. com.packtpub.liverestaurant.client包的src/main/java文件夹中选择OrderServiceClient类,然后选择Run As | Java Application

在客户端上运行 Java 类时的控制台输出如下:

Received response ....
<tns:placeOrderResponse > <tns:refNumber>order-John_Smith_9999</tns:refNumber>
</tns:placeOrderResponse>
for request...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>.... 

  1. 在 Eclipse 中运行一个 JUnit 测试用例。

  2. com.packtpub.liverestaurant.client包的src/test/java文件夹中选择OrderServiceClientTest类,然后选择Run As | Junit Test

运行 JUnit 测试用例时的控制台输出如下(您可以单击Console标签旁边的JUnit标签,查看测试用例是否成功):

Received response ..
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_9999</tns:refNumber>
</tns:placeOrderResponse>..
......
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

注意

要传递参数或自定义测试的设置,请选择测试单元类,Run As | Run Configuration |,然后在左窗格上双击JUnit

然后,您将能够自定义传递的参数或设置并运行客户端。

工作原理...

当运行调用 Web 服务的 Java 类的主方法时,Eclipse 通过以下 Java 类路径内部运行以下命令:

java -classpath com.packtpub.liverestaurant.client.OrderServiceClient 

当运行 JUnit 测试用例时,Eclipse 通过内部调用以下命令来运行 JUnit 框架的测试用例:

java -classpath com.packtpub.liverestaurant.client.OrderServiceClientTest 

另请参阅

在第一章中讨论的使用 Maven 构建和运行 Spring-WS 项目使用 JDOM 处理传入的 XML 消息配方,

本章讨论的使用 HTTP 传输创建 Web 服务客户端配方。

使用 Maven 设置 Web 服务客户端开发环境

Maven 支持使用命令提示符运行类的主方法以及 JUnit 测试用例。

在这个配方中,解释了设置 Maven 环境以调用客户端 Web 服务。在这里,客户端 Java 代码以两种形式调用服务器上的 Web 服务。第一种是在类的主方法中调用 Web 服务的 Java 类。第二种使用 JUnit 调用服务器端 Web 服务。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-2.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-2.2-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • junit-4.7.jar

如何做...

这个配方使用了第一章中讨论的使用 DOM 处理传入的 XML 消息配方,构建 SOAP Web 服务作为服务器端项目。

  1. 在主方法中运行调用 Web 服务的 Java 类。

  2. 转到命令提示符中的LiveRestaurant_R-2.2文件夹,并使用以下命令运行服务器:

mvn clean package tomcat:run 

  1. 转到文件夹LiveRestaurant_R-2.2-Client并运行以下命令:
mvn clean package exec:java 

  • 在客户端上运行 Maven 命令时,以下是输出:
Received response ....
<placeOrderResponse >
<refNumber>order-John_Smith_9999</refNumber>
</placeOrderResponse>....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

  1. 使用 Maven 运行 JUnit 测试用例。

  2. 转到命令提示符中的LiveRestaurant_R-2.2文件夹,并使用以下命令运行服务器:

mvn clean package tomcat:run 

  1. 转到文件夹LiveRestaurant_R-2.2-Client并运行以下命令:
mvn clean package 

  • 在客户端上使用 Maven 运行 JUnit 测试用例后,以下是输出:
Received response ...
<placeOrderResponse >
<refNumber>order-John_Smith_9999</refNumber>
</placeOrderResponse>...
for request ...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.702 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 

工作原理...

pom.xml文件中设置exec-maven-plugin,告诉 Maven 运行OrderServiceClientmainClass的 Java 类。该 Java 类告诉 Maven 运行OrderServiceClientmainClass

<build>
<finalName>LiveRestaurant_Client</finalName>
<plugins>
.......
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.packtpub.liverestaurant.client.OrderServiceClient</mainClass>
</configuration>
</plugin>
</plugins>
</build>

Maven 通过项目类路径内部运行以下命令:

java -classpath com.packtpub.liverestaurant.client.OrderServiceClient

要在 Maven 中设置和运行 JUnit 测试用例,测试类 OrderServiceClientTest 应该包含在文件夹 src/test/java 中,并且测试类名称应该以 Test 结尾(OrderServiceClientTest)。命令 mvn clean package 运行 src/test/java 文件夹中的所有测试用例(内部 Maven 调用):

java -classpath ...;junit.jar.. junit.textui.TestRunner com.packtpub.liverestaurant.client.OrderServiceClientTest ) . 

另请参阅

在第一章中讨论的使用 Maven 构建和运行 Spring-WS 项目使用 JDOM 处理传入的 XML 消息的配方,构建 SOAP Web 服务

在本章中讨论的在 HTTP 传输上创建 Web 服务客户端的配方。

在 HTTP 传输上创建 Web 服务客户端

在这个配方中,WebServiceTemplate 用于通过 HTTP 传输从客户端发送/接收简单的 XML 消息。

准备工作

在这个配方中,项目的名称是 LiveRestaurant_R-2.3(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是 LiveRestaurant_R-2.3-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • junit-4.7.jar

如何做到...

这个配方使用了在第一章中讨论的通过注释负载根来设置端点的配方,构建 SOAP Web 服务,作为服务器端项目。以下是如何设置客户端:

  1. 创建一个调用 WebServiceTemplate 中的 Web 服务服务器的类在 src/test 中。

  2. applicationContext.xml 文件中配置 WebServiceTemplate

  3. 从文件夹 Liverestaurant_R-2.3 运行以下命令:

mvn clean package tomcat:run 

  1. 打开一个新的命令窗口到 Liverestaurant_R-2.3-Client 并运行以下命令:
mvn clean package 

  • 以下是客户端输出:
Received response ....
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
.....
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.749 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

Liverestaurant_R-2.3 是一个服务器端项目,它重复使用了在第一章中讨论的通过注释负载根来设置端点的配方。构建 SOAP Web 服务

已配置的客户端 WebServiceTemplateapplicationContext.xml 文件(id="webServiceTemplate")用于发送和接收 XML 消息。可以从客户端程序中获取此 bean 的实例以发送和接收 XML 消息。

messageFactorySaajSoapMessageFactory 的一个实例,它被引用在 WebServiceTemplate 内。messageFactory 用于从 XML 消息创建 SOAP 数据包。默认的服务 URI 是 WebServiceTemplate 默认使用的 URI,用于发送/接收所有请求/响应:

<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://localhost:8080/LiveRestaurant/spring-ws/OrderService" />
</bean>

OrderServiceClientTest.java 是一个简单的 JUnit 测试用例,用于在 setUpBeforeClass() 方法中从 applicationContext.xml 中获取和初始化 WebServiceTemplate(由 @BeforeClass 标记)。在 testCancelOrderRequesttestPlaceOrderRequest 方法中(由 @Test 标记),WebServiceTemplate 发送一个简单的 XML 消息(由现有输入 XML 文件的 StringSource 对象创建),并接收包装在 Result 对象中的来自服务器的响应:

private static WebServiceTemplate wsTemplate = null;
private static InputStream isPlace;
private static InputStream isCancel;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
wsTemplate = (WebServiceTemplate) appContext.getBean("webServiceTemplate");
isPlace = new OrderServiceClientTest().getClass().getResourceAsStream("placeOrderRequest.xml");
isCancel = new OrderServiceClientTest().getClass().getResourceAsStream("cancelOrderRequest.xml");
}
@Test
public final void testPlaceOrderRequest() throws Exception {
Result result = invokeWS(isPlace);
Assert.assertTrue(result.toString().indexOf("placeOrderResponse")>0);
}
@Test
public final void testCancelOrderRequest() throws Exception {
Result result = invokeWS(isCancel);
Assert.assertTrue(result.toString().indexOf("cancelOrderResponse")>0);
}
private static Result invokeWS(InputStream is) {
StreamSource source = new StreamSource(is);
StringResult result = new StringResult();
wsTemplate.sendSourceAndReceiveToResult(source, result);
return result;
}

另请参阅

在第一章中讨论的通过注释负载根来设置端点的配方,构建 SOAP Web 服务和在本章中讨论的使用 Maven 设置 Web 服务客户端开发环境的配方。

在 JMS 传输上创建 Web 服务客户端

JMS(Java 消息服务)于 1999 年由 Sun Microsystems 作为 Java 2、J2EE 的一部分引入。使用 JMS 的系统可以同步或异步通信,并基于点对点和发布-订阅模型。Spring Web 服务提供了在 Spring 框架中基于 JMS 功能构建 JMS 协议的 Web 服务的功能。Spring Web 服务在 JMS 协议上提供以下通信功能:

  • 客户端和服务器可以断开连接,只有在发送/接收消息时才能连接

  • 客户端不需要等待服务器回复(例如,如果服务器需要很长时间来处理,例如进行复杂的数学计算)

  • JMS 提供了确保客户端和服务器之间消息传递的功能

在这个配方中,WebServiceTemplate用于在客户端上通过 JMS 传输发送/接收简单的 XML 消息。使用一个 JUnit 测试用例类在服务器端设置并使用WebServiceTemplate发送和接收消息。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-2.4,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • spring-jms-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • log4j-1.2.9.jar

  • jms-1.1.jar

  • activemq-core-4.1.1.jar

如何做...

本配方使用在第一章中讨论的配方在 JMS 传输上设置 Web 服务构建 SOAP Web 服务作为服务器端项目。

  1. 创建一个调用WebServiceTemplate的 Web 服务服务器的 JUnit 测试类。

  2. applicationContext中配置WebServiceTemplate以通过 JMS 协议发送消息。

  3. 运行命令mvn clean package。您将看到以下输出:

Received response ..
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

它是如何工作的...

在这个项目中,我们使用一个 JUnit 类在 JMS 传输上设置 Web 服务服务器。服务器使用PayloadEndpoint接收 XML 请求消息,并返回一个简单的 XML 消息作为响应(服务器已经在第一章中讨论的配方在 JMS 传输上设置 Web 服务构建 SOAP Web 服务中描述)。

已配置客户端WebServiceTemplateapplicationContext.xml文件(id="webServiceTemplate")用于发送和接收 XML 消息。可以从客户端程序中获取此 bean 的实例以发送和接收 XML 消息。messageFactorySaajSoapMessageFactory的一个实例,被引用在WebServiceTemplate内。messageFactory用于从 XML 消息创建 SOAP 数据包。默认服务 URI 是WebServiceTemplate默认使用的 JMS URI,用于发送/接收所有请求/响应。配置在WebServiceTemplate内的JmsMessageSender用于发送 JMS 消息。要使用JmsMessageSenderdefaultUriJMS URI应包含jms:前缀和目的地名称。一些JMS URI的例子是jms:SomeQueue, jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT, jms:RequestQueue?replyToName=ResponseName等。默认情况下,JmsMessageSender发送 JMSBytesMessage,但可以通过在 JMS URI 上使用messageType参数来覆盖使用TextMessages。例如,jms:Queue?messageType=TEXT_MESSAGE

<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>

JmsTransportWebServiceIntegrationTest.java是一个 JUnit 测试用例,从applicationContext.xml文件中获取并注入WebServiceTemplate(由@ContextConfiguration("applicationContext.xml")标记)。在testSendReceive()方法(由@Test标记),WebServiceTemplate发送一个简单的 XML 消息(由简单输入字符串的StringSource对象创建),并接收包装在Result对象中的服务器响应。在testSendReceive()方法(由@Test标记)中,发送和接收消息类似于 HTTP 客户端,并使用WebServiceTemplate.sendSourceAndReceiveToResult发送/接收消息:

@Test
public void testSendReceive() throws Exception {
InputStream is = new JmsTransportWebServiceIntegrationTest().getClass().getResourceAsStream("placeOrderRequest.xml");
StreamSource source = new StreamSource(is);
StringResult result = new StringResult();
webServiceTemplate.sendSourceAndReceiveToResult(source, result);
XMLAssert.assertXMLEqual("Invalid content received", expectedResponseContent, result.toString());
}

另请参阅

在第一章中讨论的配方在 JMS 传输上设置 Web 服务构建 SOAP Web 服务

使用 Spring Junit 对 Web 服务进行单元测试

在 E-mail 传输上创建 Web 服务客户端

在这个示例中,WebServiceTemplate 用于在客户端上通过电子邮件传输发送/接收简单的 XML 消息。使用第一章中讨论的 在电子邮件传输上设置 Web 服务 这个示例,构建 SOAP Web 服务 来设置 Web 服务。使用 JUnit 测试用例类来在服务器端设置 Web 服务,并使用 WebServiceTemplate 发送/接收消息。

准备工作

在这个示例中,项目的名称是 LiveRestaurant_R-2.5,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • mail-1.4.1.jar

  • mock-javamail-1.6.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

如何做...

这个示例使用第一章中讨论的 在电子邮件传输上设置 Web 服务 这个示例,构建 SOAP Web 服务 作为服务器端项目。

  1. 创建一个测试类,使用 WebServiceTemplate 调用 Web 服务服务器。

  2. applicationContext 中配置 WebServiceTemplate 以通过电子邮件协议发送消息。

  3. 运行命令 mvn clean package。以下是此命令的输出:

Received response
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

它是如何工作的...

该项目通过 JUnit 类在电子邮件传输上设置 Web 服务服务器。这个类使用 Spring JUnit 来加载应用程序上下文,首先设置服务器,然后运行客户端单元测试以验证其是否按预期运行。服务器已在第一章中讨论的 在电子邮件传输上设置 Web 服务 这个示例中解释过。

配置的客户端 WebServiceTemplate (id="webServiceTemplate")applicationContext.xml 文件用于发送和接收 XML 消息。可以从客户端程序中获取此 bean 的实例以发送和接收 XML 消息。messageFactorySaajSoapMessageFactory 的一个实例,被引用在 WebServiceTemplate 内。messageFactory 用于从 XML 消息创建 SOAP 数据包。transportURI 是一个由 WebServiceTemplate 使用的 URI,指示用于发送请求的服务器。storeURI 是一个 URI,配置在 WebServiceTemplate 内,指示用于轮询响应的服务器(通常是 POP3 或 IMAP 服务器)。默认 URI 是 WebServiceTemplate 默认使用的电子邮件地址 URI,用于发送/接收所有请求/响应:

<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="client@packtpubtest.com"/>
<property name="transportUri" value="smtp://smtp.packtpubtest.com"/>
<property name="storeUri" value="imap://client@packtpubtest.com/INBOX"/>
<property name="receiveSleepTime" value="1500"/>
<property name="session" ref="session"/>
</bean>
</property>
<property name="defaultUri" value="mailto:server@packtpubtest.com"/>
</bean>
<bean id="session" class="javax.mail.Session" factory-method="getInstance">
<constructor-arg>
<props/>
</constructor-arg>
</bean>

MailTransportWebServiceIntegrationTest.java 是一个 JUnit 测试用例,从 applicationContext.xml 中获取并注入 WebServiceTemplate(由 @ContextConfiguration("applicationContext.xml") 标记)。在 testWebServiceOnMailTransport() 方法中(由 @Test 标记),WebServiceTemplate 发送一个简单的 XML 消息(由输入 XML 文件的 StringSource 对象创建),并接收包装在 Result 对象中的来自服务器的响应。

@Test
public void testWebServiceOnMailTransport() throws Exception {
InputStream is = new MailTransportWebServiceIntegrationTest().getClass().getResourceAsStream("placeOrderRequest.xml");
StreamSource source = new StreamSource(is);
StringResult result = new StringResult();
webServiceTemplate.sendSourceAndReceiveToResult(source, result);
applicationContext.close();
XMLAssert.assertXMLEqual("Invalid content received", expectedResponseContent, result.toString());
}

另请参阅..

在第一章中讨论的 在电子邮件传输上设置 Web 服务 这个示例,构建 SOAP Web 服务

使用 Spring Junit 对 Web 服务进行单元测试

在 XMPP 传输上设置 Web 服务

XMPP(可扩展消息和出席协议)是一种开放和分散的 XML 路由技术,系统可以使用它向彼此发送 XMPP 消息。XMPP 网络由 XMPP 服务器、客户端和服务组成。使用 XMPP 的每个系统都由唯一的 ID(称为Jabber ID (JID))识别。XMPP 服务器发布 XMPP 服务,以提供对客户端的远程服务连接。

在这个配方中,WebServiceTemplate用于通过 XMPP 传输在客户端发送/接收简单的 XML 消息。使用了第一章中的在 XMPP 传输上设置 Web 服务配方,构建 SOAP Web 服务,来设置一个 Web 服务。使用了一个 JUnit 测试用例类来在服务器端设置 Web 服务,并使用WebServiceTemplate发送和接收消息。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-2.6,具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • smack-3.1.0.jar

如何做...

  1. 这个配方使用了在第一章中讨论的在 XMPP 传输上设置 Web 服务配方,构建 SOAP Web 服务,作为服务器端项目。

  2. 创建一个测试类,调用WebServiceTemplate调用 Web 服务服务器。

  3. applicationContext中配置WebServiceTemplate以通过 XMPP 协议发送消息。

  4. 运行命令mvn clean package。您将看到以下输出:

Received response ..
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest> 

工作原理...

该项目使用 JUnit 类在 XMPP 传输上设置了一个 Web 服务服务器。该服务器已经在配方在电子邮件传输上设置 Web 服务中解释过,在第一章中讨论了构建 SOAP Web 服务

已配置客户端WebServiceTemplateapplicationContext.xml文件(id="webServiceTemplate")用于发送和接收 XML 消息。可以从客户端程序中获取此 bean 的实例,以发送和接收 XML 消息。messageFactorySaajSoapMessageFactory的一个实例,被引用在WebServiceTemplate内。messageFactory用于从 XML 消息创建 SOAP 数据包。WebServiceTemplate使用XmppMessageSender发送消息到服务器。默认 URI 是WebServiceTemplate默认使用的 XMPP 地址 URI,用于发送/接收所有请求/响应:

<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:yourUserName@gmail.com"/>
</bean>

XMPPTransportWebServiceIntegrationTest.java是一个 JUnit 测试用例,从applicationContext.xml中获取并注入WebServiceTemplate(由@ContextConfiguration("applicationContext.xml")标记)。在testWebServiceOnXMPPTransport()方法中(由@Test标记),WebServiceTemplate发送一个 XML 消息(由简单的输入 XML 文件的StringSource对象创建),并接收服务器包装在Result对象中的响应。

@Autowired
private GenericApplicationContext applicationContext;
@Test
public void testWebServiceOnXMPPTransport() throws Exception {
StringResult result = new StringResult();
StringSource sc=new StringSource(requestContent);
webServiceTemplate.sendSourceAndReceiveToResult(sc, result);
XMLAssert.assertXMLEqual("Invalid content received", requestContent, result.toString());
applicationContext.close();
}

另请参阅

在第一章中讨论的在 XMPP 传输上设置 Web 服务配方,构建 SOAP Web 服务

使用 Spring JUnit 对 Web 服务进行单元测试

使用 XPath 表达式创建 Web 服务客户端

在 Java 编程中使用 XPath 是从 XML 消息中提取数据的标准方法之一。但是,它将 XML 节点/属性的 XPath 地址(最终可能非常长)与 Java 代码混合在一起。

Spring 提供了一个功能,可以从 Java 中提取这些地址,并将它们转移到 Spring 配置文件中。在这个配方中,使用了第一章中的通过注释有效负载根设置端点配方,构建 SOAP Web 服务,来设置一个 Web 服务服务器。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-2.7(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-2.7-Client(用于客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • junit-4.7.jar

  • log4j-1.2.9.jar

如何做...

此食谱使用了在服务器端项目中讨论的通过注释负载根设置端点食谱。

  1. applicationContext.xml中配置 XPath 表达式。

  2. applicationContext中配置WebServiceTemplate以通过 HTTP 协议发送消息,如食谱在 HTTP 传输上创建 Web 服务客户端中所述。

  3. 创建一个测试类,使用WebServiceTemplate调用 Web 服务服务器,并在 Java 代码中使用 XPath 表达式提取所需的值。

  4. 从文件夹Liverestaurant_R-2.7中运行命令mvn clean package tomcat:run

  5. 打开一个新的命令窗口到Liverestaurant_R-2.7-Client并运行以下命令:

mvn clean package. 

  • 以下是客户端代码的输出:
--Request
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
<tns:addressPrimary>
<tns:doorNo>808</tns:doorNo>
<tns:building>W8</tns:building>
<tns:street>St two</tns:street>
<tns:city>NY</tns:city>
<tns:country>US</tns:country>
<tns:phoneMobile>0018884488</tns:phoneMobile>
<tns:phoneLandLine>0017773366</tns:phoneLandLine>
<tns:email>d@b.c</tns:email>
</tns:addressPrimary>
<tns:addressSecondary>
<tns:doorNo>409</tns:doorNo>
<tns:building>W2</tns:building>
<tns:street>St one</tns:street>
<tns:city>NY</tns:city>
<tns:country>US</tns:country>
<tns:phoneMobile>0018882244</tns:phoneMobile>
<tns:phoneLandLine>0019991122</tns:phoneLandLine>
<tns:email>a@b.c</tns:email>
</tns:addressSecondary>
<tns:name>
<tns:fName>John</tns:fName>
<tns:mName>Paul</tns:mName>
<tns:lName>Smith</tns:lName>
</tns:name>
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
<!--Received response-->
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
...Request
<tns:cancelOrderRequest >
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>]
...Received response..
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse> 

工作原理...

设置客户端和服务器端,并使用WebserviceTemplate的方式与我们在食谱在 HTTP 传输上创建 Web 服务客户端中所做的一样。在客户端applicationContext.xml中配置了xpathExpPlacexpathExpCancel,并创建了XPathExpressionFactoryBean的实例,该实例获取所需数据的 XPath 属性和 XML 消息的命名空间:

<bean id="xpathExpCancel"
class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
<property name="expression" value="/tns:cancelOrderResponse/tns:cancelled" />
<property name="namespaces">
<props>
<prop key="tns">http://www.packtpub.com/liverestaurant/OrderService/schema</prop>
</props>
</property>
</bean>
<bean id="xpathExpPlace"
class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
<property name="expression" value="/tns:placeOrderResponse/tns:refNumber" />
<property name="namespaces">
<props>
<prop key="tns">http://www.packtpub.com/liverestaurant/OrderService/schema</prop>
</props>
</property>
</bean>

OrderServiceClientTest类中,可以从applicationContext中提取XPathExpressionFactoryBean的实例。String message = xpathExp.evaluateAsString(result.getNode())使用 XPath 表达式返回所需的数据:

@Test
public final void testPlaceOrderRequest() {
DOMResult result=invokeWS(isPlace);
String message = xpathExpPlace.evaluateAsString(result.getNode());
Assert.assertTrue(message.contains("Smith"));
}
@Test
public final void testCancelOrderRequest() {
DOMResult result= invokeWS(isCancel);
Boolean cancelled = xpathExpCancel.evaluateAsBoolean(result.getNode());
Assert.assertTrue(cancelled);
}

另请参阅

食谱使用 XPath 表达式设置端点在第一章 构建 SOAP Web 服务中讨论。

在本章中讨论的食谱在 HTTP 传输上创建 Web 服务客户端

使用 Spring JUnit 对 Web 服务进行单元测试。

为 WS-Addressing 端点创建 Web 服务客户端

如食谱设置一个传输中立的 WS-Addressing 端点中所述,讨论在第一章 构建 SOAP Web 服务,WS-Addressing 是一种替代的路由方式。WS-Addressing 将路由数据与消息分开,并将其包含在 SOAP 头中,而不是在 SOAP 消息的主体中。以下是从客户端发送的 WS-Addressing 样式的 SOAP 消息示例:

<SOAP-ENV:Header >
<wsa:To>server_uri</wsa:To>
<wsa:Action>action_uri</wsa:Action>
<wsa:From>client_address </wsa:From>
<wsa:ReplyTo>client_address</wsa:ReplyTo>
<wsa:FaultTo>admen_uri </wsa:FaultTo>
<wsa:MessageID>..</wsa:MessageID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest>....</tns:placeOrderReques>
</SOAP-ENV:Body></SOAP-ENV:Envelope>] 

在使用 WS-Addressing 时,与其他方法(包括在消息中包含路由数据)相比,客户端或服务器可以访问更多功能。例如,客户端可以将ReplyTo设置为自己,将FaultTo设置为管理员端点地址。然后服务器将成功消息发送到客户端,将故障消息发送到管理员地址。

Spring-WS 支持客户端和服务器端的 WS-Addressing。要为客户端创建 WS-Addressing 头,可以使用org.springframework.ws.soap.addressing.client.ActionCallback。此回调将Action头保留为参数。它还使用 WS-Addressing 版本和To头。

在此食谱中,使用了在第一章 构建 SOAP Web 服务中讨论的设置一个传输中立的 WS-Addressing 端点食谱来设置 WS-Addressing Web 服务。在这里使用客户端应用程序来调用服务器并返回响应对象。

准备工作

在此食谱中,项目名称为LiveRestaurant_R-2.8(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-2.8-Client(用于客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • junit-4.7.jar

  • log4j-1.2.9.jar

如何做...

这个配方使用了第一章中讨论的为 Web 服务设置与传输无关的 WS-Addressing 端点配方,构建 SOAP Web 服务,作为服务器端项目。创建 WS-Addressing 的客户端与在 HTTP 传输上创建 Web 服务客户端配方中描述的方式相同,不使用 WebServiceTemplate。为了在客户端上添加 WS-Addressing 头,WebServiceTemplatesendSourceAndReceiveToResult方法获得一个ActionCallBack实例。

  1. 从文件夹LiveRestaurant_R-2.8中运行以下命令:
mvn clean package tomcat:run 

  1. 打开一个新的命令窗口到LiveRestaurant_R-2.8-Client,并运行以下命令:
mvn clean package 

  • 以下是客户端输出:
Received response [<SOAP-ENV:Envelope xmlns:SOAP-ENV="..../">
<SOAP-ENV:Header >
<wsa:To SOAP-ENV:mustUnderstand="1">....</wsa:To><wsa:Action>http://www.packtpub.com/OrderService/CanOrdReqResponse</wsa:Action>
<wsa:MessageID>....</wsa:MessageID>
<wsa:RelatesTo>...</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>]
for request ...
<SOAP-ENV:Envelope xmlns:SOAP
-ENV=".."><SOAP-ENV:Header >
<wsa:To SOAP-ENV:mustUnderstand="1">http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:To SOAP-ENV:mustUnderstand="1">http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/CanOrdReq</wsa:Action>
<wsa:MessageID>..</wsa:MessageID>
</SOAP-ENV:Header><SOAP-ENV:Body/>
</SOAP-ENV:Envelope>]
<?xml version="1.0" encoding="UTF-8"?>
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse> 

工作原理...

Liverestaurant_R-2.8项目是一个支持 WS-Addressing 端点的服务器端 Web 服务。

已配置的客户端WebServiceTemplateapplicationContext.xml文件(id="webServiceTemplate")用于发送和接收 XML 消息,如*在 HTTP 传输上创建 Web 服务客户端*配方中描述的,除了使用WebServiceTemplate`的 Java 类的实现。

WS-Addressing 客户端将ActionCallBack的实例传递给WebServiceTemplatesendSourceAndReceiveToResult方法。使用ActionCallBack,客户端添加一个包含Action URI 的自定义头,例如,www.packtpub.com/OrderService/OrdReqTo URI,例如,www.packtpub.com/liverestaurant/OrderService/schema

@Test
public final void testPlaceOrderRequest() throws URISyntaxException {
invokeWS(isPlace,"http://www.packtpub.com/OrderService/OrdReq");
}
@Test
public final void testCancelOrderRequest() throws URISyntaxException {
invokeWS(isCancel,"http://www.packtpub.com/OrderService/CanOrdReq");
}
private static Result invokeWS(InputStream is,String action) throws URISyntaxException {
StreamSource source = new StreamSource(is);
StringResult result = new StringResult();
wsTemplate.sendSourceAndReceiveToResult(source, new ActionCallback(new URI(action),new Addressing10(),new URI("http://www.packtpub.com/liverestaurant/OrderService/schema")),
result);
return result;
}

使用这个头,服务器端将能够在端点中找到方法(使用@Action注释)。

参见

在第一章中讨论的为 Web 服务设置与传输无关的 WS-Addressing 端点配方,构建 SOAP Web 服务

在本章中讨论的在 HTTP 传输上创建 Web 服务客户端配方。

使用 Spring JUnit 对 Web 服务进行单元测试

使用 XSLT 转换 Web 服务消息

最终,Web 服务的客户端可能使用不同版本的 XML 消息,要求在服务器端使用相同的 Web 服务。

Spring Web 服务提供PayloadTransformingInterceptor。这个端点拦截器使用 XSLT 样式表,在需要多个版本的 Web 服务时非常有用。使用这个拦截器,您可以将消息的旧格式转换为新格式。

在这个配方中,使用第一章中的为 Web 服务设置简单的端点映射配方,构建 SOAP Web 服务,来设置一个 Web 服务,这里的客户端应用程序调用服务器并返回响应消息。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-2.9(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-2.9-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • junit-4.7.jar

  • log4j-1.2.9.jar

如何做...

这个配方使用了第一章中讨论的为 Web 服务设置简单的端点映射配方,构建 SOAP Web 服务,作为服务器端项目。客户端与在 HTTP 传输上创建 Web 服务客户端配方中讨论的相同,除了 XSLT 文件及其在服务器端应用程序上下文文件中的配置:

  1. 创建 XSLT 文件(oldResponse.xslt,oldRequest.xslt)。

  2. 修改LiveRestaurant_R-2.9中的spring-ws-servlet.xml文件以包含 XSLT 文件

  3. 从文件夹Liverestaurant_R-2.9中运行以下命令:

mvn clean package tomcat:run 

  1. 打开一个新的命令窗口到Liverestaurant_R-2.9-Client,并运行以下命令:
mvn clean package 

  • 以下是客户端输出:
Received response...
<ns:OrderResponse  message="Order Accepted!"/>...
for request ....
<OrderRequest  message="This is a sample Order Message"/> 

  • 以下是服务器端输出:
actual request ..
<ns:OrderRequest >
<ns:message>This is a sample Order Message</ns:message></ns:OrderRequest>
actual response = <ns:OrderResponse >
<ns:message>Order Accepted!</ns:message></ns:OrderResponse> 

它是如何工作的...

服务器端与第一章中描述的设置简单的端点映射用于 Web 服务的配方相同,构建 SOAP Web 服务。在客户端,WebServiceTemplateOrderServiceClientTest.java在 HTTP 传输上创建 Web 服务客户端的配方中描述的相同。

唯一的区别是服务器应用程序上下文文件。spring-servlet.xml中的transformingInterceptor bean 使用oldRequests.xsltoldResponse.xslt分别将旧的请求 XML 消息转换为服务器的更新版本,反之亦然:

. <bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
<property name="endpoints">
<ref bean="OrderServiceEndpoint" />
</property>
<property name="methodPrefix" value="handle"></property>
<property name="interceptors">
<list>
<bean
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
<property name="logRequest" value="true" />
<property name="logResponse" value="true" />
</bean>
<bean id="transformingInterceptor"
class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
<property name="requestXslt" value="/WEB-INF/oldRequests.xslt" />
<property name="responseXslt" value="/WEB-INF/oldResponse.xslt" />
</bean>
</list>
</property>
</bean>

另请参阅

在第一章中讨论的设置简单的端点映射用于 Web 服务的配方,构建 SOAP Web 服务

使用 Spring JUnit 对 Web 服务进行单元测试。

第三章:测试和监视 Web 服务

在本章中,我们将涵盖:

  • 使用 Spring-JUnit 支持进行集成测试

  • 使用MockWebServiceClient进行服务器端集成测试

  • 使用MockWebServiceServer进行客户端集成测试

  • 使用 TCPMon 监视 Web 服务的 TCP 消息

  • 使用 soapUI 监视和负载/功能测试 Web 服务

介绍

新的软件开发策略需要全面的测试,以实现软件开发过程中的质量。测试驱动设计(TDD)是开发过程的一种演进方法,它结合了测试优先的开发过程和重构。在测试优先的开发过程中,您在编写完整的生产代码之前编写测试以简化测试。这种测试包括单元测试和集成测试。

Spring 提供了使用 spring-test 包的集成测试功能支持。这些功能包括依赖注入和在测试环境中加载应用程序上下文。

编写一个使用模拟框架(如 EasyMock 和 JMock)测试 Web 服务的单元测试非常容易。但是,它不测试 XML 消息的内容,因此不模拟测试的真实生产环境。

Spring Web-Services 2.0 提供了创建服务器端集成测试以及客户端集成测试的功能。使用这些集成测试功能,可以在不部署在服务器上时测试 SOAP 服务,当测试服务器端时,而在测试客户端时无需设置服务器。

在第一个配方中,我们将讨论如何使用 Spring 框架进行集成测试。在接下来的两个配方中,详细介绍了 Spring-WS 2.0 的集成测试的新功能。在最后两个配方中,介绍了使用 soapUI 和 TCPMon 等工具监视和测试 Web 服务。

使用 Spring-JUnit 支持进行集成测试

Spring 支持使用org.springframework.test包中的类进行集成测试。这些功能使用生产应用程序上下文或任何用于测试目的的自定义应用程序上下文在测试用例中提供依赖注入。本教程介绍了如何使用具有功能的 JUnit 测试用例,spring-test.jar,JUnit 4.7 和 XMLUnit 1.1。

注意

请注意,要运行集成测试,我们需要启动服务器。但是,在接下来的两个配方中,我们将使用 Spring-WS 2.0 的集成测试的新功能,无需启动服务器。

准备工作

在本配方中,项目名称为LiveRestaurant_R-3.1(服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-3.1-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

如何做...

本教程使用了在第一章中讨论的通过注释有效负载根设置端点配方中使用的项目,构建 SOAP Web 服务,作为服务器端项目。以下是客户端设置:

  1. 创建一个调用src/test中的WebServiceTemplate的 Web 服务服务器的测试类。

  2. applicationContext.xml中配置WebServiceTemplate

  3. 从文件夹Liverestaurant_R-3.1运行以下命令:

mvn clean package tomcat:run 

  1. 打开一个新的命令窗口到Liverestaurant_R-3.1-Client并运行以下命令:
mvn clean package. 

  • 以下是客户端输出:
.................
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.packtpub.liverestaurant.client.OrderServiceClientTest
............................
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.633 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

服务器端项目设置了一个 Web 服务服务器,客户端项目运行集成测试,并向服务器发送预定义的请求消息,并从服务器获取响应消息。然后将服务器响应与预期响应进行比较。Web 服务的设置和 Web 服务的客户端已在前两章中详细介绍。这里只详细介绍测试框架。

OrderServiceClientTest.java中,方法setUpBefore()将首先被调用以初始化数据(因为它被@before注释),然后将调用由@Test注释的测试方法(testCancelOrderRequesttestPalceOrderRequest),最后,方法setUpAfter()将被调用以释放资源(因为它被@after注释)。

当您运行mvn clean package时,Maven 会构建并运行src/test/java文件夹中的任何测试类。因此,在OrderServiceClientTest.java中,首先将加载测试应用程序上下文。在应用程序上下文中,只需要WebServiceTemplate的配置:

<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://localhost:8080/LiveRestaurant/spring-ws/OrderService" />
</bean>

OrderServiceClientTest.java中,为了包含 Spring 依赖注入,并设置和运行测试,代码用一些信息进行了注释。JUnit @RunWith注解告诉 JUnit 使用 Spring TestRunner。Spring 的@ContextConfiguration注解告诉加载哪个应用程序上下文,并使用此上下文注入applicationContextwebServiceTemplate,这些都用@Autowired进行了注解:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class OrderServiceClientTest {
@Autowired
private WebServiceTemplate webServiceTemplate;
........

JUnit 中的@Before告诉在运行测试用例之前运行标记的方法(setUpBefore)。JUnit @After导致在执行测试用例后调用标记的方法。JUnit 中的@Test将标记的方法(testCancelOrderRequesttestPlaceOrderRequest)转换为 JUnit 测试方法:

@After
public void setUpAfter() {
applicationContext.close();
}
@Test
public final void testPlaceOrderRequest() throws Exception {
Result result = invokeWS(placeOrderRequest);
XMLAssert.assertXMLEqual("Invalid content received", getStringFromInputStream(placeOrderResponse), result.toString());
}
@Test
public final void testCancelOrderRequest() throws Exception {
Result result = invokeWS(cancelOrderRequest);
XMLAssert.assertXMLEqual("Invalid content received", getStringFromInputStream(cancelOrderResponse), result.toString());
}
private Result invokeWS(InputStream is) {
StreamSource source = new StreamSource(is);
StringResult result = new StringResult();
webServiceTemplate.sendSourceAndReceiveToResult(source, result);
return result;
}
public String getStringFromInputStream (InputStream is)
throws IOException {
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int result = bis.read();
while(result != -1) {
byte b = (byte)result;
buf.write(b);
result = bis.read();
}
return buf.toString();
}

请注意,对于每个测试方法,@After@Before方法将被执行一次。XMLAssert.assertXMLEqual比较实际结果和预期的 XML 消息。

提示

在实际情况下,数据将每天动态变化。我们应该能够根据日期和数据库动态构建数据。这有助于持续集成和一段时间内的冒烟测试。

另请参阅

在第一章中讨论的通过注释 payload-root 设置端点配方,构建 SOAP Web 服务

在第二章中讨论的在 HTTP 传输上创建 Web 服务客户端配方,构建 SOAP Web 服务的客户端

使用 MockWebServiceClient 进行服务器端集成测试

编写使用 EasyMock 和 JMock 等模拟框架测试 Web 服务的单元测试非常容易。但是,它不测试 XML 消息的内容,因此它不模拟测试的真实生产环境(因为这些模拟对象模拟软件的一部分,而这部分软件没有运行,这既不是单元测试也不是集成测试)。

Spring Web-Services 2.0 提供了创建服务器端集成测试的功能。使用这个功能,可以非常简单地测试 SOAP 服务,而无需在服务器上部署,也无需在 Spring 配置文件中配置测试客户端。

服务器端集成测试的主要类是org.springframework.ws.test.server包中的MockWebServiceClient。这个类创建一个请求消息,将请求发送到服务,并获取响应消息。客户端将响应与预期消息进行比较。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-3.2(作为包含使用MockWebServiceClient的测试用例的服务器端 Web 服务),并具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

这个配方使用了来自通过注释 payload-root 设置端点的项目,该项目在第一章中讨论,构建 SOAP Web 服务,作为服务器端项目。以下是测试用例的设置:

  1. pom.xml中包含以下数据:
<testResources>
<testResource>
<directory>src/main/webapp</directory>
</testResource>
</testResources>
</build>

  1. src/test/java文件夹中添加测试用例类。

  2. Liverestaurant_R-3.2运行以下命令:

mvn clean package 

  • 以下是服务器端的输出:
..................
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.packtpub.liverestaurant.service.test.OrderServiceServerSideIntegrationTest
l.........
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.047 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

在类OrderServiceServerSideIntegrationTest.java中,注释和单元测试材料与配方使用 Spring-JUnit 支持进行集成测试中使用的相同。唯一的区别是我们不在这里设置服务器。相反,我们在测试用例类中加载服务器应用上下文:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/WEB-INF/spring-ws-servlet.xml")
public class OrderServiceServerSideIntegrationTest {
.......................

@Before方法中,测试用例类初始化了客户端模拟对象和 XML 消息:

@Before
public void createClient() {
wsMockClient = MockWebServiceClient.createClient(applicationContext);
placeOrderRequest = new OrderServiceServerSideIntegrationTest().getClass().getResourceAsStream("placeOrderRequest.xml");
cancelOrderRequest = new OrderServiceServerSideIntegrationTest().getClass().getResourceAsStream("cancelOrderRequest.xml");
placeOrderResponse = new OrderServiceServerSideIntegrationTest().getClass().getResourceAsStream("placeOrderResponse.xml");
cancelOrderRsponse = new OrderServiceServerSideIntegrationTest().getClass().getResourceAsStream("cancelOrderResponse.xml");
}

然后,它发送一条消息并接收响应。然后比较预期的响应和实际的响应:

@After
public void setUpAfterClass() {
applicationContext.close();
}
@Test
public final void testPlaceOrderRequest() throws Exception {
Source requestPayload = new StreamSource(placeOrderRequest);
Source responsePayload = new StreamSource(placeOrderResponse);
wsMockClient.sendRequest(withPayload(requestPayload)).
andExpect(payload(responsePayload));
}
@Test
public final void testCancelOrderRequest() throws Exception {
Source requestPayload = new StreamSource(cancelOrderRequest);
Source responsePayload = new StreamSource(cancelOrderRsponse);
wsMockClient.sendRequest(withPayload(requestPayload)).
andExpect(payload(responsePayload));
}

在方法createClient()中,MockWebServiceClient.createClient(applicationContext)创建了客户端模拟对象(wsMockClient)的实例。在测试用例方法(testCancelOrderRequest, testPlaceOrderRequest)中,使用代码wsMockClient.sendRequest(withPayload(requestPayload)).andExpect(payload(responsePayload)),模拟客户端发送 XML 消息并将响应(来自服务器端点)与预期响应进行比较(客户端模拟知道来自应用上下文文件的服务器端点,当它向服务器发送请求时,调用端点方法并获取响应)。

另请参阅

在本章中讨论的配方使用 Spring-JUnit 支持进行集成测试使用 MockWebServiceServer 进行客户端集成测试

在第一章中讨论的配方通过注释 payload-root 设置端点构建 SOAP Web 服务

使用 MockWebServiceServer 进行客户端集成测试

编写一个使用模拟框架测试 Web 服务客户端的客户端单元测试非常容易。但是,它不会测试通过线路发送的 XML 消息的内容,特别是当模拟整个客户端类时。

Spring Web 服务 2.0 提供了创建客户端集成测试的功能。使用这个功能,很容易测试 SOAP 服务的客户端而不需要设置服务器。

客户端集成测试的主要类是org.springframework.ws.test.server包中的MockWebServiceServer。这个类接受来自客户端的请求消息,对其进行验证,然后将响应消息返回给客户端。

由于这个项目是使用MockWebServiceServer进行客户端测试集成,它不需要任何外部服务器端 Web 服务。

准备就绪

在这个配方中,项目的名称是LiveRestaurant_R-3.3-Client(作为客户端项目,包括使用MockServiceServer作为服务器的测试用例),并且具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

这个配方使用了来自在 HTTP 传输上创建 Web 服务客户端的客户端项目,该项目在第二章中讨论,构建 SOAP Web 服务的客户端。以下是测试用例的设置:

  1. src/test下创建一个测试用例类。

  2. 创建一个扩展WebServiceGatewaySupport的类来发送/接收消息。

  3. Liverestaurant_R-3.3-Client运行以下命令:

mvn clean package 

  • 以下是客户端输出:
**************************
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.packtpub.liverestaurant.client.test.ClientSideIntegrationTest
........
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.945 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

测试用例类ClientSideIntegrationTest.java中的流程如下:

  1. 使用WebServiceGatewaySupport(扩展WebServiceGatewaySupportOrderServiceClient)创建MockWebServiceServer。您还可以使用WebServiceTemplate或使用ApplicationContext创建MockWebServiceServer

  2. 使用RequestMatcher设置请求期望,并使用ResponseCreator返回响应。

  3. 通过使用WebServiceTemplate进行客户端调用。

  4. 调用verify方法以确保满足所有期望。应用程序上下文文件只是WebServiceTemplateOrderServiceClient的配置:

<bean id="client" class=" com.packtpub.liverestaurant.client.test.OrderServiceClient">
<property name="webServiceTemplate" ref="webServiceTemplate"/>
</bean>
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="defaultUri" value="http://www.packtpub.com/liverestaurant/OrderService/schema"/>
</bean>
</beans>

ClientSideIntegrationTest.java中,注释和单元测试材料与使用 Spring-JUnit 支持进行集成测试配方中使用的相同。方法createServer()使用WebServiceGatewaySupportOrderServiceClient扩展WebServiceGatewaySupport)创建MockWebServiceServer

public class OrderServiceClient extends WebServiceGatewaySupport {
public Result getStringResult(Source source) {
StringResult result = new StringResult();
getWebServiceTemplate().sendSourceAndReceiveToResult(source, result);
return result;
}
}

在测试中,方法testExpectedRequestResponse, mockServer.expect设置了预期的请求和响应(webServiceTemplateclient-integration-test.xml中以“测试模式”配置。当调用sendSourceAndReceiveToResult方法时,模板会在没有任何真正的 HTTP 连接的情况下虚拟调用服务器)。然后client.getStringResult调用webserviceTemplate来调用服务器(MockWebServiceServer)。然后,mockServer.verify检查返回的响应是否与预期的响应匹配:

@Test
public void testExpectedRequestResponse() throws Exception {
Source requestPayload = new StringSource(getStringFromInputStream(placeOrderRequest));
Source responsePayload = new StringSource(getStringFromInputStream(placeOrderResponse));
mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));
Result result = client.getStringResult(requestPayload);
XMLAssert.assertXMLEqual("Invalid content received", xmlToString(responsePayload), result.toString());
mockServer.verify();
}

在测试方法testSchema中,使用了预期请求和响应的模式,而不是使用硬编码的请求/响应。此测试可以测试请求/响应的格式是否符合预期。如下所示:

. @Test
public void testSchema() throws Exception {
Resource schema=new FileSystemResource("orderService.xsd");
mockServer.expect(validPayload(schema));
client.getStringResult(new StreamSource(placeOrderRequest));
mockServer.verify();
}

在测试方法testSchemaWithWrongRequest中,使用了预期请求和响应的模式。然而,客户端试图发送无效请求,这将导致失败:

@Test(expected = AssertionError.class)
public void testSchemaWithWrongRequest() throws Exception {
Resource schema=new FileSystemResource("orderService.xsd");
mockServer.expect(validPayload(schema));
client.getStringResult(new StringSource(getStringFromInputStream(cancelOrderRequestWrong)));
mockServer.verify();
}

另请参阅

本章讨论了使用 Spring-JUnit 支持进行集成测试的配方。

使用 TCPMon 监视 Web 服务的 TCP 消息

TCPMon是一个带有 Swing UI 的 Apache 项目,它提供了监视客户端和服务器之间传输的基于 TCP 的消息的功能。还可以使用 TCPMon 向服务器发送 SOAP 消息。

本配方介绍了如何监视 Web 服务客户端和服务器之间传递的消息。此外,它还展示了如何使用 TCPMon 发送 SOAP 消息。该配方使用 Spring-JUnit 支持进行集成测试用于服务器端和客户端项目。

准备工作

从网站ws.apache.org/commons/tcpmon/download.cgi下载并安装 TCPMon 1.0。

如何操作...

监视客户端和服务器之间的消息如下:

  1. 在 Windows 上使用tcpmon.bat(Linux 上的 tcpmon.sh)运行它。

  2. Listen port #Target port #字段中输入值80818080,然后单击Add选项。如何操作...

  3. 更改LiveRestaurant_R-3.1-Client中的applicationContext.xml以使用8081端口进行webserviceTemplate

<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://localhost:8081/LiveRestaurant/spring-ws/OrderService" />
</bean>

  1. 使用以下命令从项目LiveRestaurant_R-3.1运行服务器:
mvn clean package tomcat:run 

  1. 从项目LiveRestaurant_R-3.1-Client中使用以下命令运行客户端:
mvn clean package 

  1. 转到Port 8081选项卡,查看请求和响应消息,如下截图所示:

如何操作...

按以下方式向服务器发送 SOAP 请求:

转到Sender选项卡。输入 SOAP 服务地址和 SOAP 请求消息,然后单击Send按钮查看响应:

如何操作...

工作原理...

监视客户端和 Web 服务服务器之间传输的消息是 TCPMon 的最重要用途。此外,TCPMon 还可以用作客户端向 Web 服务服务器发送消息。这是一个中间角色,显示了客户端和服务器之间传输的消息。客户端必须指向中间件而不是服务器服务。

工作原理...

第二个活动(向服务器发送 SOAP 请求)显示使用 TCPMon 向服务器发送消息,接收响应,并在 TCPMon 上显示所有这些。

另请参阅

本章讨论了使用 Spring-JUnit 支持进行集成测试的方法。

使用 soapUI 监控和负载/功能测试 Web 服务

soapUI 是用于测试 Web 服务的开源测试解决方案。使用用户友好的 GUI,该工具提供了创建和执行自动功能和负载测试以及监控 SOAP 消息的功能。

本方法介绍了如何使用 soapUI 监控 Web 服务的 SOAP 消息以及功能和负载测试。为了设置 Web 服务,使用了Recipe 3.1使用 Spring-JUnit 支持进行集成测试

准备工作

通过执行以下步骤开始:

  1. 安装并运行 soapUI 4.0 (www.soapui.org/)。

  2. 从文件夹LiveRestaurant_R-3.1运行以下命令:

mvn clean package tomcat:run 

如何操作...

要运行功能测试并监控 SOAP 消息,请执行以下步骤:

  1. 右键单击Projects节点。选择New soapUI Project并输入 WSDL URL 和Project Name操作步骤...

  2. 在导航窗格中右键单击项目名称OrderService。选择Launch HTTP Monitor并启用Set as Global Proxy选项。单击OK按钮:操作步骤...

  3. 展开OrderService方法(cancelOrderplaceOrder)。双击cancelOrder。单击Submit Request to Specific Endpoint URLRequest1屏幕左上角的绿色图标)。这是此操作的输出:操作步骤...

  4. 右键单击OrderServiceSoap11 | Generate Test Suite | OK。输入OrderServiceSoap11 TestSuite操作步骤...

  5. 在导航窗格中双击OrderServiceSoap11 TestSuite。单击运行所选的TestCases操作步骤...

  6. 当运行测试套件时,以下是输出:

操作步骤...

进行负载测试如下:

  1. 右键单击cancelOrder测试用例。选择New Local Test并输入Load Test Name操作步骤...

  2. 双击Load test name。输入Parameter并单击Run Load Test操作步骤...

  3. 以下是测试的输出:

操作步骤...

工作原理...

功能测试和监控 SOAP 消息:soapUI 提供三个级别的功能测试:测试套件、测试用例和测试步骤。

测试用例是从 WSDL 文件生成的单元测试,测试套件是这些单元测试的集合。测试步骤控制执行流程并验证要测试的服务的功能。例如,前面提到的cancelOrder测试套件中的一个测试用例可能首先测试数据库。如果有这样的订单可用,它会取消订单。

负载测试:soapUI 提供了在测试用例上运行多个线程(取决于您的计算机硬件限制)的功能。运行负载测试时,底层测试用例将在每个线程内部克隆。延迟设置让每个线程在开始之前等待,并让 Web 服务为每个线程休息。

另请参阅

本章讨论了使用 Spring-JUnit 支持进行集成测试的方法。

第四章:异常/SOAP 故障处理

在本章中,我们将涵盖:

  • 通过将异常的消息作为 SOAP 故障字符串返回来处理服务器端异常

  • 将异常类名称映射到 SOAP 故障

  • 使用@SOAPFault对异常类进行注释

  • 在 Spring-WS 中编写自己的异常解析器

介绍

在处理 Web 服务时生成的服务器端异常被传输为 SOAP 故障。SOAP <Fault>元素用于在 SOAP 消息中携带错误和状态信息。

以下代码表示 SOAP 消息中 SOAP 故障元素的一般结构:

<SOAP-ENV:Fault>
<faultcode xsi:type="xsd:string">SOAFP-ENV:Client</faultcode>
<faultstring xsi:type="xsd:string">
A human readable summary of the fault
</faultstring>
<detail xsi:type="xsd:string">
Application specific error information related to the Body element
</detail>
</SOAP-ENV:Fault>

如果存在Fault元素,则必须作为Body元素的子元素出现。SOAP 消息中只能出现一次Fault元素。

Spring Web 服务提供了智能机制来处理 SOAP 故障,其易于使用的 API。在处理请求时抛出的异常由MessageDispatcher捕捉,并委托给应用程序上下文(XML 或注释中声明的)中声明的任何端点异常解析器。这种异常解析器基于处理机制允许开发人员在抛出特定异常时定义自定义行为(例如返回自定义的 SOAP 故障)。

本章从易于处理异常的机制开始,然后转向稍微复杂的情景。

org.springframework.ws.server.EndpointExceptionResolver是 Spring-WS 中服务器端异常处理的主要规范/合同。org.springframework.ws.soap.server.endpoint.SimpleSoapExceptionResolverEndpointExceptionResolver的默认实现,可在 Spring-WS 框架中使用。如果开发人员没有明确处理,MessageDispatcher将使用SimpleSoapExceptionResolver处理服务器端异常。

本章中的示例演示了org.springframework.ws.server.EndpointExceptionResolver及其实现的不同用法,包括SimpleSoapExceptionResolver

为了演示目的,构建 Spring-WS 的最简单的方法是使用MessageDispatcherServlet简化 WebService 的创建。

通过将异常的消息作为 SOAP 故障字符串返回来处理服务器端异常

Spring-WS 框架会自动将服务器端抛出的应用程序级别异常的描述转换为 SOAP 故障消息,并将其包含在响应消息中发送回客户端。本示例演示了捕获异常并设置有意义的消息以作为响应中的 SOAP 故障字符串发送。

准备就绪

在此示例中,项目的名称是LiveRestaurant_R-4.1(用于服务器端 Web 服务),并具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-4.1-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

本示例使用了通过注释负载根设置端点中讨论的项目,该项目在第一章中讨论了构建 SOAP Web 服务。以下步骤描述了如何修改端点:

  1. 修改端点以在应用程序/系统错误发生时抛出异常。

  2. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。

  3. 从项目的根目录在命令行窗口中运行以下命令:

mvn clean package tomcat:run. 

  1. 要测试,打开一个新的命令窗口,转到文件夹LiveRestaurant_R-4.1-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端控制台的输出(请注意消息中生成的SOAP-Env:Fault元素):
DEBUG [http-8080-1] (MessageDispatcher.java:167) - Received request.....
<SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">Reference number is not provided!</faultstring>
</SOAP-ENV:Fault>
For request
...
<tns:placeOrderRequest >
......
</tns:placeOrderRequest> 

  • 以下是客户端控制台的输出:
Received response ....
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">Reference number is not provided!</faultstring>
</SOAP-ENV:Fault>
... for request....
<tns:placeOrderRequest >
.........
</tns:placeOrderRequest>
....
[WARNING]
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
.........
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:297)
at java.lang.Thread.run(Thread.java:619)
Caused by: org.springframework.ws.soap.client.SoapFaultClientException: Reference number is not provided!
........... 

它是如何工作的...

在端点(OrderServiceEndpoint)的处理程序方法(handlePlaceOrderRequest)中,由于传入消息不包含参考编号,因此会抛出一个简单的RuntimeException。这象征着任何意外的运行时异常。为了澄清,一个有意义的错误描述(未提供参考编号!)被传递给异常:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="";
if(refNumber.length()>0)
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
else
throw new RuntimeException("Reference number is not provided!");
}

您可以看到,对于这个项目没有配置显式的异常解析器。当没有配置异常解析器时,Spring-WS 框架的智能MessageDispatcher会分配一个默认的异常解析器来处理任何异常。它使用SimpleSoapExceptionResolver来处理这种情况。

SimpleSoapExceptionResolver通过执行以下操作解决异常:

  • 将异常记录到日志记录器(控制台,日志文件)

  • 生成带有异常消息作为故障字符串的 SOAP 故障消息,并作为响应消息的一部分返回

当我们在客户端检查响应消息时,可以看到在方法OrderServiceEndpoint.handlePlaceOrderRequest中设置的确切异常消息(未提供参考编号!)作为响应消息中的 SOAP 故障字符串返回。

有趣的是,开发人员不需要做任何处理或发送 SOAP 故障消息,除了抛出一个带有有意义的消息的异常。

另请参阅

在第一章中讨论的配方通过注释有效负载根设置端点构建 SOAP Web 服务

在第二章中讨论的配方在 HTTP 传输上创建 Web 服务客户端构建 SOAP Web 服务的客户端

将异常类名称映射到 SOAP 故障

Spring-WS 框架允许在 bean 配置文件spring-ws-servlet.xml中轻松定制 SOAP 故障消息。它使用一个特殊的异常解析器SoapFaultMappingExceptionResolver来完成这项工作。我们可以将异常类映射到相应的 SOAP 故障,以便生成并返回给客户端。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-4.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-4.2-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

这个配方使用了通过注释有效负载根设置端点中的项目,在第一章中讨论,构建 SOAP Web 服务

  1. 创建一个自定义异常类DataOutOfRangeException.java

  2. 修改OrderServiceEndpoint以抛出DataOutOfRangeException

  3. spring-ws-servlet.xml中注册SoapFaultMappingExceptionResolver

  4. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。

  5. 从项目的根目录,在命令行窗口中运行以下命令:

mvn clean package tomcat:run 

  1. 要测试,打开一个新的命令窗口,转到文件夹LiveRestaurant_R-4.2-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端控制台的输出(请注意,消息中生成了SOAP-Env:Fault元素):
DEBUG [http-8080-1] (MessageDispatcher.java:177) -
Sent response
...
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">such a data is out of range!</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
... for request
<tns:placeOrderRequest >
.......
</tns:placeOrderRequest> 

  • 以下是客户端控制台的输出:
Received response...
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">such a data is out of range!</faultstring>
</SOAP-ENV:Fault>
......
for request....
<tns:placeOrderRequest >
.......
</tns:placeOrderRequest>
.....
[WARNING]
java.lang.reflect.InvocationTargetException
.........
Caused by: org.springframework.ws.soap.client.SoapFaultClientException: such a data is out of range!
....... 

工作原理...

在前面的代码中,OrderServiceEndpoint.placeOrderRequest方法抛出一个自定义异常DataOutOfRangeException,它象征着典型的服务器端异常:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="123456789";
if(refNumber.length()<7)
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
else
throw new DataOutOfRangeException("RefNumber is out of range");
}

MessageDispatcher捕获了这个异常,并将其委托给配置的异常解析器。在这个项目中,使用了SoapFaultMappingExceptionResolver,这是一种特殊的解析器,允许在配置文件中将异常类与自定义消息进行映射。在这个例子中,使用了不同的消息来映射DataOutOfRangeException。它充当拦截器,将 SOAP 故障消息转换为以下映射中给定的内容:

<bean id="exceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER" />
<property name="exceptionMappings">
<value>
com.packtpub.liverestaurant.service.exception.DataOutOfRangeException=SERVER,
such a data is out of range!
</value>
</property>
</bean>

生成的 SOAP 故障消息在服务器端和客户端控制台屏幕上都有记录。它显示了映射的 SOAP 故障消息,而不是DataOutOfRangeException类最初抛出的内容。

还有更多...

这个强大的功能可以将异常与 SOAP 故障字符串进行映射,非常有用,可以将 SOAP 故障管理外部化,不需要修改代码并重新构建。此外,如果设计得当,spring-ws.xml文件中的配置(SOAP 故障映射)可以作为所有可能的 SOAP 故障消息的单一参考点,可以轻松维护。

提示

这是 B2B 应用的一个很好的解决方案。不适用于 B2C,因为需要支持多种语言。一般来说,最好的方法是通过在数据库中配置消息。这样,我们可以在运行时更改和修复它们。在 XML 中配置的缺点是需要重新启动。在实时情况下,一个应用在 30 台服务器上运行。部署和重新启动是痛苦的过程。

另请参阅

在第一章中讨论的通过注释有效负载根设置端点的配方,构建 SOAP Web 服务

在第二章中讨论的在 HTTP 传输上创建 Web 服务客户端配方,构建 SOAP Web 服务的客户端

在本章中讨论的通过将异常消息作为 SOAP 故障字符串返回来处理服务器端异常的配方。

使用@SOAPFault 对异常类进行注释

Spring-WS 框架允许将应用程序异常注释为 SOAP 故障消息,并在异常类本身中进行轻松定制。它使用一个特殊的异常解析器SoapFaultAnnotationExceptionResolver来完成这项工作。可以通过在类中进行注释来定制 SOAP 故障字符串和故障代码。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-4.3(服务器端 Web 服务),并且具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以下是LiveRestaurant_R-4.3-Client(客户端 Web 服务)的 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

如何做...

这个配方使用了第一章中讨论的通过注释有效负载根设置端点的项目作为服务器端,以及第三章中讨论的如何使用 Spring-Junit 支持集成测试的配方作为客户端。

  1. 创建一个自定义异常类(InvalidOrdeRequestException.java),并用@SoapFault进行注释。

  2. 创建一个自定义异常类(OrderProcessingFailedException.java),并用@SoapFault进行注释。

  3. 修改Endpoint(OrderServiceEndpoint)以抛出InvalidOrderRequestExceptionOrderProcessingFailedException

  4. 在服务器应用程序上下文文件(spring-ws-servlet.xml)中注册SoapFaultAnnotationExceptionResolver

  5. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。

  6. 从项目的根目录,在命令行窗口中运行以下命令:

mvn clean package tomcat:run. 

  1. 为了测试,打开一个新的命令窗口,进入文件夹LiveRestaurant_R-4.3-Client,并运行以下命令:
mvn clean package 

  • 以下是客户端控制台的输出(请注意消息中生成的 SOAP-Env:Fault 元素):
DEBUG [main] (WebServiceTemplate.java:632) -
Received response
.....
<SOAP-ENV:Fault><faultcode>SOAP-ENV:Client</faultcode>
<faultstring xml:lang="en">Invalid Order Request: Request message incomplete</faultstring>
</SOAP-ENV>
for request....
<tns:placeOrderRequest ....>
....
</tns:placeOrderRequest>
....................
Received response ...
<SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">Database server under maintenance, please try after some time.</faultstring>
</SOAP-ENV:Fault>...
for request ...
<tns:cancelOrderRequest ..>
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest>
...
Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.874 sec <<< FAILURE! 

它是如何工作的...

在端点的方法中,OrderServiceMethodEndoint.processOrderplaceOrderRequestcancelOrderRequest),抛出自定义异常(ProcessingFailedExceptionInvalidOrderRequestException),代表典型的服务器端/客户端异常:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="";
if(refNumber.length()>0)
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
else
throw new InvalidOrderRequestException("Reference number is not provided!");
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleCancelOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
boolean cancelled =true ;
if( isDataBaseServerRunning())
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>");
else
throw new ProcessingFailedException("Database server is down!");
}
private boolean isDataBaseServerRunning(){
return false;
}

这个异常被MessageDispatcher捕获并委托给配置的异常解析器。在这个项目中,使用了SoapFaultAnnotationExceptionResolver,这是一种特殊的解析器,允许异常类在类中用自定义的故障代码和故障字符串进行注释。SoapFaultAnnotationExceptionResolver被配置为在spring-ws-servlet.xml中使用,因此任何异常处理都会在运行时由MessageDispatcherServlet委托给它:

<bean id="exceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver">
<property name="defaultFault" value="SERVER" />
</bean>

ProcessingFailedException代表服务器端系统异常(faultCode = FaultCode.SERVER):

@SoapFault(faultCode = FaultCode.SERVER,
faultStringOrReason = "Database server under maintenance, please try after some time.")
public class ProcessingFailedException extends Exception {
public ProcessingFailedException(String message) {
super(message);
}
}

InvalidOrderRequestException代表客户端业务逻辑异常(faultCode = FaultCode.CLIENT):

@SoapFault(faultCode = FaultCode.CLIENT,
faultStringOrReason = "Invalid Order Request: Request message incomplete")
public class InvalidOrderRequestException extends Exception {
public InvalidOrderRequestException(String message) {
super(message);
}
}

您可以看到,带注释的faultStringOrReason被生成为 SOAP 故障,并传输回客户端。生成的 SOAP 故障消息在服务器端和客户端控制台屏幕上都被记录,显示了带注释的 SOAP 故障消息,而不是在Endpoint类中最初抛出的内容。

还有更多...

@SoapFault注释的faultCode属性具有以下可能的枚举值:

  • CLIENT

  • CUSTOM

  • RECEIVER

  • SENDER

从枚举列表中选择一个指示调度程序应生成哪种 SOAP 故障以及其具体内容。根据前面的选择,依赖属性变得强制性。

例如,如果为faultCode选择了FaultCode.CUSTOM,则必须使用customFaultCode字符串属性,而不是faultStringOrReason,如本配方的代码片段中所示。用于customFaultCode的格式是QName.toString()的格式,即"{" + Namespace URI + "}" + local part,其中命名空间是可选的。请注意,自定义故障代码仅在 SOAP 1.1 上受支持。

@SoaPFault注释还有一个属性,即 locale,它决定了 SOAP 故障消息的语言。默认语言环境是英语。

注意

在一般实践中,我们使用错误代码而不是错误消息。在客户端使用映射信息进行映射。这样可以避免对网络的任何负载,并且不会出现多语言支持的问题。

另请参阅

在第一章中讨论的通过注释有效负载根设置端点的配方,构建 SOAP Web 服务。

在第三章中讨论的如何使用 Spring-JUnit 支持集成测试的配方,测试和监控 Web 服务。

在本章中讨论的将异常类名映射到 SOAP 故障的配方。

在 Spring-WS 中编写自己的异常解析器

Spring-WS 框架提供了默认机制来处理异常,使用标准异常解析器,它允许开发人员通过构建自己的异常解析器来以自己的方式处理异常。SOAP 故障可以定制以在其自己的格式中添加自定义细节,并传输回客户端。

本示例说明了一个自定义异常解析器,它将异常堆栈跟踪添加到 SOAP 响应的 SOAP 故障详细元素中,以便客户端获取服务器端异常的完整堆栈跟踪,对于某些情况非常有用。这个自定义异常解析器已经具有注释的功能,就像前面的示例一样。

准备就绪

在本配方中,项目的名称是LiveRestaurant_R-4.4(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-4.4-Client(用于客户端)具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

如何做...

本配方使用了通过注释 payload-root 设置端点中讨论的项目,第一章,构建 SOAP Web 服务

  1. 创建一个自定义异常解析器DetailedSoapFaultExceptionResolver,扩展SoapFaultAnnotationExceptionResolver

  2. spring-ws-servlet.xml中注册DetailedSoapFaultExceptionResolver

  3. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。

  4. 从项目的根目录,在命令行窗口中运行以下命令:

mvn clean package tomcat:run. 

  1. 要进行测试,请打开一个新的命令窗口,转到文件夹LiveRestaurant_R-4.4-Client,并运行以下命令:
mvn clean package exec:java 

  • 以下是服务器端控制台的输出(请注意在消息中生成的SOAP-Env:Fault元素):
DEBUG [http-8080-1] (MessageDispatcher.java:167) - Received request.....
<tns:placeOrderRequest >
......
</tns:placeOrderRequest></SOAP-ENV:Body>...
DEBUG [http-8080-1] (MessageDispatcher.java:177) - Sent response
...
<SOAP-ENV:Fault><faultcode>SOAP-ENV:Client</faultcode>
<faultstring xml:lang="en">Invalid Order Request: Request message incomplete</faultstring
><detail>
<stack-trace >
at com.packtpub.liverestaurant.service.endpoint.OrderSeviceEndpoint.handlePlaceOrderRequest(OrderSeviceEndpoint.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.ws.server.endpoint.MethodEndpoint.invoke(MethodEndpoint.java:132)
at org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter.invokeInternal(DefaultMethodEndpointAdapter.java:229)
at org.springframework.ws.server.endpoint.adapter.AbstractMethodEndpointAdapter.invoke(AbstractMethodEndpointAdapter.java:53)
at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:230)
....... 
</stack-trace></detail></SOAP-ENV:Fault>

工作原理...

在上述代码中,我们的自定义异常解析器DetailedSoapFaultExceptionResolverSoapFaultAnnotationExceptionResolver的子类,覆盖了方法custmizeFault(),将异常堆栈跟踪添加到 SOAP 故障详细元素中。方法stackTraceToString()从给定的异常返回异常堆栈跟踪,并用于将堆栈跟踪设置为响应消息的 SOAP 故障的详细元素。

还有更多...

有许多不同的创建自定义异常解析器的方法。不仅可以继承SoapFaultAnnotationExceptionResolver来实现这一目的。任何org.springframework.ws.server.EndpointExceptionResolver的实现都可以适当配置为用作异常解析器。开发人员可以从 Spring-WS API 中提供的一组非常方便的EndpointExceptionResolver实现中进行选择,利用这些实现的功能。

自定义这些类的位置是方法customizeFault。可以通过覆盖方法customizeFault来自定义 SOAP 故障。查看包org.springframework.ws.soap.server.endpoint,以获取适合您要求的现成异常解析器。

如果需要开发一个与当前可用实现不符的专门自定义异常解析器,则AbstractSoapFaultDefinitionExceptionResolver将是一个理想的起点,因为它已经实现了一些任何异常解析器都需要的非常常见和基本功能。开发人员只需实现抽象方法resolveExceptionInternal(),以满足特定需求。

需要注意的是,应该指示MessageDispatcherServlet考虑使用的解析器,可以通过在spring-ws-servlet.xml中注册或在异常类中进行注释(除了在spring-ws-servlet.xml中注册)。

另请参阅

本配方通过注释 payload-root 设置端点中讨论的项目,第一章,构建 SOAP Web 服务

本章讨论的配方使用@SOAP fault 注释异常类

第五章:记录和跟踪 SOAP 消息

在本章中,我们将涵盖:

  • 手动记录消息有效载荷

  • 使用 log4j 记录请求和响应 SOAP 信封

  • 使用 Spring-WS 的拦截器记录请求和响应

  • 使用 Eclipse IDE 调试 Spring-WS

介绍

日志记录和跟踪是指捕获和记录有关软件程序执行的事件和数据结构,以提供审计跟踪。它有助于开发人员和支持团队收集有关软件程序执行的运行时信息。对于任何严肃的软件开发团队,实现系统日志记录非常重要。

对于 Web 服务开发,能够看到在客户端和服务器之间传输的 SOAP 消息非常有用。Spring Web 服务在 SOAP 消息到达时或发送前提供日志记录和跟踪。在 Spring-WS 中,日志记录由标准的 Commons Logging 接口管理。

通常,在 Spring 项目中,log4j 被用作具体的日志记录库(因为 Spring 日志功能是建立在 log4j 之上的)。本章介绍了记录 SOAP 消息的几种简单方法。

这里所示的配方可以应用于本书中任何配方的项目源。为了演示目的,使用了配方通过注释负载根设置端点的现有项目源,因为这可以应用于本书中使用的任何项目。

手动记录消息有效载荷

消息有效载荷是 SOAP 消息元素SOAP-ENV:Body的内容。这是整个 SOAP 信封的确切消息部分,用于请求和响应。

这个配方演示了从代码内部手动记录消息有效载荷。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-5.1(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-5.1-Client(用于客户端),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

这个配方使用了第一章中使用的通过注释负载根设置端点的配方中使用的项目,构建 SOAP Web 服务

  1. 修改log4j.properties将日志级别默认设置为INFO。在log4j.properties中删除任何包或 API 的调试设置。

  2. 修改OrderServiceEndpoint以创建两个xmlToString方法,并调用这两个方法将传入的消息转换为 String 并记录下来。

  3. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。从项目的根目录在命令行窗口中运行mvn clean package tomcat:run

  4. 要测试这个,打开一个新的命令行窗口,转到文件夹LiveRestaurant_R-5.1-Client并运行:mvn clean package exec:java

  5. 以下是服务器端控制台的输出:

INFO [http-8080-1] (OrderSeviceEndpoint.java:49) -
Message Payload method handlePlaceOrderRequest start ====
<?xml version="1.0" encoding="UTF-8"?>
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
..........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
==== Message Payload End
........................
INFO [http-8080-1] (OrderSeviceEndpoint.java:67) -
Message Payload method handleCancelOrderRequest start ====
<?xml version="1.0" encoding="UTF-8"?>
<tns:cancelOrderRequest >
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest>
==== Message Payload End 

它是如何工作的...

代码简单地手动记录消息有效载荷,而不需要在应用程序的任何地方进行配置更改。在log4j.properties中的更改确保日志消息被打印到控制台(因为附加程序是ConsoleAppender),并且不打印调试消息:

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

方法xmlToString(...)将 XML 源/元素对象转换为String,使用StringWriter

private String xmlToString(Node node) {
try {
Source source = new DOMSource(node);
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return stringWriter.getBuffer().toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
return null;
}
private static String xmlToString(Source source) {
try {
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return stringWriter.getBuffer().toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
return null;
}

handleCancelOrderRequest()handlePlaceOrderRequest()方法中,调用xmlToString()传递RequestPayload的源/元素以将消息有效载荷返回为 String 实例,然后将其记录到配置的日志附加程序(在这种情况下是控制台)中:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
String placeOrderRequestMessage = xmlToString(source);
logger.info("\n\n Message Payload method handlePlaceOrderRequest start ==== \n\n\n " + placeOrderRequestMessage + "\n\n\n ==== Message Payload End\n\n");
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
@PayloadRoot(namespace = SERVICE_NS, localPart = "cancelOrderRequest")
@ResponsePayload
public Source handleCancelOrderRequest(@RequestPayload Element cancelOrderRequest) throws Exception {
String refNumber=cancelOrderRequest.getElementsByTagNameNS(SERVICE_NS, "refNumber") .item(0).getTextContent();
String cancelOrderRequestMessage = xmlToString(cancelOrderRequest);
logger.info("\n\nMessage Payload method handleCancelOrderRequest start ==== \n\n\n " + cancelOrderRequestMessage + "\n\n\n ==== Message Payload End\n\n");
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns=\"http://www.packtpub.com/liverestaurant/OrderService/schema\"><tns:cancelled>"+orderService.cancelOrder(refNumber)+"</tns:cancelled></tns:cancelOrderResponse>");
}

提示

作为良好的实践,我们以调试模式记录消息。为了获得更好的性能,我们做如下操作:

If(logger.isDebugEnabled())
logger.debug(message);

在运行时,我们可以根据需求启用和禁用日志。

还有更多...

本配方中给出的示例使用SimpleMethodEndpointMapping,它以 XML Source(javax.xml.transform.Source)或 Element(org.w3c.dom.Element)对象的形式接收消息负载作为方法参数,并使用RequestPayload注解,而在其他情况下,传入消息将以不同的形式出现。例如,在编组端点中,输入已经是编组对象。在这些情况下,您需要采用适当的机制来转换传入的参数。之后的配方将为您提供有关日志记录和跟踪的其他方法的见解。

另请参阅

  • 通过注释 payload-root 设置端点在第一章中,构建 SOAP Web 服务

使用 log4j 记录请求和响应 SOAP 信封

Spring-WS 框架允许开发人员使用简单的日志记录器配置记录整个 SOAP 消息。本配方通过 log4j 日志记录框架配置框架的 SOAP 消息内部日志记录。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-5.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

它还有LiveRestaurant_R-5.2-Client(用于客户端)并具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

本配方使用了配方通过注释 payload-root 设置端点中使用的项目:

  1. 修改log4j.properties以设置消息跟踪。

  2. 在 Maven 嵌入的 Tomcat 服务器中构建和部署项目。在命令行窗口中从项目的根目录运行mvn clean package tomcat:run

  3. 要测试这一点,打开一个新的命令行窗口,转到文件夹LiveRestaurant_R-5.1-Client,并运行mvn clean package exec:java

以下是服务器端控制台的输出(请注意 Web 服务响应中生成的SOAP-Env:Envelope元素):

DEBUG [http-8080-1] (MessageDispatcher.java:167) - Received request
....
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Body>
....
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
....
DEBUG [http-8080-1] (MessageDispatcher.java:177) - Sent response
....
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Body>
.....
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
...
DEBUG [http-8080-1] (MessageDispatcher.java:167) - Received request ...
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Body>
....
<tns:cancelOrderRequest >
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
...
DEBUG [http-8080-1] (MessageDispatcher.java:177) - Sent response
...
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Body>
.....
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
... 

工作原理...

Spring-WS 框架的核心组件MessageDispatcherreceive()方法中接收到每个传入的 SOAP 消息后,会从MessageContext中提取消息内容,并在跟踪或调试启用时记录日志。

receive()方法中,它检查命名日志实例的日志设置,org.springframework.ws.server.MessageTracing.received检查记录 SOAP 请求,org.springframework.ws.server.MessageTracing.sent检查 SOAP 响应。如果这些设置的值为TRACEDEBUG,它将打印相应请求或响应的整个 SOAP 信封:

log4j.rootLogger=INFO, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
#RollingFileAppender
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=LiveRestaurant.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
log4j.logger.org.springframework.ws.server.MessageTracing.received=TRACE
log4j.logger.org.springframework.ws.server.MessageTracing.sent=TRACE

跟踪或调试的最简单设置是在log4j.properties中,如前所述。

提示

以前,出于安全目的,消息是加密的,因此启用日志记录并不总是有用的。最好在输入方法内完成解密后记录消息。

另请参阅

  • 通过注释 payload-root 设置端点在第一章中,构建 SOAP Web 服务

使用 Spring-WS 的拦截器记录请求和响应

Spring-WS 提供了记录传入/传出消息的功能。这些功能是通过使用PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor类来实现的,这些类使用Commons Logging Log进行记录。虽然PayloadLoggingInterceptor仅记录消息的有效负载,但SoapEnvelopeLoggingInterceptor记录整个 SOAP 信封,包括标头。要使用这两个拦截器激活日志记录功能,log4j属性文件中的日志属性应设置为拦截器包的调试。

在这个配方中,解释了使用PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor记录 Web 服务消息。

准备工作

在本教程中,项目的名称是LiveRestaurant_R-5.3(用于服务器端 Web 服务),并具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

以及LiveRestaurant_R-5.3-Client(用于客户端)和以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

本教程使用了教程通过注释 payload-root 设置端点中使用的项目:

  1. 打开log4j.properties并将日志设置为调试org.springframework.ws.server.endpoint.interceptor包。

  2. 在服务器端应用程序上下文中注册PayloadLoggingInterceptor

  3. 在 Maven 嵌入式 Tomcat 服务器中构建和部署项目。从项目的根目录在命令行窗口中运行mvn clean package tomcat:run

  4. 要测试这一点,打开一个新的命令行窗口,转到文件夹LiveRestaurant_R-5.3-Client,然后运行mvn clean package exec:java

以下是服务器端控制台的输出:

DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Request:
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Response:
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Request:
<tns:cancelOrderRequest >
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Response:
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled>
</tns:cancelOrderResponse> 

要使用SoapEnvelopeLoggingInterceptor记录 Web 服务消息,请按照以下步骤操作:

  1. 在服务器端应用程序上下文中注册SoapEnvelopeLoggingInterceptor

  2. 打开log4j.properties并将日志设置为调试org.springframework.ws.soap.server.endpoint.interceptor包。

  3. 在 Maven 嵌入式 Tomcat 服务器中构建和部署项目。从项目的根目录在命令行窗口中运行mvn clean package tomcat:run

  4. 要测试这一点,打开一个新的命令行窗口,转到文件夹LiveRestaurant_R-5.3-Client,然后运行mvn clean package exec:java

以下是服务器端控制台的输出:

DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Request:
<SOAP-ENV:Envelope xmlns:SOAP-ENV=....">
<SOAP-ENV:Header/><SOAP-ENV:Body>
<tns:placeOrderRequest >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV=..."><SOAP-ENV:Header/><SOAP-ENV:Body>
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Request:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Header/><SOAP-ENV:Body>
<tns:cancelOrderRequest >
<tns:refNumber>9999</tns:refNumber>
</tns:cancelOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
DEBUG [http-8080-1] (AbstractLoggingInterceptor.java:160) - Response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="..."><SOAP-ENV:Header/><SOAP-ENV:Body>
<tns:cancelOrderResponse >
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>

它是如何工作的...

当消息被接收时,MessageDispatcherServlet调用拦截器(如果有),以及在调用端点中的处理程序方法之前和向客户端发送响应之前。

仅在spring-ws-servlet.xml中注册PayloadLoggingInterceptor会记录消息的有效负载:

<sws:interceptors>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
</sws:interceptors>

同样,在spring-ws-servlet.xml中注册SoapEnvelopeLoggingInterceptor会记录整个消息的 SOAP Envelope:

<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor"/>
</sws:interceptors>

在这两种情况下,这些拦截器的包名称应设置为用于记录目的的 debug:

........
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
log4j.logger.org.springframework.ws.soap.server.endpoint.interceptor=debug
log4j.logger.org.springframework.ws.server.endpoint.interceptor=debug

还有更多...

PayloadLoggingInterceptorlogRequestlogResponse属性设置为 true/false,启用/禁用请求/响应消息的记录。

<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
<property name="logRequest" value="false" />
<property name="logResponse" value="true" />
</bean>

除了logRequestlogResponse之外,SoapEnvelopeLoggingInterceptor还有一个logFault属性,将这些设置为 true/false,启用/禁用请求/响应/故障消息的记录:

....
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor">
<property name="logRequest" value="false" />
<property name="logResponse" value="true" />
<property name="logFault" value="true" ></property>
</bean>

另请参阅

  • 通过注释 payload-root 设置端点在第一章中,构建 SOAP Web 服务

  • 使用 Log4j 记录请求和响应 SOAP Envelope

使用 Eclipse IDE 调试 Spring-WS

在开发阶段调试应用程序的能力是 IDE 最重要的功能之一,因为它可以帮助开发人员轻松找出错误,从而加快开发速度。对于更复杂的服务器端应用程序,调试能力对于发现缺陷更为重要。附加到 Eclipse 等 IDE 的远程调试器可以显著缩短问题分析时间,并使过程更加愉快。

Eclipse 可以配置为在嵌入式和远程服务器中进行 Web/app 服务器调试。本教程解释了如何在 Eclipse 内部以 Web 应用程序的形式调试 Spring-WS 项目,使用外部远程 Tomcat 实例。

准备就绪

要开始:

  1. 安装 Apache-Tomcat-6.0.14。

  2. 下载并安装 Eclipse IDE for Java EE Developers Helios。

在本教程中,项目的名称是LiveRestaurant_R-5.4(用于服务器端 WebService),并具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

它还有LiveRestaurant_R-5.4-Client(用于客户端)和以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

如何做...

  1. 在 Linux 的用户主目录(/home/weblogic)中修改配置文件,或者在 Windows 的系统变量中为 Tomcat 进行设置。

安装 Tomcat 后:在 Linux 上>编辑.profile>,添加以下内容以用于 Tomcat:

export TOMCAT_HOME=/opt2/apache-tomcat-6.0.14
export PATH=$TOMCAT_HOME:$PATH

  1. 在 Windows 上>编辑系统变量,如下图所示设置 Tomcat 的系统变量:如何做...

  2. $TOMCAT_HOME/conf/tomcat-users.xml文件中,将角色设置为managerusernamepassword如下:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="manager"/>
<user username="tomcat" password="tomcat" roles="manager"/>
</tomcat-users>

  1. MAVEN_HOME/conf/settings.xml文件中,以及如果有任何.m2/settings.xml.m2是 maven 存储库文件夹),添加一个名为tomcat的用户登录配置,密码为tomcat,如下所示:
<server>
<id>myserver</id>
<username>tomcat</username>
<password>tomcat</password>
</server>

  1. 在文件末尾修改debug.sh/debug.bat TOMCAT_HOME/bin/

在 Windows 上,修改 debug.bat:

set JPDA_TRANSPORT=dt_socket
set JPDA_ADDRESS=8000
call "%EXECUTABLE%" jpda start %CMD_LINE_ARGS%

  1. 在 Linux 上,修改 debug.sh:
export JPDA_ADDRESS=8000
export JPDA_TRANSPORT=dt_socket
exec "$PRGDIR"/"$EXECUTABLE" jpda start "$@"

  1. 在 Linux/Windows 上使用TOMCAT_HOME/bin/中的debug.sh/debug.bat运行 Tomcat。

  2. 修改LiveRestaurant_R-5.4pom.xml文件:

<!-- <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
</plugin> -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
<configuration>
<server>myserver</server>
<path>/LiveRestaurant</path>
</configuration>
</plugin>

  1. 将项目LiveRestaurant_R-5.4导入 Eclipse,并在com.packtpub.liverestaurant.service.endpoint.OrderEndpoint.java类的handleCancelOrderRequest方法中设置断点。

  2. LiveRestaurant_R-5.4运行mvn clean package,然后将 WAR 文件复制到tomcat/webapp(应用将被部署到 Tomcat 中)。

  3. 在 Eclipse 中设置 Maven 安装:Windows | Preferences | Maven | Installations,点击Add按钮,并设置外部 Maven:如何做...

  4. 打开 Eclipse。右键单击LiveRestaurant_R-5.4 | Debug as | Debug Configurations | Remote Java Application,点击New,然后点击Debug按钮:如何做...

  5. 从项目LiveRestaurant_R-5.4-Client中运行mvn clean package

这是测试的输出:

如何做...

  1. 现在可以尝试不同的调试应用程序选项,例如:
  • Step Over (F5)

  • Step Into (F5)

  • Step Out (F7)

  • 观察

  • 检查

工作原理...

此处使用Java Debugger (JDB)工具来帮助在本地和服务器上找到并修复 Java 语言程序中的错误。JDB 是Java Platform Debugging Architecture (JPDA)的一部分,为您构建 Java 平台的最终用户调试器应用程序提供了所需的基础设施。

要在 Java EE 应用程序服务器或 Servlet 容器(如 Tomcat)中使用 JDB,必须首先启用调试并通过 JPDA 端口(默认端口为 1044)将其附加到调试器。在第 4 步,JPDA 端口设置为 8000。此处使用debug.bat/debug.sh启动服务器,而不是run.bat/run.sh,这意味着服务器以调试模式启动。

JDB 参数指定调试器的操作方式。例如,JPDA_TRANSPORT=dt_socket指示 JVM 调试器连接将通过套接字进行,而JPDA_ADDRESS=8000参数通知端口号将为 8000。

然后 Eclipse IDE 将连接到接受调试连接的 JVM。该项目被设置为 Eclipse 内的远程 Java 应用程序,监听相同的端口,即 8000,以进行任何调试活动。接下来,将在服务类中设置断点,该断点将在运行时由 JDB 管理和重定向到 IDE。

LiveRestaurant_R-5.4-Client项目作为服务的客户端程序执行时,服务类OrderServiceEndpoint被调用,并在处于调试模式的 JVM 中命中断点。它通知前端 JDI 的实现位置以及在这种情况下的 IDE。

还有更多...

类似于 Tomcat 服务器,您可以将任何应用程序服务器附加到 Eclipse、Net Beans 或 JDeveloper 等 IDE。概念是相同的。但是,每个应用程序服务器和 IDE 的步骤可能会有所不同。

提示

启用调试模式时,尝试将消息在给定层中所花费的总时间作为 XML 消息中的一个属性发送。这有助于在性能测试中进行故障排除。

第六章:编组和对象/XML 映射(OXM)

在本章中,我们将涵盖以下主题:

  • 使用 JAXB2 进行编组

  • 使用 XMLBeans 进行编组

  • 使用 JiBX 进行编组

  • 使用 XStream 进行编组

  • 使用 MooseXML 进行编组

  • 使用 XPath 创建自定义编组器进行条件 XML 解析

介绍

在对象/XML 映射(OXM)术语中,编组(序列化)将数据的对象表示转换为 XML 格式,而解组将 XML 转换为相应的对象。

Spring 的 OXM 通过使用 Spring 框架的丰富特性简化了 OXM 操作。例如,可以使用依赖注入功能将不同的 OXM 技术实例化为对象以使用它们,Spring 可以使用注解将类或类的字段映射到 XML。

Spring-WS 受益于 Spring 的 OXM,可以将有效负载消息转换为对象,反之亦然。例如,可以在应用程序上下文中使用以下配置将 JAXB 设置为 OXM 框架:

<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="marshaller" />
</bean>
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.packtpub.liverestaurant.domain" />
</bean>

此外,可以通过更改配置文件中的marshaller bean 来更改编组框架,同时保持 Web 服务的实现不变。

有许多可用的编组框架的实现。JAXB(Java Architecture for XML Binding)、JiBX、XMLBeans、Castor 等都是例子。对于一些 OXM 框架,提供了工具来将模式转换为 POJO 类,并在这些类中生成映射数据,或者在单独的外部配置文件中生成映射数据。

本章提供了示例来说明不同框架用于对象/XML 映射的用法。

为了简化,本章中大多数示例使用了“使用 Spring-JUnit 支持进行集成测试”一章中讨论的项目,该项目在“测试和监视 Web 服务”一章中讨论,用于设置服务器并通过客户端发送和接收消息。然而,在“使用 XStream 进行编组”一章中的示例中,使用了“为 WS-Addressing 端点创建 Web 服务客户端”一章中讨论的项目,该项目在“构建 SOAP Web 服务的客户端”一章中讨论,用于服务器和客户端。

使用 JAXB2 进行编组

Java Architecture for XML Binding(http://jaxb.java.net/tutorial/)是一个 API,允许开发人员将 Java 对象绑定到 XML 表示。JAXB 实现是 Metro 项目(http://metro.java.net/)的一部分,它是一个高性能、可扩展、易于使用的 Web 服务堆栈。JAXB 的主要功能是将 Java 对象编组为 XML 等效项,并根据需要将其解组为 Java 对象(也可以称为对象/XML 绑定或编组)。当规范复杂且变化时,JAXB 特别有用。

JAXB 提供了许多扩展和工具,使得对象/XML 绑定变得简单。其注解支持允许开发人员在现有类中标记 O/X 绑定,以便在运行时生成 XML。其 Maven 工具插件(maven-jaxb2-plugin)可以从给定的 XML Schema 文件生成 Java 类。

这个示例说明了如何设置编组端点并使用 JAXB2 作为编组库构建客户端程序。

准备工作

这个示例包含一个服务器(LiveRestaurant_R-6.1)和一个客户端(LiveRestaurant_R-6.1-Client)项目。

LiveRestaurant_R-6.1具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-6.1-Client具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

这个示例使用maven-jaxb2-plugin从模式生成类。

如何做...

  1. 在服务器/客户端配置文件中注册 JAXB 编组器。

  2. 在服务器/客户端 POM 文件中配置maven-jaxb2-plugin

  3. 设置服务器并运行客户端(它还从模式生成类):

  • 客户端项目根目录:mvn clean package

  • 服务器项目根目录:mvn clean package tomcat:run

以下是客户端输出:

- Received response ....
<ns2:cancelOrderResponse...>
<ns2:cancelled>true</ns2:cancelled>
</ns2:cancelOrderResponse>
...
for request ...
<ns2:cancelOrderRequest ...>
<ns2:refNumber>Ref-2010..</ns2:refNumber>
</ns2:cancelOrderRequest>
.....
....
- Received response ....
<ns2:placeOrderResponse ...>
<ns2:refNumber>Ref-2011-1..</ns2:refNumber>
</ns2:placeOrderResponse>
...
for request ...
<ns2:placeOrderRequest ...>
<ns2:order>.....
</ns2:order></ns2:placeOrderRequest>
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.293 sec 

它是如何工作的...

在这个编组业务中的主要参与者是GenericMarshallingMethodEndpointAdapter,它利用编组器执行对象/XML 编组过程。这里使用的编组器是org.springframework.oxm.jaxb.Jaxb2Marshaller,它执行 O/X 编组,利用 JAXB2 框架。如果您检查 Maven 插件工具生成的 Java 类,您可以看到 JAXB 注释,如@XmlType,@XmlRootElement,@XmlElement等。这些注释是 JAXB 引擎的指令,用于确定在运行时生成的 XML 的结构。

POM 文件中的以下部分从文件夹src\main\webapp\WEB-INF(由schemaDirectory设置)中的模式(OrderService.xsd)生成 JAXB 类。

GeneratePackage设置包括生成类的包,generateDirectory设置了托管生成包的文件夹:

<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<configuration>
<schemaDirectory>src\main\webapp\WEB-INF</schemaDirectory> 
<schemaIncludes>
<include>orderService.xsd</include> 
</schemaIncludes>
<generatePackage>com.packtpub.liverestaurant.domain</generatePackage> 
</configuration>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

OrderServiceEndPoint被注释为@Endpoint,将 Web 服务请求与placeOrderRequest的有效载荷根映射到getOrder方法,识别注释@PayloadRoot。编组器将传入的 XML 编组为PlaceOrderRequest的实例,方法getOrder返回PlaceOrderResponse。方法cancelOrder也是同样的情况:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public PlaceOrderResponse getOrder(
PlaceOrderRequest placeOrderRequest) {
PlaceOrderResponse response = JAXB_OBJECT_FACTORY
.createPlaceOrderResponse();
response.setRefNumber(orderService.placeOrder(placeOrderRequest
.getOrder()));
return response;
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public CancelOrderResponse cancelOrder(
CancelOrderRequest cancelOrderRequest) {
CancelOrderResponse response = JAXB_OBJECT_FACTORY
.createCancelOrderResponse();
response.setCancelled(orderService.cancelOrder(cancelOrderRequest
.getRefNumber()));
return response;
}

在服务器的spring-ws-servlet.xml中的以下部分将编组器设置为端点(OrderServiceEndpoint)为Jaxb2Marshaller。在marshaller bean 中的contextPath设置注册了包com.packtpub.liverestaurant.domain中的所有 bean,以便由Jaxb2Marshaller进行编组/解组:

<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="marshaller" />
</bean>
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.packtpub.liverestaurant.domain" />
</bean>

在客户端中也会发生同样的事情。唯一的区别是编组器设置为WebServiceTemplate

<bean id="orderServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory" />
<property name="marshaller" ref="orderServiceMarshaller"></property>
<property name="unmarshaller" ref="orderServiceMarshaller"></property>
.........</bean>
<bean id="orderServiceMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.packtpub.liverestaurant.domain" />
</bean>

MessageDispatcherServlet借助Jaxb2Marshaller检测 O/X 映射注释以及反射,并将最终编组过程委托给 JAXB 框架。

使用 XMLBeans 进行编组

XMLBeans(xmlbeans.apache.org/)是一种通过将其绑定到 Java 类型来访问 XML 的技术。该库来自 Apache 基金会,并且是 Apache XML 项目的一部分。XMLBeans 以其友好的 Java 特性而闻名,允许开发人员充分利用 XML 和 XML Schema 的丰富性和功能,并将这些功能尽可能自然地映射到等效的 Java 语言和类型构造中。

使 XMLBeans 与其他 XML-Java 绑定选项不同的两个主要特点是:

  • 完整的 XML Schema 支持:XMLBeans 完全支持(内置)XML Schema,并且相应的 Java 类为 XML Schema 的所有主要功能提供了构造。

  • 完整的 XML 信息集忠实度:在解组 XML 数据时,开发人员可以获得完整的 XML 信息集。XMLBeans 提供了许多扩展和工具,使对象/XML 绑定变得简单。

准备工作

此配方包含服务器(LiveRestaurant_R-6.2)和客户端(LiveRestaurant_R-6.2-Client)项目。

LiveRestaurant_R-6.2具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • xmlbeans-2.4.0.jar

LiveRestaurant_R-6.2-Client具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • xmlbeans-2.4.0.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

此配方使用xmlbeans-maven-plugin从模式生成类并绑定文件。

如何做...

  1. 在服务器/客户端配置文件中注册 XMLBean 编组器。

  2. 在服务器/客户端 POM 文件中配置xmlbeans-maven-plugin

  3. 设置服务器并运行客户端(它还从模式生成类):

  4. 运行以下命令:

  • 服务器项目根目录:mvn clean package tomcat:run

  • 客户端项目根:mvn clean package

以下是客户端输出:

[INFO]
[INFO] --......
[INFO]
[INFO] --- xmlbeans-maven-plugin:2.3.2:xmlbeans ....
[INFO]
[INFO] .....
Received response ...
<sch:cancelOrderResponse ...>
<sch:cancelled>true</sch:cancelled>
</sch:cancelOr
derResponse>...
for request.....
......
- Received response ...
<sch:placeOrderResponse ...>
<sch:refNumber>Ref-2011-10-..</sch:refNumber>
</sch:placeOrderResponse>
...
for request ....
...
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.845 sec 

工作原理...

这个示例的工作方式与第一个示例使用 JAXB2 进行编组完全相同,只是它使用了不同的编组器XMLBeansMarshaller。这里使用的 scomp(模式编译器)工具从 XML 模式(OrderService.xsd)生成 Java XMLBeans 类。除了域类,它还生成了表示文档根元素的类,例如CancelOrderRequestDocument。所有生成的类都包含Factory方法来实例化它们。

很容易注意到,代码中的两个主要区别在于OrderServiceEndPointspring-ws-servlet.xml。与上一个示例不同,getOrder方法返回OrderResponseDocument的实例,并接受OrderRequestDocument作为输入参数。cancelOrderDoc方法也是如此。

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public PlaceOrderResponseDocument getOrder(PlaceOrderRequestDocument orderRequestDoc) {
PlaceOrderResponseDocument orderResponseDocument =PlaceOrderResponseDocument.Factory.newInstance();
orderResponseDocument.addNewPlaceOrderResponse();
orderResponseDocument.getPlaceOrderResponse().setRefNumber(orderService.placeOrder(orderRequestDoc));
return orderResponseDocument;
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public CancelOrderResponseDocument placeCancelOrderDoc(
CancelOrderRequestDocument cancelOrderRequestDoc) {
CancelOrderResponseDocument cancelOrderResponseDocument= CancelOrderResponseDocument.Factory.newInstance();
cancelOrderResponseDocument.addNewCancelOrderResponse();
cancelOrderResponseDocument.getCancelOrderResponse().setCancelled(orderService.cancelOrder(cancelOrderRequestDoc.getCancelOrderRequest().getRefNumber()));
return cancelOrderResponseDocument;
}

spring-ws-servlet.xml中使用的编组器是XMLBeansMarshaller,它使用 XMLBeans 库在 XML 和 Java 之间进行编组和解组。

<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="marshaller" />
</bean>
<bean id="marshaller" class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller"/>

@Endpoint类和XMLBeansMarshaller之间的契约是,@PayloadRoot方法应该接受并返回org.apache.xmlbeans.XmlObject的实例。然后它动态地找到相应的类,并使用它们的Factory方法,在运行时创建实例并绑定到 XML。

与上一个示例一样,POM 文件中的插件从文件夹src\main\webapp\WEB-INF(由schemaDirectory设置)中的模式(OrderService.xsd)生成XMLBean类:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xmlbeans-maven-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<goals>
<goal>xmlbeans</goal>
</goals>
</execution>
</executions>
<inherited>true</inherited>
<configuration>
<schemaDirectory>src/main/webapp/WEB-INF/</schemaDirectory> 
</configuration>
</plugin>

MessageDispatcherServlet借助XMLBeansMarshaller检测 O/X 映射注解和编组配置,并将最终编组过程委托给 XMLBeans 框架。

还有更多...

XMLBeans 配备了一套内置的强大工具,可以为 XML 和 Java 之间的编组添加更多功能。本示例仅使用了其中一个工具,即scomp,即模式编译器,它可以从 XML 模式(.xsd)文件生成 Java 类/压缩的 JAR 文件。其他一些有用的工具包括:

  • inst2xsd(Instance to Schema Tool):从 XML 实例文件生成 XML 模式。

  • scopy(Schema Copier):将指定 URL 的 XML 模式复制到指定文件

  • validate(Instance Validator):根据模式验证实例

  • xpretty(XML Pretty Printer):将指定的 XML 格式化打印到控制台

  • xsd2inst(Schema to Instance Tool):使用指定的模式从指定的全局元素打印 XML 实例

  • xsdtree(Schema Type Hierarchy Printer):打印模式中定义的类型的继承层次结构

  • xmlbean Ant task: 将一组 XSD 和/或 WSDL 文件编译成 XMLBeans 类型

xmlbean Ant task是自动化生成 Java 类的一种不错的方式,可以与构建脚本集成。

使用 JiBX 进行编组

JiBX(jibx.sourceforge.net/)是另一个用于将 XML 数据绑定到 Java 对象的工具和库。JiBX 以速度性能和灵活性而闻名。然而,它也以绑定的复杂性而闻名,特别是对于复杂的数据模型。

从 1.2 版本开始,JiBX 已解决了这些瓶颈,现在它具有易于使用的编组工具和框架。使用 JiBX 工具,用户可以从现有 Java 代码生成模式,或者从现有模式生成 Java 代码和绑定文件。JiBX 库在运行时将 Java 类绑定到 XML 数据,反之亦然。

在本示例中,使用 JiBX 工具(jibx-maven-plugin)生成 POJO 类,并从现有模式绑定定义文件,然后将基于 JiBX 库构建 Web 服务客户端和服务器。

准备工作

这个示例包含一个服务器(LiveRestaurant_R-6.3)和一个客户端(LiveRestaurant_R-6.3-Client)项目。

LiveRestaurant_R-6.3具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • spring-expression-3.0.5.RELEASE.jar

  • jibx-run-1.2.3.jar

  • jibx-extras-1.2.3.jar

  • jibx-ws-0.9.1.jar

LiveRestaurant_R-6.3-Client具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • spring-expression-3.0.5.RELEASE.jar

  • jibx-run-1.2.3.jar

  • jibx-extras-1.2.3.jar

  • jibx-ws-0.9.1.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

如何做...

  1. 在服务器/客户端配置文件中注册 JiBX 编组器。

  2. 在服务器/客户端 POM 文件中配置xmlbeans-maven-plugin

  3. 设置服务器并运行客户端(它还从模式生成类):

  • 服务器项目根目录:mvn clean package(它还从模式生成类)。将 WAR 文件复制到 Tomcat 的webapp文件夹中并运行 Tomcat(apache-tomcat-6.0.18)

  • 客户端项目根目录:mvn clean package(它还从模式生成类)

以下是客户端的输出:

.......
.........
[INFO] --- jibx-maven-plugin:1.2.3:bind (compile-binding) @ LiveRestaurant_Client ---
[INFO] Running JiBX binding compiler (single-module mode) on 1 binding file(s)
[INFO]
[INFO] ....
Received response ...
<tns:cancelOrderResponse ...>
<tns:cancelled>true</tns:cancelled></tns:cancelOrderResponse>
...
for request ...
<tns:cancelOrderRequest ...><tns:refNumber>12345</tns:refNumber>
</tns:cancelOrderRequest>
.....
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

如前面的配方中所述,服务器/客户端的应用程序上下文使用自定义编组器(org.springframework.oxm.jibx.JibxMarshaller)执行对象/XML 编组过程。这个 Spring 编组器使用 JiBX 库进行绑定和编组过程。以下 POM 插件设置(目标:schema-codegen)从模式(OrderService.xsd)生成 POJO 类到一个包(com.packtpub.liverestaurant.domain),并且还生成一个绑定文件(目标:bind):

<plugin>
<groupId>org.jibx</groupId>
<artifactId>jibx-maven-plugin</artifactId>
<version>1.2.3</version>
<executions>
<execution>
<id>generate-java-code-from-schema</id>
<goals>
<goal>schema-codegen</goal> 
</goals>
</execution>
<execution>
<id>compile-binding</id>
<goals>
<goal>bind</goal> 
</goals>
</execution>
</executions>
<configuration>
<schemaLocation>src/main/webapp/WEB-INF</schemaLocation> 
<includeSchemas>
<includeSchema>orderService.xsd</includeSchema>
</includeSchemas>
<options>
<package>com.packtpub.liverestaurant.domain</package> 
</options>
</configuration>
</plugin>

如前面的配方中所述,服务器和客户端 Spring 上下文文件中的此设置使客户端和服务器使用自定义编组器(JibxMarshaller)对 POJO 类进行编组/解组为 XML 数据:

<bean id="marshaller"
class="org.springframework.oxm.jibx.JibxMarshaller">
<property name="targetClass" value="com.packtpub.liverestaurant.domain.CancelOrderRequest" />
</bean>

JibxMarshaller使用binding.xml文件来进行编组任务。正如在映射文件中所示,JiBX 支持简单数据绑定(<value style="element" name="fName"...)以及被称为结构的复杂数据绑定(<structure map-as="tns:Address"...)。这个特性使 JiBX 成为其他框架中最灵活的绑定框架。

......
<mapping abstract="true" type-name="tns:Customer" class="com.packtpub.liverestaurant.domain.Customer">
<structure map-as="tns:Address" get-method="getAddressPrimary" set-method="setAddressPrimary" name="addressPrimary"/>
<structure map-as="tns:Address" get-method="getAddressSecondary" set-method="setAddressSecondary" name="addressSecondary"/>
<structure map-as="tns:Name" get-method="getName" set-method="setName" name="name"/>
</mapping>
<mapping abstract="true" type-name="tns:Name" class="com.packtpub.liverestaurant.domain.Name">
<value style="element" name="fName" get-method="getFName" set-method="setFName"/>
<value style="element" name="mName" get-method="getMName" set-method="setMName"/>
<value style="element" name="lName" get-method="getLName" set-method="setLName"/>
</mapping>
.....

OrderServiceEndPoint被注释为@Endpoint,几乎与之前的配方(使用 JAXB2 进行编组)相同;只是实现略有不同。

@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public
CancelOrderResponse handleCancelOrderRequest(CancelOrderRequest cancelOrderRequest) throws Exception {
CancelOrderResponse cancelOrderResponse=new CancelOrderResponse();
cancelOrderResponse.setCancelled(orderService.cancelOrder(cancelOrderRequest.getRefNumber()));
return cancelOrderResponse;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public
PlaceOrderResponse handleCancelOrderRequest(PlaceOrderRequest placeOrderRequest) throws Exception {
PlaceOrderResponse orderResponse=new PlaceOrderResponse();
orderResponse.setRefNumber(orderService.placeOrder(placeOrderRequest.getOrder()));
return orderResponse;
}
......

还有更多...

JiBX 通过让用户创建自己的自定义编组器来提供更大的灵活性。这意味着可以使用自定义绑定文件和自定义编组器类来对任何类型的数据结构进行编组到 XML 文档中。

使用 XStream 进行编组

XStream (xstream.codehaus.org/)是一个简单的库,用于将对象编组/解组为 XML 数据。以下主要特点使这个库与其他库不同:

  • 不需要映射文件

  • 不需要更改 POJO(不需要 setter/getter 和默认构造函数)

  • 备用输出格式(JSON 支持和变形)

  • XStream 没有工具可以从现有的 Java 代码生成模式,也不能从现有模式生成 Java 代码

  • XStream 不支持命名空间

在这个配方中,创建了一个使用 XStream 库作为编组器的 Web 服务客户端和服务器。由于 XStream 在 XML 数据(有效负载)中不使用任何命名空间,因此设置了一种 Web 服务的网址样式。

准备工作

此配方包含一个服务器(LiveRestaurant_R-6.4)和一个客户端(LiveRestaurant_R-6.4-Client)项目。

LiveRestaurant_R-6.4具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • spring-expression-3.0.5.RELEASE.jar

  • jxstream-1.3.1.jar

LiveRestaurant_R-6.4-Client具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • jxstream-1.3.1.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

如何做...

  1. 在服务器/客户端配置文件中注册 XStream 编组器。

  2. Xstream注释领域类。

  3. 设置服务器并运行客户端:

  • 服务器项目根目录:mvn clean package tomcat:run

  • 客户端项目根目录:mvn clean package

以下是客户端输出:

Received response
..
...
<wsa:Action>http://www.packtpub.com/OrderService/CancelOrdReqResponse</wsa:Action>
<wsa:MessageID>urn:uuid:a4b681ff-00f5-429e-9ab9-f9054e796a89</wsa:MessageID>
....
<cancelOrderResponse><cancelled>true</cancelled>
</cancelOrderResponse></SOAP-ENV:Body>
....
...
<wsa:Action>http://www.packtpub.com/OrderService/CancelOrdReq</wsa:Action>
...<cancelOrderRequest><refNumber>12345</refNumber></cancelOrderRequest> 

它是如何工作的...

如前一配方所述,服务器/客户端的应用程序上下文使用自定义的 marshaller(org.springframework.oxm.xstream.XStreamMarshaller)来执行对象/XML 编组过程。这个 spring marshaller 使用 XStream 库进行编组过程。在端点方法中的输入和输出参数的 bean(OrderServiceEndPoint.java)必须在XstreamMarshaller中注册。autodetectAnnotations设置为检测 POJO 类中的注释:

<bean id="marshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="autodetectAnnotations" value="true"/>
<property name="aliases">
<map>
<entry key="placeOrderResponse" value="com.packtpub. liverestaurant.domain.PlaceOrderResponse" />
<entry key="placeOrderRequest" value="com.packtpub. liverestaurant.domain.PlaceOrderRequest" />
<entry key="cancelOrderRequest" value="com.packtpub. liverestaurant.domain.CancelOrderRequest" />
<entry key="cancelOrderResponse" value="com.packtpub. liverestaurant.domain.CancelOrderResponse" />
</map>
</property></bean>

XStreamMarshaller使用 POJO 类中的注释(而不是绑定文件)来执行编组任务。@XstreamAlias告诉 marshaller 这个类将被序列化/反序列化为'name'。还有其他注释是可选的,但它告诉 marshaller 如何序列化/反序列化类的字段(@XStreamAsAttribute@XStreamImplicit等)。

import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("name")
public class Name
{
private String FName;
private String MName;
private String LName;

被注释为@EndpointOrderServiceEndPoint与 JiBX 配方相同,即端点方法的输入和返回参数是 POJO(PlaceOrderResponsePlaceOrderRequest等),它们被映射到模式。唯一的区别是端点使用 Web 寻址进行方法映射:

@Action("http://www.packtpub.com/OrderService/CancelOrdReq")
public
CancelOrderResponse handleCancelOrderRequest(CancelOrderRequest cancelOrderRequest) throws Exception {
CancelOrderResponse cancelOrderResponse=new CancelOrderResponse();
cancelOrderResponse.setCancelled(orderService.cancelOrder(cancelOrderRequest.getRefNumber()));
return cancelOrderResponse;
}
@Action("http://www.packtpub.com/OrderService/OrdReq")
public
PlaceOrderResponse handlePancelOrderRequest(PlaceOrderRequest placeOrderRequest) throws Exception {
PlaceOrderResponse orderResponse=new PlaceOrderResponse();
orderResponse.setRefNumber(orderService.placeOrder(placeOrderRequest.getOrder()));
return orderResponse;
}

使用 MooseXML 进行编组

Moosequigley.com/moose/)是一个轻量级的框架,用于将对象编组/解组为 XML 数据。Moose 的模式生成器使得这个框架与其他框架不同。Moose 能够直接从带注释的 POJO 类生成模式。这是开发面向契约的 Web 服务开发所需的。

在这个配方中,Moose 用于在 Web 服务客户端和服务器通信中将对象编组/解组为 XML 数据。

准备工作

这个配方包含一个服务器(LiveRestaurant_R-6.5)和一个客户端(LiveRestaurant_R-6.5-Client)项目。

LiveRestaurant_R-6.5具有以下 Maven 依赖项:

  • log4j-1.2.9.jar

  • moose-0.4.6.jar

LiveRestaurant_R-6.5-Client具有以下 Maven 依赖项:

  • log4j-1.2.9.jar

  • moose-0.4.6.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

如何做...

  1. 在服务器/客户端配置文件中注册 Moose marshaller。

  2. 使用@XML注释对领域类进行注释。

  3. 设置服务器并运行客户端:

  • 服务器项目根目录:mvn clean package tomcat:run

  • 客户端项目根目录:mvn clean package

以下是客户端输出:

Received response ...
<ns:cancelOrderResponse...>
<ns:cancelled>true</ns:cancelled>
</ns:cancelOrderResponse>
...
for request ...
<ns:cancelOrderRequest...>
<ns:refNumber>12345</ns:refNumber>
</ns:cancelOrderRequest>
....... 

它是如何工作的...

如前一配方所述,服务器/客户端的应用程序上下文使用自定义的 marshaller(com.quigley.moose.spring.MooseMarshaller)来执行对象/XML 编组过程。一个映射提供程序被注入到这个自定义的 marshaller 中。当对象被编组成 XML 时,映射提供程序用于设置命名空间和xmlPrefix,当 XML 数据被转换为对象时也是如此。映射提供程序从com.quigley.moose.mapping.provider.annotation.StaticClassesProvider中获取已注册的 POJO 类列表:

<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="mooseMarshaller"/>
</bean>
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>
<bean id="mooseMarshaller" class="com.quigley.moose.spring.MooseMarshaller">
<property name="mappingProvider"><ref bean="mooseMappingProvider"/></property>
</bean>
<bean id="mooseMappingProvider"
class="com.quigley.moose.mapping.provider.annotation.AnnotationMappingProvider">
<property name="xmlNamespace">
<value>http://www.liverestaurant.com/OrderService/schema</value></property>
<property name="xmlPrefix"><value>ns</value></property>
<property name="annotatedClassesProvider"><ref bean="mooseClassesProvider"/></property>
</bean>
<bean id="mooseClassesProvider"
class="com.quigley.moose.mapping.provider.annotation. StaticClassesProvider">
<property name="classes">
<list>
<value>com.packtpub.liverestaurant.domain. CancelOrderRequest</value>
<value>com.packtpub.liverestaurant.domain. CancelOrderResponse</value>
<value>com.packtpub.liverestaurant.domain.Order </value>
<value>com.packtpub.liverestaurant.domain.Address </value>
<value>com.packtpub.liverestaurant.domain.Customer </value>
<value>com.packtpub.liverestaurant.domain.FoodItem </value>
<value>com.packtpub.liverestaurant.domain.Name </value>
<value>com.packtpub.liverestaurant.domain. PlaceOrderResponse</value>
<value>com.packtpub.liverestaurant.domain. PlaceOrderRequest</value>
</list>
</property>
</bean>

MooseMarshaller,就像XStreamMarshaller一样,使用 POJO 类中的注释来执行编组任务。@XML告诉 marshaller 这个类将被序列化/反序列化为'name'。@XMLField是应该放在每个类字段上的标记。

@XML(name="cancelOrderRequest")
public class CancelOrderRequest
{
@XMLField(name="refNumber")
private String refNumber;
/** * Get the 'refNumber' element value.
*
* @return value
*/
public String getRefNumber() {
return refNumber;
}
/**
* Set the 'refNumber' element value.
*
* @param refNumber
*/
public void setRefNumber(String refNumber) {
this.refNumber = refNumber;
}
}

被注释为@EndpointOrderServiceEndPoint与 JiBX 配方相同,传递和返回参数是映射到模式的 POJO(PlaceOrderResponsePlaceOrderRequest等)。

@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public
CancelOrderResponse handleCancelOrderRequest(CancelOrderRequest cancelOrderRequest) throws Exception {
CancelOrderResponse cancelOrderResponse=new CancelOrderResponse();
cancelOrderResponse.setCancelled(orderService.cancelOrder(cancelOrderRequest.getRefNumber()));
return cancelOrderResponse;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public
PlaceOrderResponse handleCancelOrderRequest(PlaceOrderRequest placeOrderRequest) throws Exception {
PlaceOrderResponse orderResponse=new PlaceOrderResponse();
orderResponse.setRefNumber(orderService.placeOrder(placeOrderRequest.getOrder()));
return orderResponse;
}

使用 XPath 创建自定义的 marshaller 进行条件 XML 解析。

始终使用现有的编组器框架(JAXB、JiBX 等)是处理编组任务的最简单方法。但是,最终您可能需要编写自定义的编组器。例如,您可能会收到一个 XML 输入数据,它的格式与通常由已识别的编组器使用的格式不同。

Spring 允许您定义自定义编组器并将其注入到端点编组器中,就像现有的编组器框架一样。在这个示例中,客户端以以下格式向服务器发送/接收数据:

<ns:placeOrderRequest >
<ns:order refNumber="12345" customerfName="fName" customerlName="lName" customerTel="12345" dateSubmitted="2008-09-29 05:49:45" orderDate="2008-09-29 05:40:45">
<ns:item type="SNACKS" name="Snacks" quantity="1.0"/>
<ns:item type="DESSERTS" name="Desserts" quantity="1.0"/>
</ns:order>
</ns:placeOrderRequest>
<ns:placeOrderResponse  refNumber="1234"/>

然而,可以映射到/从服务器的 POJO 的 XML 输入如下:

<ns:placeOrderRequest >
<ns:order>
<ns:refNumber>12345</ns:refNumber>
<ns:customerfName>fName</ns:customerfName>
<ns:customerlName>lName</ns:customerlName>
<ns:customerTel>12345</ns:customerTel>
<ns:dateSubmitted>2008-09-29 05:49:45</ns:dateSubmitted>
<ns:orderDate>2008-09-29 05:40:45</ns:orderDate>
<ns:items>
<FoodItem>
<ns:type>SNACKS</ns:type>
<ns:name>Snack</ns:name>
<ns:quantity>1.0</ns:quantity>
</FoodItem>
<FoodItem>
<ns:type>COFEE</ns:type>
<ns:name>Cofee</ns:name>
<ns:quantity>1.0</ns:quantity>
</FoodItem>
</ns:items>
</ns:order>
</ns:placeOrderRequest>
<ns:placeOrderResponse  />
<ns:refNumber>1234</ns:refNumber>
</ns:placeOrderResponse>

在这个示例中,使用自定义的编组器将传入的 XML 数据映射到服务器的 POJO,并将解组服务器响应到客户端格式。

准备工作

这个示例包含一个服务器(LiveRestaurant_R-6.6)和一个客户端(LiveRestaurant_R-6.6-Client)项目。

LiveRestaurant_R-6.6 具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • dom4j-1.6.1.jar

LiveRestaurant_R-6.6-Client 具有以下 Maven 依赖项:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • dom4j-1.6.1.jar

如何做到...

  1. 创建一个自定义编组器类。

  2. 在服务器端配置文件中注册新的编组器。

  3. 设置服务器并运行客户端:

  • 服务器项目根目录:mvn clean package tomcat:run

  • 客户端项目根目录:mvn clean package

以下是服务器端输出:

Received request ..
...
<ns:placeOrderRequest ...>
<ns:order customerTel="12345" customerfName="fName" customerlName="lName" dateSubmitted="2008-09-29 05:49:45" orderDate="2008-09-29 05:40:45" refNumber="12345">
<ns:item name="Snacks" quantity="1.0" type="SNACKS"/>
<ns:item name="Desserts" quantity="1.0" type="DESSERTS"/>
</ns:order>
</ns:placeOrderRequest>
....
Sent response...
<ns:placeOrderResponse  refNumber="12345"/> 

它是如何工作的...

为了能够作为端点编组器工作,自定义编组器(ServerCustomMarshaller)应该实现 MarshallerUnmarshaller 接口。supports 方法用于验证 POJO 类是否已注册到该编组器。注册的 POJO 的值来自 Spring 上下文文件。

当 Web 服务调用端点方法(handleOrderRequest)构建传递参数(PlaceOrderRequest)时,将调用方法 unmarshal。在 unmarshal 方法中,使用 DOM4j 和 XPath 从传入的 XML 数据中提取值。这些值将填充 POJO 类并将其返回给端点。当端点方法(handleOrderRequest)返回响应(PlaceOrderResponse)时,将调用方法 marshal。在 marshal 方法内部,使用 XMLStreamWriter 将所需格式的 XML 数据返回给客户端:

public boolean supports(Class<?> arg0) {
return registeredClassNames.contains(arg0.getSimpleName()) ; }
@Override
public Object unmarshal(Source source) throws IOException,
XmlMappingException {
PlaceOrderRequest placeOrderRequest=new PlaceOrderRequest();
Order order=new Order();
try {
DOMSource in = (DOMSource)source;
org.dom4j.Document document = org.dom4j.DocumentHelper.parseText( xmlToString(source) );
org.dom4j.Element orderRequestElem=document.getRootElement();
org.dom4j.Node orderNode=orderRequestElem.selectSingleNode("//ns:order");
order.setRefNumber(orderNode.valueOf("@refNumber"));
....
placeOrderRequest.setOrder(order);
List orderItems=orderNode.selectNodes("//ns:order/ns:item");
.....
}
@Override
public void marshal(Object bean, Result result) throws IOException,
XmlMappingException
{
XMLStreamWriter writer=null;
PlaceOrderResponse placeOrderResponse=(PlaceOrderResponse) bean;
try {
DOMResult out = (DOMResult)result;
writer = XMLOutputFactory.newInstance().createXMLStreamWriter(out);
writer.writeStartElement("ns", "placeOrderResponse", "http://www.packtpub.com/LiveRestaurant/OrderService/schema");
writer.writeAttribute( "refNumber", placeOrderResponse.getRefNumber());
writer.writeEndElement();
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally{
try{writer.close();}catch (Exception e) {}
}
} .......

如前面的示例所述,服务器/客户端的应用程序上下文使用这个自定义编组器(ServerCustomMarshaller)来执行对象/XML 编组过程。RegisteredClassNames 用于注册符合条件的 POJO 类,以便通过自定义编组器(ServerCustomMarshaller)进行编组/解组。

<bean id="customMarshaller"
class="com.packtpub.liverestaurant.marshaller.ServerCustomMarshaller">
<property name="registeredClassNames">
<list>
<value>PlaceOrderRequest</value>
<value>PlaceOrderResponse</value>
</list>
</property>
</bean>

OrderEndPoint 被注释为 @Endpoint,与 JiBX 示例相同,端点方法的输入和返回参数是映射到模式的 POJO(PlaceOrderResponsePlaceOrderRequest 等)。

第七章:使用 XWSS 库保护 SOAP Web 服务

在本章中,我们将涵盖:

  • 使用用户名令牌和明文/摘要密码对 Web 服务调用进行身份验证

  • 使用 Spring 安全对 Web 服务调用进行身份验证,以验证用户名令牌和明文/摘要密码

  • 使用 JAAS 服务对 Web 服务调用进行身份验证,以验证用户名令牌

  • 准备成对和对称密钥库

  • 使用数字签名保护 SOAP 消息

  • 使用 X509 证书对 Web 服务调用进行身份验证

  • 加密/解密 SOAP 消息

介绍

WS-Security(WSS)由 OASIS 发布,是 SOAP 的扩展,提供 Web 服务的安全标准功能。 XML 和 Web 服务安全(XWSS)是 SUN 对 WSS 的实现,包含在 Java Web 服务开发人员包(WSDP)中。

XWSS 是一种消息级安全,其中安全数据包含在 SOAP 消息/附件中,并允许安全信息随消息或附件一起传输。例如,在签署消息时,将从消息的部分加密生成的安全令牌添加到消息中,以供特定接收者使用。当发送者发送此消息时,此令牌保持在加密形式并随消息一起传输。当接收者收到此消息时,只有具有特定解密密钥的人才能解密令牌。因此,如果在传输此消息期间,任何未经授权的接收者(没有特定密钥的人)收到此消息,他/她无法解密令牌(此令牌将用于检查原始消息是否已更改)。可以通过在接收端(从传入消息中)重新生成令牌并将其与随消息一起传入的令牌进行比较来进行消息的原始性验证。

EndpointInterceptor,顾名思义,拦截请求并在调用端点之前执行某些操作。在之前的章节中,已经解释了SoapEnvelopeLoggingInterceptor,PayloadLoggingInterceptorPayloadValidatingInterceptor用于记录和验证目的。

在本章和下一章中,将解释SecurityInterceptors

Spring-WS XwsSecurityInterceptor是用于在调用端点之前对请求消息执行安全操作的EndpointInterceptor。这个基于 XWSS 的拦截器需要一个策略配置文件来运行。这是一个可以包含多个安全要求的策略配置文件的示例:

<xwss:SecurityConfiguration ...>
<xwss:RequireTimestamp .../>
<xwss:RequireUsernameToken ...../>
........
</xwss:SecurityConfiguration>

安全拦截器使用此配置来查找接收到的 SOAP 消息中期望的安全信息(在接收方),以及要添加到传出消息中的信息(在发送方)。

此外,此拦截器需要一个或多个callBackHandlers用于安全操作,如身份验证,签署传出消息,验证传入消息的签名,解密和加密。这些callBackHandlers需要在应用程序上下文文件中注册:

<sws:interceptors>
<bean
class="...XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler1" />
<ref bean="callbackHandler2" />
..............
</list>
</property>
</bean>
</sws:interceptors>
<bean id="callbackHandler1"
class=".....SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="admin">secret</prop>
<prop key="clinetUser">pass</prop>
</props>
</property>
</bean>
.........

本章介绍了如何将 Spring-WS XWSS 应用于不同的安全操作。在每个配方的项目中,客户端通过在传出消息中添加或修改数据来应用安全操作,并将其发送到服务器。服务器接收消息,提取安全信息,并根据安全信息是否符合预期要求继续处理消息;否则,将故障消息返回给客户端。

为简化起见,本章中的大多数示例使用了在第三章中讨论的使用 Spring-JUnit 支持进行集成测试示例中使用的项目,用于设置服务器并由客户端发送和接收消息。但是,在最后一个示例中,使用了来自为 WS-Addressing 端点创建 Web 服务客户端示例中讨论的第二章中的项目,用于服务器和客户端。

使用明文/摘要用户名令牌进行 Web 服务调用的身份验证

身份验证简单地意味着检查服务的调用者是否是他们声称的人。检查调用者的身份验证的一种方式是检查密码。

XWSS 提供 API 来从传入的 SOAP 消息中获取用户名和密码,并将它们与配置文件中定义的内容进行比较。通过为消息的发送方和接收方定义策略文件来实现此目标,发送方在传出消息中包含用户名令牌,而接收方在接收消息时期望收到此用户名令牌以进行身份验证。

传输明文密码会使 SOAP 消息不安全。 XWSS 提供了在策略文件中配置设置以在发送方消息中包含密码的摘要(由特定算法生成的密码文本的哈希)的配置。在服务器端,服务器将传入消息中包含的摘要密码与从配置文件中设置的摘要密码进行比较(请参见spring-ws-servlet.xmlcallbackHandler bean 内的属性用户)使用发送方端上的相同算法。本教程展示了如何使用用户名令牌和明文/摘要密码对 Web 服务调用进行身份验证。本教程包含两种情况。在第一种情况下,密码将以明文格式传输。但是,在第二种情况下,通过更改策略文件配置,密码将以摘要格式传输。

准备工作

在本教程中,项目名称为LiveRestaurant_R-7.1(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-7.1-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

以下步骤实现使用用户名令牌和明文密码进行身份验证:

  1. LiveRestaurant_R-7.1-Client的应用程序上下文文件(applicationContext.xml)中注册安全拦截器(XwsSecurityInterceptor)和callbackHandlerSimplePasswordValidationCallbackHandler)。

  2. LiveRestaurant_R-7.1-Client添加安全策略文件(securityPolicy.xml)。

  3. LiveRestaurant_R-7.1的应用程序上下文文件(spring-ws-servlet.xml)中注册安全拦截器(XwsSecurityInterceptor)和callbackHandlerSimplePasswordValidationCallbackHandler)。

  4. LiveRestaurant_R-7.1添加安全策略文件(securityPolicy.xml)。

  5. Liverestaurant_R-7.1运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.1-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出(请注意下划线部分中密码标签wsse:Password ...#PasswordText):
INFO: ==== Sending Message Start ====
<SOAP-ENV:Envelope ...">
<SOAP-ENV:Header>
<wsse:Security ..>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T07:19:16.225Z</wsu:Created>
<wsu:Expires>2011-11-06T07:24:16.225Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken .....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordText">****</wsse:Password>
<wsse:Nonce ..#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T07:19:16.272Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest >
.....
.......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
.....
INFO: ==== Received Message Start ====
......
<SOAP-ENV:Envelope....">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse .....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

以下步骤实现使用用户名令牌和摘要密码进行身份验证:

  1. 修改Liverestaurant_R-7.1的安全策略文件(securityPolicy.xml),以从传入消息中获取摘要密码。

  2. 修改 Liverestaurant_R-7.1-Client 的安全策略文件(securityPolicy.xml)以发送摘要密码。

  3. Liverestaurant_R-7.1 运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.1-Client 运行以下命令:
mvn clean package 

  • 以下是客户端输出(请注意密码标签 wsse:Password ...#PasswordDigest)在下划线部分内:
Nov 6, 2011 12:19:25 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
..
<SOAP-ENV:Envelope .../">
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ..>
<wsu:Created>2011-11-06T08:19:25.515Z</wsu:Created>
<wsu:Expires>2011-11-06T08:24:25.515Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordDigest">****</wsse:Password>
<wsse:Nonce ...#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T08:19:25.562Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest..">
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
........
INFO: ==== Received Message Start ====
<?xml version="1.0" ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

Liverestaurant_R-7.1 项目是一个服务器端 Web 服务,需要其客户端发送带有用户名令牌和密码的消息。Liverestaurant_R-7.1-Client 项目是一个客户端测试项目,向服务器发送带有用户名令牌和密码的消息。

在服务器端,XwsSecurityInterceptor 强制服务器对所有传入消息应用 securityPolicy.xml 中的策略,并使用 SimplePasswordValidationCallbackHandler 来比较传入消息的用户名/密码与服务器配置文件中包含的用户名/密码(请参阅 callbackHandler bean 中的属性 users):

<sws:interceptors>
...
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler" />
</list>
</property>
</bean>
</sws:interceptors>
<bean id="callbackHandler"
class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="admin">secret</prop>
<prop key="clinetUser">pass</prop>
</props>
</property> 
</bean>

securityPolicy.xml 文件中,<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="true"/> 要求传入消息具有非加密密码的用户名令牌。useNonce="true" 表示每个传入消息都将具有一个与上一条消息不相等的随机数:

<xwss:SecurityConfiguration dumpMessages="true" >
<xwss:RequireTimestamp maxClockSkew="60" timestampFreshnessLimit="300"/>
<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="true"/>
</xwss:SecurityConfiguration>

在客户端,XwsSecurityInterceptor 强制客户端对所有传出消息应用 securityPolicy.xml 中的策略:

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
....
<property name="interceptors">
<list>
<ref local="xwsSecurityInterceptor" />
</list>
</property>
</bean>
<bean id="xwsSecurityInterceptor"
class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler"/>
</list>
</property>
</bean>
<bean id="callbackHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"/>

securityPolicy.xml 文件中,<xwss:UsernameToken name="clinetUser" password="pass" digestPassword="false" useNonce="true"/> 包括所有传出消息的带有密码的用户名令牌:

<xwss:SecurityConfiguration dumpMessages="true" >
<xwss:Timestamp />
<xwss:UsernameToken name="clinetUser" password="pass" digestPassword="false" useNonce="true"/> ...
</xwss:SecurityConfiguration>

在这里,useNonce="true" 表示每个请求将随着每条消息发送一个新的随机数(Nonce 有助于防止用户名令牌被劫持)。

在使用带有明文密码的用户名令牌进行身份验证的情况下,由于在客户端和服务器端策略文件中都有 digestPassword="false",因此在输出结果中可以看到客户端发送的消息中包含用户名和明文密码:

<wsse:UsernameToken ....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ..>****</wsse:Password>
...
</wsse:UsernameToken> 

然而,在使用摘要用户名令牌和摘要密码进行身份验证的第二种情况中,由于在客户端和服务器端策略文件中都有 digestPassword="true",密码的摘要包含在用户名令牌中:

<wsse:UsernameToken ....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordDigest">****</wsse:Password>
...
</wsse:UsernameToken> 

在这种情况下,服务器将传入的 SOAP 消息摘要密码与 spring-ws-servlet.xml 中计算的摘要密码进行比较。通过这种方式,与密码以明文传输的第一种情况相比,通信将更加安全(明文密码可以轻松从 SOAP 消息中提取。但是,使用 SSL 连接可以保护此类通信)。

另请参阅...

本章讨论的配方 使用 Spring 安全验证 Web 服务调用以验证带有明文/摘要密码的用户名令牌,使用 JAAS 服务验证用户名令牌的 Web 服务调用使用 X509 证书验证 Web 服务调用

使用 Spring 安全验证 Web 服务调用以验证带有明文/摘要密码的用户名令牌

在这里,我们使用了第一个配方中使用的相同身份验证方法。唯一的区别是这里使用了 Spring Security 框架进行身份验证。由于 Spring Security 框架超出了本书的范围,因此这里没有进行描述。但是,您可以在 Spring Security 参考文档中阅读更多相关信息(www.springsource.org/security)。

与本章的第一个配方相同,这个配方也包含两种情况。在第一种情况下,密码将以明文格式传输。在第二种情况下,通过更改策略文件的配置,密码将以摘要格式传输。

准备就绪

在这个配方中,项目的名称是LiveRestaurant_R-7.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-7.2-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

在这个配方中,所有步骤与前一个配方使用用户名令牌对 Web 服务调用进行身份验证中的步骤相同,只是服务器端应用程序上下文文件(spring-ws.servlet.xml)回调处理程序更改并使用 DAO 层来获取数据:

以下步骤实现了使用 Spring Security 对带有明文密码的用户名令牌进行身份验证的 Web 服务调用的身份验证:

  1. LiveRestaurant_R-7.2的应用程序上下文文件(spring-ws-servlet.xml)中注册安全拦截器(XwsSecurityInterceptor)和callbackHandlerSpringPlainTextPasswordValidationCallbackHandler)。

  2. 添加 DAO 层类以获取数据。

  3. Liverestaurant_R-7.2运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.2-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出:
Nov 6, 2011 1:42:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp....>
<wsu:Created>2011-11-06T09:42:37.391Z</wsu:Created>
<wsu:Expires>2011-11-06T09:47:37.391Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken ...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordText">****</wsse:Password>
<wsse:Nonce ...#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T09:42:37.442Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest ...>
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
INFO: ==== Received Message Start ====
<SOAP-ENV:Envelope ...">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

以下步骤实现了使用 Spring Security 对摘要用户名令牌进行身份验证的 Web 服务调用的身份验证:

  1. 在服务器应用程序上下文文件(spring-ws-servlet.xml)中将springSecurityHandler修改为SpringDigestPasswordValidationCallbackHandler

  2. 修改安全策略文件(securityPolicy.xml)在服务器端和客户端都使用摘要密码。

  3. Liverestaurant_R-7.2运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.2-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出:
Nov 6, 2011 2:04:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T10:04:36.622Z</wsu:Created>
<wsu:Expires>2011-11-06T10:09:36.622Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password #PasswordDigest">****</wsse:Password>
<wsse:Nonce #Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T10:04:36.683Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest >
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 6, 2011 2:04:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

Liverestaurant_R-7.2项目中,客户端和服务器端的安全方面几乎与我们在配方使用用户名和明文/摘要密码令牌对 Web 服务调用进行身份验证中使用的Liverestaurant_R-7.1相同,除了在服务器端验证用户。Spring Security 类负责通过与来自 DAO 层的数据进行比较来验证用户和密码(而不是在spring-ws-servlet.xml中硬编码用户名/密码)。此外,与成功验证的用户(与用户名和密码匹配)相关的其他数据(如权限、是否帐户已锁定、是否帐户已过期等)可以从 DAO 层获取并返回用于授权任务或用于验证帐户的到期日期以及检查帐户是否已锁定。

在第一种情况下,CallbackHandler SpringPlainTextPasswordValidationCallbackHandler 比较了传入 SOAP 消息中包含的明文密码和从 DAO 层获取的明文密码。

<sws:interceptors>
<bean
....
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="springSecurityHandler"/>
</list>
</property>
</bean>
</sws:interceptors>
<bean id="springSecurityHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
....

然而,在第二个测试中,CallbackHandlerSpringDigestPasswordValidationCallbackHandler,它比较了传入 SOAP 消息中包含的摘要密码和从 DAO 层获取的密码的摘要。

<bean id="springSecurityHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>

springSecurityHandler使用MyUserDetailService.java,它应该实现 Spring 的UserDetailService来从提供者获取用户名,并从 DAO 层内部获取该用户的所有信息(例如密码、角色、是否过期等)。

public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
return getUserDataFromDao(username);
}
private MyUserDetail getUserDataFromDao(String username) {
/**
*Real scenario: find user data from a DAO layer by userName,
* if this user name found, populate MyUserDetail with its data(username, password,Role, ....).
*/
MyUserDetail mydetail=new MyUserDetail(username,"pass",true,true,true,true);
mydetail.getAuthorities().add(new GrantedAuthorityImpl("ROLE_GENERAL_OPERATOR"));
return mydetail;
}

该服务最终返回了在MyUserDetails.java中填充的数据,该类应该实现 Spring 的UserDetails

public class MyUserDetail implements UserDetails {
private String password;
private String userName;
private boolean isAccountNonExpired;
private boolean isAccountNonLocked;
private boolean isCredentialsNonExpired;
private boolean isEnabled;
public static Collection<GrantedAuthority> authority =
new ArrayList<GrantedAuthority>();
public MyUserDetail( String userName, String password,boolean isAccountNonExpired, boolean isAccountNonlocked,boolean isCredentialsNonExpired, boolean isEnabled){
this.userName=userName;
this.password=password;
this.isAccountNonExpired=isAccountNonExpired;
this.isAccountNonLocked=isAccountNonlocked;
this.isCredentialsNonExpired=isCredentialsNonExpired;
this.isEnabled=isEnabled;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return authority;
}
.....
}

现在,如果UserDetails数据与传入消息的用户名/密码匹配,则返回响应;否则,返回 SOAP 故障消息。

与 7.1 项目一样,在服务器端/客户端的securityPolicy.xml中将digestPassword设置为true/false会导致密码以明文或摘要格式传输。

提示

在实时环境中,我们从不配置明文密码选项。这是一个让黑客启用和禁用的好选项。我们在实时环境中从不需要这样的选项。无论任何类型的系统或应用程序配置,密码始终以加密格式传输。

另请参阅...

在本章中讨论的示例使用 Spring 安全性对用户名令牌进行身份验证,使用 JAAS 服务对用户名令牌进行身份验证使用 X509 证书对 Web 服务调用进行身份验证

使用 JAAS 服务对用户名令牌进行身份验证的 Web 服务调用

我们使用与第一个示例中使用的相同的使用明文用户名令牌的身份验证任务。唯一的区别是这里使用 Java 身份验证和授权服务(JAAS)进行身份验证和授权。由于 JAAS 框架超出了本书的范围,因此这里没有描述。但是,您可以在参考文档中阅读有关 JAAS 的更多信息(download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html)。

xwss包中的JaasPlainTextPasswordValidationCallbackHandler是调用在 JAAS 配置文件中配置的Login模块的 API。

准备工作

在这个示例中,项目的名称是LiveRestaurant_R-7.3(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-7.3-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何操作...

在这个示例中,所有步骤与前一个示例使用用户名令牌和明文/摘要密码进行 Web 服务调用身份验证相同,只是服务器端应用程序上下文文件(spring-ws.servlet.xml)的回调处理程序更改并使用 JAAS 框架作为身份验证和授权服务:

  1. 在服务器端应用程序上下文文件(spring-ws.servlet.xml)中注册 JAAScallbackHandlerJaasPlainTextPasswordValidationCallbackHandler)。

  2. 添加 JAAS 框架所需的类(RdbmsPrincipalRdbmsCredentialRdbmsPlainTextLoginModule)和配置文件(jaas.config)。

  3. Liverestaurant_R-7.3运行以下命令:

mvn clean package tomcat:run -Djava.security.auth.login.config="src/main/resources/jaas.config" 

  1. Liverestaurant_R-7.3-Client运行以下命令:
mvn clean package 

  • 以下是客户端的输出:
INFO: ==== Sending Message Start ====
....
<SOAP-ENV:Envelope ....">
<SOAP-ENV:Header>
<wsse:Security ....>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T11:59:09.712Z</wsu:Created>
<wsu:Expires>2011-11-06T12:04:09.712Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken ...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ....#PasswordText">****</wsse:Password>
<wsse:Nonce ...0#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T11:59:09.774Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest...>
.....
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
...
INFO: ==== Received Message Start ====
...
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ....>
<wsu:Created>2011-11-06T11:59:11.630Z</wsu:Created>
<wsu:Expires>2011-11-06T12:04:11.630Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

Liverestaurant_R-7.3项目中,关于客户端和服务器端的安全性几乎与我们在使用用户名和明文/摘要密码令牌对 Web 服务调用进行身份验证示例中使用的Liverestaurant_R-7.1项目相同,除了在服务器端验证用户。一个 JAAS 框架负责通过比较传入消息的用户名/密码与从数据源(这里是数据库)获取的数据来验证用户和密码。

客户端发送包含明文用户名令牌的请求 SOAP 消息。服务器接收此消息并使用 JAAS 框架将传入消息的用户名/密码与 JAAS 从 DAO 层获取的内容进行比较。如果匹配,则返回正常响应;否则返回失败消息。

spring-ws-servlet.xml 中,JaasPlainTextPasswordValidationCallbackHandler 被注册为一个回调处理程序,它使用 RdbmsPlainText 作为可插拔的 JAAS 登录模块进行用户名/密码身份验证:

<sws:interceptors>
.......
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="callbackHandlers">
<list>
<ref bean="jaasValidationHandler" />
</list>
</property>
</bean>
</sws:interceptors>
<bean id="jaasValidationHandler" class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
<property name="loginContextName" value="RdbmsPlainText" />
</bean>

当服务器端使用 mvn -Djava.security.auth.login.config="src/main/resources/jaas.config" 运行时,它使用 jaas.config 文件来定位在服务器端应用程序上下文中注册的 JAAS 登录模块(RdbmsPlainTextLoginModule):

RdbmsPlainText {
com.packtpub.liverestaurant.service.security.RdbmsPlainTextLoginModule Required;
};

将从 RdbmsPlainTextLoginModule.javalogin 方法中调用以从 DAO 层获取用户密码和凭据。如果获取的密码与传入消息的密码匹配,则设置凭据并返回 true;否则,抛出异常,导致服务器向客户端发送故障消息:

public class RdbmsPlainTextLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private boolean success;
private List<RdbmsPrincipal> principals = new ArrayList<RdbmsPrincipal>();
private List<RdbmsCredential> credentials = new ArrayList<RdbmsCredential>();
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
.....
}
@Override
public boolean login() throws LoginException {
......
}
private List<String> getAllPermission(String username) {
......
}
private boolean authenticate(String username,String password)
{
....
}
public boolean commit() throws LoginException {
.....
}
@Override
public boolean logout() throws LoginException {
.....
}
}

注意

在重要的应用程序中,甚至用户名也是加密的。这提供了更多的安全性,竞争对手无法猜测哪些用户来自哪个位置,使用 ISP 级别的过滤。黑客猜测或跟踪用户名,并发送重复请求以加载不必要的数据到服务器。在这个配方中,由于密码以明文格式传输,建议使用 SSL 连接。Spring-WS 还支持 JaasCertificateValidationCallbackHandler,它使用证书进行身份验证。这个处理程序在这里没有使用。但是,您可以在以下网址找到更多信息:

static.springsource.org/spring-ws/site/apidocs/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandler.html

另请参阅...

在本章中讨论了使用用户名令牌和明文/摘要密码进行 Web 服务调用身份验证、使用 Spring Security 对用户名令牌进行身份验证以及使用 X509 证书进行身份验证的配方。

准备配对和对称密钥库

为了为 Web 服务调用添加更多安全措施,我们需要一些额外的操作,如对 Web 服务消息进行签名和验证签名、加密/解密以及使用证书进行身份验证。XWSS 使用密钥库提供这些操作。java.security.KeyStore 类为加密密钥和证书提供了内存容器。此类可以包括三种类型的条目:

  • 包含私钥和公钥证书的私钥条目(请注意,这里的公钥包含在 X.509 证书中,私钥和公钥证书的组合被称为密钥对

  • 包含对称密钥的秘密密钥条目

  • 信任的证书条目,其中包含一个受信任的证书(此证书是另一方的证书,作为受信任的证书导入,这意味着所有者密钥存储在第三方属于另一方证书的公钥)

密钥库可以包含一个到多个条目。密钥库中的别名用于区分条目。私钥和证书由一个别名引用,而任何其他受信任的证书或秘密密钥条目则由密钥库中的不同个别别名引用。

在本章的前面,介绍了使用用户名令牌对 Web 服务调用进行身份验证。可以使用证书对 Web 服务调用进行身份验证。在本章的后面,将介绍使用证书进行身份验证。此外,这些证书可以用于证书验证、签名验证和加密。

Java keytool 是一个生成和存储密钥和证书在密钥库文件中的工具。这个密钥库由一个密钥库密码保护。此外,还有一个密码保护私钥。

在本配方中,使用 keytool 生成具有对称密钥条目、私钥条目(私钥和公钥证书对)和受信任证书条目的密钥库。这些密钥稍后将在本章和第八章以及Securing SOAP Web-Services using WSS4J Library中用于签名和验证 Web 服务消息的签名、加密/解密和使用证书进行身份验证。

准备工作

按照第一个配方中描述的安装 Java。

如何操作...

要生成一个具有别名symmetric的秘密密钥条目的密钥库,请运行以下命令(此密钥库稍后用于对称加密/解密):

keytool -genseckey -alias 'symmetric' -keyalg 'DESede' -keystore symmetricStore.jks -storepass 'symmetricPassword' -keypass 'keyPassword' -storetype "JCEKS" 

要生成一个具有私钥条目或密钥对(包含私钥和公钥证书对)的密钥库,请按照以下步骤进行:

  1. 要生成接收方(服务器端)的密钥库,请运行以下命令并按照命令提示进行:
keytool -genkey -alias server -keyalg RSA -keystore serverStore.jks -validity 3653
Enter keystore password:serverPassword
Re-enter new password:serverPassword
What is your first and last name?
[Unknown]: MyFirstName MyLastName
What is the name of your organizational unit?
[Unknown]: Software
What is the name of your organization?
[Unknown]: MyCompany
What is the name of your City or Locality?
[Unknown]: MyCity
What is the name of your State or Province?
[Unknown]: MyProvince
What is the two-letter country code for this unit?
[Unknown]: ME
Is CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME correct?
[no]: yes
Enter key password for <server>
(RETURN if same as keystore password):serPkPassword
Re-enter new password:serPkPassword 

  1. 要生成发送方(客户端端)的密钥库,请运行以下命令并按照命令提示进行:
keytool -genkey -alias client -keyalg RSA -keystore clientStore.jks -validity 3653
Enter keystore password:clientPassword
Re-enter new password:clientPassword
What is your first and last name?
[Unknown]: MyFirstName MyLastName
What is the name of your organizational unit?
[Unknown]: Software
What is the name of your organization?
[Unknown]: MyCompany
What is the name of your City or Locality?
[Unknown]: MyCity
What is the name of your State or Province?
[Unknown]: MyProvince
What is the two-letter country code for this unit?
[Unknown]: ME
Is CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME correct?
[no]: yes
Enter key password for <server>
(RETURN if same as keystore password):cliPkPassword
Re-enter new password:cliPkPassword 

  1. 要查看密钥库中生成的私钥条目,请运行以下命令(请注意在下划线文本中的privateKeyEntry):
keytool -list -v -keystore serverStore.jks -storepass serverPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: server
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3 

  1. 要从具有私钥条目的密钥库生成证书(公钥),请在客户端/服务器端的密钥库上运行以下命令:
keytool -export -file clientStore.cert -keystore clientStore.jks -storepass clientPassword -alias client
keytool -export -file serverStore.cert -keystore serverStore.jks -storepass serverPassword -alias server 

  1. 要将发送方(客户端)的公钥证书导入接收方(服务器)的密钥库,请在服务器端的密钥库上运行以下命令(此证书将存储为密钥库中的受信任证书条目,别名为client)
keytool -import -file clientStore.cert -keystore serverStore.jks -storepass serverPassword -alias client
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore 

  1. 要将接收方(服务器)的公钥证书导入发送方(客户端)的密钥库,请在发送方(客户端端)的密钥库上运行以下命令(此证书将存储为密钥库中的受信任证书条目,别名为server)
keytool -import -file serverStore.cert -keystore clientStore.jks -storepass clientPassword -alias server
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore 

  1. 要查看密钥库中服务器的私钥条目和受信任证书条目,请运行以下命令(请注意在下划线文本中的trustedCertEntryprivateKeyEntry):
keytool -list -v -keystore serverStore.jks -storepass serverPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
Alias name: client
Creation date: 26-Jul-2011
Entry type: trustedCertEntry
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
*******************************************
Alias name: server
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
******************************************* 

  1. 要查看密钥库中客户端的私钥条目和受信任证书条目,请运行以下命令:
keytool -list -v -keystore clientStore.jks -storepass clientPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
Alias name: client
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
******************************************
Alias name: server
Creation date: 26-Jul-2011
Entry type: trustedCertEntry
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
******************************************* 

工作原理...

首先生成一个对称密钥库,可以由客户端和服务器共享进行加密和解密。此命令生成对称密钥库:

keytool -genseckey -alias 'symmetric' -keyalg 'DESede' -keystore symmetricStore.jks -storepass 'symmetricPassword' -keypass 'keyPassword' -storetype "JCEKS" 

要生成一个具有私钥条目和受信任证书条目的密钥库,首先应该为客户端和服务器端生成一个密钥对(私钥和公钥证书)密钥库。

然后应该从客户端/服务器密钥库中导出公钥证书。最后,应该将客户端证书导入服务器密钥库,并将服务器证书导入客户端密钥库(此导入的证书将被称为受信任证书)

keytool -genkey -alias aliasName -keyalg RSA -keystore keyStoreFileName.jks -validity 3653 

上述命令生成一个具有私钥条目的密钥库,其中aliasName是密钥库的标识符。有效期是此密钥有效的天数。

keytool -export -file clientStore.cert -keystore clientStore.jks -storepass clientPassword -alias client 

上述命令导出嵌入在密钥库中私钥条目内的公钥证书。

keytool -import -file clientStore.cert -keystore serverStore.jks -storepass serverPassword -alias client

上述命令将从客户端密钥库中导入生成的公钥证书到服务器密钥库中(此导入的证书将被称为受信任证书)。

有关加密和密钥库的更多信息可以在以下网址找到:

docs.oracle.com/javase/1.5.0/docs/api/java/security/KeyStore.html.

en.wikipedia.org/wiki/Category:Public-key_cryptography.

另请参阅...

本章讨论了使用数字签名保护 SOAP 消息使用 X509 证书对 Web 服务调用进行身份验证以及加密/解密 SOAP 消息的方法。

使用数字签名保护 SOAP 消息

数字签名的目的是验证接收到的消息是否被篡改,以证明发送者是他/她声称的身份(身份验证),并证明来自特定发送者的操作。对消息进行数字签名意味着添加哈希数据,即将一段信息(令牌)添加到 SOAP 信封中。接收者需要从传入的消息中重新生成自己的哈希并将其与发送者的哈希进行比较。如果接收者的哈希与发送者的哈希匹配,则实现了数据完整性,接收者将继续进行;否则,它将向发送者返回一个 SOAP 故障消息。

为了验证发送者,发送者应使用自己的私钥加密签名令牌。接收者应在接收者密钥库中拥有发送者的公钥证书(该证书称为受信任证书,并位于受信任证书条目下)以解密发送者的签名令牌,并重复已解释的步骤以检查消息完整性。现在,如果消息完整性得到保证,则证明了发送者的身份验证(因为只有嵌入在接收者密钥库中的发送者证书才能解密发送者的加密签名)。此外,还证明了发送者发送消息的行为(因为在接收者端成功解密签名表明发送者已经使用自己的私钥对其进行了加密)。

在本章中,发送方(客户端)对消息进行签名,并使用其自己的私钥(在客户端密钥库中)对签名进行加密。在接收方(服务器)端,服务器密钥库中的客户端公钥证书(该证书称为受信任证书,并位于密钥库中的受信任证书条目下)将用于解密令牌的签名;然后服务器验证签名令牌。

准备工作

在本章中,项目名称为LiveRestaurant_R-7.4(用于服务器端 Web 服务),具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-7.4-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

  1. serverStore.jks复制到服务器端,将clientStore.jks复制到客户端(这些密钥库已在本章中的准备配对和对称密钥库中生成)。

  2. 在服务器端配置安全策略文件(securityPolicy.xml)以期望在客户端签署传入消息时附带签名令牌,并在客户端签署传出消息时签署。

  3. 在服务器端应用程序上下文文件中注册keyStoreHAndler(KeyStoreCallbackHandler)和trustStore(KeyStoreFactoryBean)。

  4. 在客户端应用程序上下文文件中注册keyStoreHAndler(KeyStoreCallbackHandler)和keyStore(KeyStoreFactoryBean)。

  5. Liverestaurant_R-7.4运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.4-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出(注意下划线文本中的ds:Signature标签):
INFO: ==== Sending Message Start ====
....
<SOAP-ENV:Envelope.....>
<SOAP-ENV:Header>
<wsse:Security ....>
...
<ds:Signature ....>
<ds:SignedInfo>
.....
</ds:SignedInfo>
<ds:SignatureValue>....</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference .........>
<wsse:Reference ..../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body....>
<tns:placeOrderRequest ...>
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====....
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

服务器端的安全策略要求客户端在消息中包含二进制签名令牌。客户端策略文件中的设置将签名令牌包含在传出消息中。客户端使用其自己的私钥(包含在客户端密钥库中)来加密消息的签名令牌。在服务器端,服务器密钥库中包含的客户端公钥证书(证书称为受信任证书,并位于密钥库中的受信任证书条目下),将用于解密传入的签名令牌。然后服务器继续进行签名的验证。

在策略文件中的以下服务器端安全配置会导致服务器期望从传入消息中获得安全令牌(用于验证传入消息):

<xwss:RequireSignature requireTimestamp="false" />
</xwss:SecurityConfiguration>

然而,在客户端,策略文件中的这个安全配置会导致客户端在传出消息中的 SOAP 消息中包含安全令牌:

<xwss:Sign includeTimestamp="false">
</xwss:Sign>

在客户端应用程序上下文中的以下设置会导致客户端使用clientStore.jks中的私钥来加密消息的签名令牌。私钥的密码是cliPkPassword,私钥条目的别名是client,并且通过读取密钥库clientStore.jks并使用密钥库密码clientPassword生成密钥库 bean:

<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="password" value="clientPassword" />
<property name="location" value="/clientStore.jks" />
</bean>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore" />
<property name="privateKeyPassword" value="cliPkPassword" />
<property name="defaultAlias" value="client" />
</bean>

在服务器端,服务器配置文件中的以下设置会导致服务器首先使用服务器密钥库中的客户端证书(证书称为受信任证书)解密签名令牌。然后验证传入消息的签名(以查看原始消息是否被篡改):

<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="/WEB-INF/serverStore.jks" />
<property name="password" value="serverPassword" />
</bean>

另请参阅...

本章讨论的准备配对和对称密钥存储使用 X509 证书对 Web 服务调用进行身份验证示例。

使用 X509 证书对 Web 服务调用进行身份验证

在前一个示例中,通过更改发送方(客户端)安全策略文件,发送方可以在传出消息中包含客户端的证书。然后在接收方(服务器)在验证签名之前,服务器会尝试通过比较传入消息中的客户端证书与服务器密钥库中嵌入的客户端证书(受信任证书)来对发送方进行身份验证。此外,在这个示例中,客户端证书包含在发送方的传出消息中,并且在接收方,用于提取证书中包含的数据进行身份验证和授权目的。

SpringCertificateValidationCallbackHandler,来自 XWSS 包,可以提取证书数据(例如CN=MyFirstName MyLastName),这些数据可以用于身份验证和授权。

在这个示例中,我们利用使用数字签名保护 SOAP 消息示例来进行签名和签名验证。然后使用SpringCertificateValidationCallbackHandler进行身份验证,使用从 DAO 层获取的数据以及对 Web 服务调用的授权。

准备工作

在这个示例中,项目的名称是LiveRestaurant_R-7.5(用于服务器端 Web 服务),它具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

LiveRestaurant_R-7.5-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

操作步骤...

在这个配方中,所有步骤与前一个配方使用数字签名保护 SOAP 消息中的步骤相同,除了修改客户端的策略文件,将客户端证书包含在传出消息中以及服务器端应用程序上下文文件(spring-ws.servlet.xml)的更改,并且使用 DAO 层来获取数据:

  1. 在服务器端应用程序上下文文件(spring-ws-servlet.xml)中注册springSecurityCertificateHandler

  2. 修改客户端安全策略文件,将客户端证书包含在传出消息中。

  3. 添加 DAO 层类以获取数据。

以下是客户端输出(请注意下划线文本中的 X509 客户端认证):

INFO: ==== Sending Message Start ====
<?xml...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:BinarySecurityToken...wss-x509-token-..>.....</wsse:BinarySecurityToken>
<ds:Signature .....>
<ds:SignedInfo>
......
</ds:SignedInfo>
<ds:SignatureValue>.....</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference...>
<wsse:Reference ...wss-x509-token-profile-1.0.../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body ....>
<tns:placeOrderRequest ...>
.....
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
INFO: ==== Received Message Start ====
<?xml version="1.0" ....>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

关于签名的一切都与配方使用数字签名保护 SOAP 消息中描述的一样。此外,客户端证书包含在传出消息中,并在服务器端提取客户端证书数据进行一些处理操作。

一旦客户端证书被提取(即嵌入在传入消息中),就可以通过检索用户名或其他信息进行身份验证。

在客户端策略文件中包含以下部分会导致客户端在传出消息中包含自己的公钥证书:

<xwss:X509Token certificateAlias="client" />

在签名消息时在调用者消息中嵌入客户端证书会导致服务器在签名验证之前验证此证书与服务器密钥库中包含的证书(发送方受信任的证书条目)的证书。此验证确认调用者是其所声称的人。但是,如果需要检查账户的激活/锁定或需要授权调用者访问特定资源,则在服务器配置文件中配置的springSecurityCertificateHandler会处理这些任务:

<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/>
<property name="secureResponse" value="false" />
<property name="callbackHandlers">
<list>
<ref bean="keyStoreHandler"/>
<ref bean="springSecurityCertificateHandler"/>
</list>
</property>
</bean>
</sws:interceptors>
<bean id="springSecurityCertificateHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider">
<property name="x509AuthoritiesPopulator">
<bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.packtpub.liverestaurant.service.dao.MyUserDetailService" />

此处理程序使用调用DaoX509AuthoritiesPopulator的身份验证管理器,该管理器应用自定义服务类MyUserDetailService进行身份验证,并提取用于授权目的的用户凭据:

public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
return findUserDetailFromDAO(username);
}
private UserDetails findUserDetailFromDAO(String userName)throws UsernameNotFoundException{
MyUserDetail mydetail=null;
/**
*Real scenario: Find user-name from DAO layer, if user found, get data from the DAO and set MyUserDetail otherwise throw UsernameNotFoundException.
*/
if(! userName.equals("MyFirstName MyLastName")){
throw new UsernameNotFoundException("User name not found");
}
mydetail=new MyUserDetail(userName,"fetchedPassword",true,true,true,true,new GrantedAuthorityImpl("ROLE_GENERAL_OPERATOR"));
return mydetail;
}
}

参见...

本章讨论的使用数字签名保护 SOAP 消息准备对和对称密钥库的配方。

加密/解密 SOAP 消息

加密是将可读或明文数据格式转换为不可读的加密格式或密文的过程,使用特定算法。这些算法称为加密算法,需要加密密钥。解密只是加密的反向操作;它使用解密密钥将密文转换回可读或明文数据格式。加密和解密密钥可以相同也可以不同。如果加密和解密密钥相同,并且发送方和接收方共享密钥,则该密钥称为对称秘密密钥。加密和解密密钥可以不同,在这种情况下,该密钥称为非对称公钥

以下图表展示了对称密钥用于加密/解密的用法。发送方和接收方可以共享相同的密钥,这被称为对称密钥。拥有此密钥的人可以解密/加密消息。例如,发送方使用对称密钥进行加密,接收方使用对称密钥进行解密:

加密/解密 SOAP 消息

以下图表展示了公钥/私钥用于加密/解密的用法。作为发送方的 Bob 获取 Alice 的公钥,加密一条消息并发送给 Alice。因为只有她拥有自己的私钥,她可以解密消息:

加密/解密 SOAP 消息

在这个配方中,发送方(客户端)以三种不同的情况对消息进行加密并将其发送给接收方(服务器)。在第一种情况下,对称密钥(在具有客户端和服务器相同的秘密密钥条目的存储中)用于在客户端进行加密和在服务器端进行解密。然后,在第二种情况下,接收方(服务器)在发送方(客户端)的密钥库中使用的接收方(服务器)的公钥证书(在接收方受信任的证书条目内)用于数据加密,并且接收方(服务器)的私钥在服务器端密钥库上用于解密。

由于在annotation端点映射(PayloadRootAnnotationMethodEndpointMapping)中对整个有效负载进行加密会使路由信息(例如localPart = "placeOrderRequest",namespace = "http://www.packtpub.com/liverestaurant/OrderService/schema",包含在有效负载中)与整个有效负载一起加密,因此无法使用annotation端点映射。相反,使用SoapActionAnnotationMethodEndpointMapping寻址样式进行端点映射。在这种情况下,路由数据包含在 SOAP 标头中,而在注释端点映射中,它包含在有效负载中。尽管有效负载的一部分加密可以与有效负载注释端点映射一起使用,但为了一致性,整个配方使用SoapActionAnnotationMethodEndpointMapping寻址样式。

有关端点映射的更多信息,请参阅第一章中讨论的配方通过注释有效负载根设置端点设置传输中立 WS-Addressing 端点构建 SOAP Web 服务

在前两种情况下,整个有效负载用于加密/解密。XWSS 策略配置文件使得可能对有效负载部分进行加密/解密。在第三种情况下,只有有效负载的一部分被设置为加密/解密的目标。

准备工作

在这个配方中,项目的名称是LiveRestaurant_R-7.6(用于服务器端 Web 服务),并具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • mail-1.4.1.jar

  • saaj-api-1.3.jar

  • saaj-impl-1.3.2.jar

LiveRestaurant_R-7.6-Client(用于客户端 Web 服务)具有以下 Maven 依赖项:

  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

以下步骤使用共享对称密钥(symmetricStore.jks)实现加密/解密:

  1. 在服务器/客户端应用程序上下文中注册keyStoreHandlersymmetricStore。将对称密钥库(symmetricStore.jks)复制到服务器/客户端文件夹(此密钥库已在本章讨论的准备对和对称密钥库配方中生成)。

  2. 在服务器端配置安全策略文件(securityPolicy.xml)以期望来自其客户端的消息加密,并在客户端端加密传出消息。

  3. Liverestaurant_R-7.6运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.6-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出(请注意输出中的下划线部分):
INFO: ==== Received Message Start ====
....
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ....>
.......
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<xenc:EncryptedData.....">
<xenc:EncryptionMethod ....>
<ds:KeyInfo ...xmldsig#">
<ds:KeyName>symmetric</ds:KeyName>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>
3esI76ANNDEIZ5RWJt.....
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
Nov 7, 2011 11:48:46 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
<?xml version="1.0" ...
><SOAP-ENV:Envelope ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ==== 

以下步骤使用服务器信任的证书(或公钥)在客户端密钥库(clientStore.jks)上进行加密,并在服务器端密钥库(serverStore.jks)上使用服务器私钥进行解密:

  1. 修改securityPolicy.xml以使用客户端端的服务器信任证书(包含在clientStore.jks中)对消息进行加密,并在服务器端使用服务器私钥(包含在serverStore.jks中)进行解密。

  2. 在服务器端和客户端应用程序上下文中注册keyStoreHandlerkeyStore,在客户端上注册keyStoreHandlertrustStore。将clientStore.jks复制到客户端,将serverStore.jks复制到服务器文件夹(此密钥库已在本章讨论的准备对和对称密钥库中生成)。

  3. 在服务器端配置安全策略文件(securityPolicy.xml)以期望来自其客户端的消息加密,并在客户端端加密传出消息。

  4. Liverestaurant_R-7.6运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.6-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出(注意输出中的下划线部分):
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ...>
........
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<xenc:EncryptedData....>
<xenc:EncryptionMethod .../>
<ds:KeyInfo >
<wsse:SecurityTokenReference...>
<wsse:Reference ..../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
...
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 8, 2011 12:12:11 AM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse >
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

以下步骤实现了负载的部分加密/解密:

  1. 修改客户端/服务器端的securityPolicy.xml以设置加密的目标。

  2. Liverestaurant_R-7.6运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-7.6-Client运行以下命令:
mvn clean package 

  • 以下是客户端输出(注意输出中的下划线部分):
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ....>
........
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest ...>
<xenc:EncryptedData .....>
..........
<xenc:CipherData>
<xenc:CipherValue>NEeTuduV....
..........
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 8, 2011 12:18:39 AM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
....
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ==== 

工作原理...

在第一种情况下,客户端和服务器共享对称密钥。客户端使用对称密钥加密整个负载并将其发送到服务器。在服务器端,相同的密钥将用于解密负载。然而,在第二和第三种情况下,客户端存储中嵌入的服务器证书用于负载的加密,服务器端的服务器存储中的私钥将用于解密。

服务器/客户端策略文件中的RequireEncryption/Encrypt标签导致客户端加密消息,服务器解密消息。keyAlias是在对称密钥库生成时设置的别名。客户端和服务器端策略文件中的以下部分针对要加密/解密的消息信封的部分。qname: {http://schemas.xmlsoap.org/soap/envelope/}Body导致仅使用 SOAP 信封的主体部分进行加密/解密。

---server policy file
<xwss:RequireEncryption>
<xwss:SymmetricKey keyAlias="symmetric" />
<xwss:EncryptionTarget type="qname" value="{http://schemas.xmlsoap.org/soap/envelope/}Body" enforce="true"
contentOnly="true" />
</xwss:RequireEncryption>
---client policy file
<xwss:Encrypt>
<xwss:SymmetricKey keyAlias="symmetric" />
<xwss:Target type="qname">{http://schemas.xmlsoap.org/soap/envelope/}Body
</xwss:Target>
</xwss:Encrypt>

服务器和客户端配置文件中的这一部分导致对称存储用于加密。callbackHandlerkeyStoreHandlerBean)使用具有密钥密码keyPassword的对称密钥库(symmetricStore bean)。KeyStore bean 将通过从密钥库位置(symmetricStore.jks)读取并使用密钥库密码symmetricPassword,类型设置为 JCEKS(密码和类型在对称密钥库生成时设置)生成。

<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="symmetricStore" ref="symmetricStore" />
<property name="symmetricKeyPassword" value="keyPassword" />
</bean>
<bean id="symmetricStore"
class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="password" value="symmetricPassword" />
<property name="location" value="/WEB-INF/symmetricStore.jks" />
<property name="type" value="JCEKS" />
</bean>

在第二种情况下,几乎所有设置都相同,只是客户端使用服务器公钥进行加密,服务器使用服务器存储私钥进行解密。服务器端配置文件中的以下部分导致服务器使用服务器端密钥库中的服务器私钥进行解密。私钥密码为serPkPasswords,密钥库中私钥条目的别名为serverKeyStore bean 将通过从密钥库文件(serverStore.jks)读取并使用密码serverPassword(密码和别名在密钥库生成时设置)生成。

---server configuration
<bean id="keyStoreHandler"
class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore" />
<property name="privateKeyPassword" value="serPkPassword" />
<property name="defaultAlias" value="server" />
</bean>
<bean id="keyStore"
class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="/WEB-INF/serverStore.jks" />
<property name="password" value="serverPassword" />
</bean>

客户端配置文件中的这一部分导致客户端使用服务器证书(公钥)在客户端信任存储中进行加密。KeyStore(这里是信任存储)bean 将通过从clientStore.jks读取并使用密码clientPAssword生成。

---client configuration
<bean id="keyStoreHandler"
class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore"
class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="/clientStore.jks" />
<property name="password" value="clientPassword" />
</bean>

在客户端和服务器端的策略文件中,以下行导致服务器公钥在客户端进行加密,服务器存储中的私钥用于解密。

<xwss:X509Token certificateAlias="server"/>

在第三种情况下,服务器和客户端的策略文件中的以下部分只会导致负载的一部分被加密:

<xwss:Target type="qname">{http://www.packtpub.com/LiveRestaurant/placeOrderService/schema}OrderRequest</xwss:Target>

提示

在加密整个负载时,使用 WS-Addressing,因为路由信息将包含在标头中。

注意

密钥库、密钥管理、密钥的频繁更新以及证书是独立的领域,不是本书的一部分。选择最佳选项需要更多的研究,这是与架构相关工作的一部分。

另请参阅...

本章讨论了《使用数字签名保护 SOAP 消息》和《准备成对和对称密钥库》的配方。

本书讨论了《创建用于 WS-Addressing 端点的 Web 服务客户端》一章中的配方,该章节可在《第二章构建 SOAP Web 服务的客户端》中找到。

第八章:使用 WSS4J 库保护 SOAP Web 服务

在本章中,我们将涵盖:

  • 使用用户名令牌和明文/摘要密码对 Web 服务调用进行身份验证

  • 使用 Spring 安全对用户名令牌进行身份验证,密码为明文/摘要

  • 使用数字签名保护 SOAP 消息

  • 使用 X509 证书对 Web 服务调用进行身份验证

  • 加密/解密 SOAP 消息

介绍

在上一章中,解释了在 Spring-WS 中使用 SUN 的实现(XWSS):OASIS Web-Services Security (WS-SecurityWSS)规范(使用XwsSecurityInterceptor执行安全操作)。在本章中,将解释 Spring-WS 对 Apache 的实现(WSS4J)OASIS WS-Security 规范的支持。尽管这两种 WS-Security 的实现都能够执行所需的安全操作(身份验证、签名消息和加密/解密),但 WSS4J 的执行速度比 XWSS 快。

Spring-WS 支持使用Wss4jSecurityInterceptor来支持 WSS4J,这是一个在调用Endpoint之前对请求消息执行安全操作的EndpointInterceptor

虽然 XWSS 使用外部配置策略文件,但 WSS4J(以及相应的Wss4jSecurityInterceptor)不需要外部配置文件,并且完全可以通过属性进行配置。该拦截器应用的验证(接收方)和保护(发送方)操作通过validationActionssecurementActions属性指定。可以将多个操作设置为由空格分隔的字符串。以下是本章中接收方(服务器端)的示例配置:

<!--In receiver side(server-side in this chapter)-->
<bean id="wss4jSecurityInterceptor"
<property name="validationActions" value="UsernameToken Encrypt" />
..
<!--In sender side(client-side in this chapter)-->
<property name="securementActions" value="UsernameToken Encrypt" />
..
</bean>

validationActions是由空格分隔的操作列表。当发送者发送消息时,将执行validationActions(在接收方)。

securementActions是由空格分隔的操作列表。当发送者向接收者发送消息时,将执行这些操作。

  • 验证操作:UsernameToken, Timestamp, Encrypt, signatureNoSecurity

  • 安全操作:UsernameToken, UsernameTokenSignature, Timestamp, Encrypt, SignatureNoSecurity

操作的顺序很重要,并由Wss4jSecurityInterceptor应用。如果传入的 SOAP 消息securementActions(在发送方)与validationActions(在接收方)配置的方式不同,该拦截器将返回故障消息。

对于加密/解密或签名等操作,WSS4J 需要从密钥库(store.jks)中读取数据:

<bean class="org.springframework. ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="key storePassword" value="storePassword" />
<property name="key storeLocation" value="/WEB-INF/store.jks" />
</bean>

在上一章中已经详细介绍了身份验证、签名、解密和加密等安全概念。在本章中,我们将讨论如何使用 WSS4J 实现这些功能。

为简化起见,在本章的大多数示例中,使用如何使用 Spring-JUnit 支持集成测试项目,第三章,测试和监控 Web 服务,来设置服务器并通过客户端发送和接收消息。然而,在最后一个示例中,使用了来自第二章的项目,为 WS-Addressing 端点创建 Web 服务客户端,用于服务器和客户端。

使用用户名令牌和明文/摘要密码对 Web 服务调用进行身份验证

身份验证简单地意味着检查服务的调用者是否是其所声称的。检查调用者的身份验证的一种方式是检查其密码(如果我们将用户名视为一个人,密码类似于该人的签名)。Spring-WS 使用Wss4jSecurityInterceptor来发送/接收带有密码的用户名令牌以及 SOAP 消息,并在接收方进行比较,比较其与属性格式中预定义的用户名/密码。拦截器的此属性设置强制告诉消息发送方,发送消息中应包含带有密码的用户名令牌,并且在接收方,接收方期望接收此用户名令牌以进行身份验证。

传输明文密码会使 SOAP 消息不安全。Wss4jSecurityInterceptor提供了配置属性(以属性格式)来将密码的摘要与发送方消息一起包括。在接收方,将与属性格式中设置的摘要密码进行比较,该摘要密码包含在传入消息中。

本示例介绍了如何使用用户名令牌对 Web 服务调用进行身份验证。在这里,客户端充当发送方,服务器充当接收方。本示例包含两种情况。在第一种情况下,密码将以明文格式传输。在第二种情况下,通过更改属性,密码将以摘要格式传输。

准备工作

在本示例中,我们有以下两个项目:

  1. LiveRestaurant_R-8.1(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-8.1-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

按照以下步骤使用带有明文密码的普通用户名令牌进行身份验证:

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为UsernameToken,并在此拦截器中配置callbackHandler....wss4j.callback.SimplePasswordValidationCallbackHandler)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将securement操作设置为UsernameToken,并在此处设置username、passwordpassword type(以text格式)。

  3. Liverestaurant_R-8.1上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.1-Client上运行以下命令:
mvn clean package 

  • 以下是客户端的输出(请注意,在 SOAP 的EnvelopeHeader中突出显示了带有明文密码标记的UsernameToken):
Sent request .....
[<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:UsernameToken ...>
<wsse:Username>admin</wsse:Username>
<wsse:Password #PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
....
<tns:placeOrderRequest ...>
....
</tns:order>
</tns:placeOrderRequest>
... Received response ....
<tns:placeOrderResponse ...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
... 

按照以下步骤使用用户名令牌和摘要密码实现身份验证:

  1. 修改客户端应用程序上下文(applicationContext.xml)以将密码类型设置为摘要格式(请注意,服务器端无需进行任何更改)。

  2. Liverestaurant_R-8.1上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.1-Client上运行以下命令:
mvn clean package 

  • 以下是客户端输出(请注意,在 SOAP 信封的标头中突出显示了带有摘要密码标记的 UsernameToken):
Sent request .....
[<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:UsernameToken ...>
<wsse:Username>admin</wsse:Username>
<wsse:Password #PasswordDigest">
VstlXUXOwyKCIxYh29bNWaSKsRI=
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
....
<tns:placeOrderRequest ...>
....
</tns:order>
</tns:placeOrderRequest>
... Received response ....
<tns:placeOrderResponse ...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
... 

工作原理...

Liverestaurant_R-8.1项目是一个服务器端 Web 服务,要求其客户端发送包含用户名和密码的 SOAP 信封。

Liverestaurant_R-8.1-Client项目是一个客户端测试项目,用于向服务器发送包含用户名令牌和密码的 SOAP 信封。

在服务器端,Wss4jSecurityInterceptor强制服务器对所有传入消息进行用户名令牌验证:

<sws:interceptors>
....
<bean id="wss4jSecurityInterceptor" class="org. springframework. ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name= "validationCallbackHandler" ref="callbackHandler" />
<property name="validationActions" value="UsernameToken" />
</bean>
</sws:interceptors>

拦截器使用validationCallbackHandlerSimplePasswordValidationCallbackHandler)来比较传入消息的用户名/密码与包含的用户名/密码(admin/password)。

<bean id="callbackHandler" class="org.springframework.aws.soap. security.wss4j.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="admin">password</prop>
</props> 
</property>
</bean>

在客户端上,wss4jSecurityInterceptor在所有传出消息中包含用户名(admin/password)令牌:

<bean id="wss4jSecurityInterceptor" class="org.springframework.ws. soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken" /> 
<property name="securementUsername" value="admin" />
<property name="securementPassword" value="password" />
<property name="securementPasswordType" value="PasswordText" /> 
</bean>

在这种情况下,使用纯文本用户名令牌进行身份验证,因为客户端在进行中的消息中包含了纯文本密码(<property name="securementPasswordType" value="PasswordText"/>):

<wsse:UsernameToke......>
<wsse:Username>admin</wsse:Username>
<wsse:Password ...#PasswordText">password</wsse:Password>
</wsse:UsernameToken> 

然而,在第二种情况下,使用摘要用户名令牌进行身份验证,因为密码摘要(<property name="securementPasswordType" value="PasswordDigest">)包含在用户名令牌中:

<wsse:UsernameToken...>
<wsse:Username>admin</wsse:Username>
<wsse:Password ...#PasswordDigest">
VstlXUXOwyKCIxYh29bNWaSKsRI=
</wsse:Password>
...
</wsse:UsernameToken> 

在这种情况下,服务器将传入的 SOAP 消息摘要密码与spring-ws-servlet.xml中设置的计算摘要密码进行比较。通过这种方式,与密码以纯文本形式传输的第一种情况相比,通信将更加安全。

另请参阅...

在这一章中:

  • 使用 Spring 安全性进行 Web 服务调用,对具有纯文本/摘要密码的用户名令牌进行身份验证

  • 使用 X509 证书进行 Web 服务调用的身份验证

使用 Spring 安全性进行 Web 服务调用的身份验证,以验证具有纯文本/摘要密码的用户名令牌

在这里,我们使用用户名令牌进行身份验证,密码为摘要/纯文本,就像本章的第一个示例中所做的那样。这里唯一的区别是使用 Spring 安全框架进行身份验证(SpringPlainTextPasswordValidationCallbackHandler 和SpringDigestPasswordValidationCallbackHandler)。由于 Spring 安全框架超出了本书的范围,因此这里不进行描述。但是,您可以在以下网站的Spring 安全参考文档中了解更多信息:www.springsource.org/security

就像本章的第一个示例一样,这个示例也包含两种情况。在第一种情况下,密码将以纯文本格式传输。在第二种情况下,通过更改配置,密码将以摘要格式传输。

准备工作

在这个示例中,我们有以下两个项目:

  1. LiveRestaurant_R-8.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-8.2-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

按照以下步骤实现 Web 服务调用的身份验证,使用 Spring 安全性对具有纯文本密码的用户名令牌进行身份验证:

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为UsernameToken,并在此拦截器中配置validationCallbackHandler(....wss4j.callback.SpringPlainTextPasswordValidationCallbackHandler)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将安全操作设置为UsernameToken,并设置用户名、密码和密码类型(这里是文本格式)。

  3. Liverestaurant_R-8.2上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.2-Client上运行以下命令:
mvn clean package 

  • 这是客户端的输出(请注意,在 SOAP 的头部中突出显示的具有摘要密码标记的 UsernameToken):
Sent request .....
<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:UsernameToken ...>
<wsse:Username>admin</wsse:Username>
<wsse:Password #PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
....
<tns:placeOrderRequest ...>
....
</tns:order>
</tns:placeOrderRequest>
... Received response ....
<tns:placeOrderResponse ...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
.... 

按照以下步骤实现使用 Spring 安全性进行 Web 服务调用的身份验证,以验证具有摘要密码的用户名令牌:

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中修改Wss4jSecurityInterceptor并在此拦截器中配置validationCallbackHandler....ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler)。

  2. 在客户端应用程序上下文(applicationContext.xml)中修改Wss4jSecurityInterceptor以设置密码类型(这里是摘要格式)。

  3. Liverestaurant_R-8.2上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.2-Client上运行以下命令:
mvn clean package 

  • 以下是客户端的输出(请注意 SOAP 信封的标头中突出显示的带有摘要密码标签的 UsernameToken):
Sent request .....
[<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:UsernameToken ...>
<wsse:Username>admin</wsse:Username>
<wsse:Password #PasswordDigest">
VstlXUXOwyKCIxYh29bNWaSKsRI=</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
....
<tns:placeOrderRequest ...>
....
</tns:order>
</tns:placeOrderRequest>
... Received response ....
<tns:placeOrderResponse ...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
... 

它是如何工作的...

Liverestaurant_R-8.2项目中,客户端和服务器的安全性几乎与Liverestaurant_R-8.1相同(如本章第一个配方所示),只是在服务器端验证用户名令牌。Spring 安全类负责通过与从 DAO 层获取的数据进行比较来验证用户名和密码(而不是在spring-ws-servlet.xml中硬编码用户名/密码)。此外,可以从 DAO 层获取其他与成功验证用户相关的数据,并返回以进行授权以检查一些帐户数据。

在第一种情况下,CallbackHandler SpringPlainTextPasswordValidationCallbackHandler使用authenticationManager,该管理器使用DaoAuthenticationProvider

<bean id="springSecurityHandler" class="org.springframework.ws.soap.security. wss4j.callback.SpringPlainTextPasswordValidationCallbackHandler">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="authenticationManager" class= "org.springframework.security.authentication.ProviderManager">
<property name="providers">
<bean class="org.springframework. security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>

此提供程序调用自定义用户信息服务(MyUserDetailService.java),该服务从提供程序获取用户名并在内部从 DAO 层获取该用户的所有信息(例如密码、角色、是否过期等)。最终,该服务以UserDetails类型类(MyUserDetails.java)返回填充的数据。现在,如果UserDetails数据与传入消息的用户名/密码匹配,则返回响应;否则,返回 SOAP 故障消息:

public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
return getUserDataFromDao(username);
}
private MyUserDetail getUserDataFromDao(String username) {
/**
*Real scenario: find user data from a DAO layer by userName,
* if this user name found, populate MyUserDetail with its data(username, password,Role, ....).
*/
MyUserDetail mydetail=new MyUserDetail( username,"pass",true,true,true,true);
mydetail.getAuthorities().add( new GrantedAuthorityImpl("ROLE_GENERAL_OPERATOR"));
return mydetail;
}

然而,在第二种情况下,CallbackHandlerSpringDigestPasswordValidationCallbackHandler,它将 SOAP 传入消息中包含的摘要密码与从 DAO 层获取的摘要密码进行比较(请注意,DAO 层可以从不同的数据源获取数据,如数据库、LDAP、XML 文件等):

<bean id="springSecurityHandler" class="org.springframework.ws.soap.security.wss4j.callback. SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>

与本章第一个配方相同,在客户端应用程序上下文中将<property name="securementPasswordType" value="PasswordText">修改为PasswordDigest会导致密码以摘要格式传输。

另请参阅...

在本章中:

  • 使用用户名令牌进行 Web 服务调用的身份验证,使用明文/摘要密码

  • 使用 X509 证书对 Web 服务调用进行身份验证

使用数字签名保护 SOAP 消息

在安全术语中,签名的目的是验证接收到的消息是否被篡改。签名在 WS-Security 中扮演着两个主要任务,即对消息进行签名和验证签名。消息签名涉及的所有概念都在上一章的使用数字签名保护 SOAP 消息中详细介绍。在这个配方中,使用 WSS4J 进行签名和验证签名。

Spring-WS 的Wss4jSecurityInterceptor能够根据 WS-Security 标准进行签名和验证签名。

将此拦截器的securementActions属性设置为Signature会导致发送方对传出消息进行签名。要加密签名令牌,需要发送方的私钥。需要在应用程序上下文文件中配置密钥库的属性。securementUsernamesecurementPassword属性指定了用于使用的密钥库中的私钥的别名和密码。securementSignatureCrypto应指定包含私钥的密钥库。

validationActions设置为value="Signature"会导致消息的接收方期望并验证传入消息的签名(如开头所述)。validationSignatureCrypto bean 应指定包含发送方公钥证书(受信任证书)的密钥库。

来自wss4j包的org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean可以提取密钥库数据(例如证书和其他密钥库信息),并且这些数据可以用于身份验证。

在本教程中,客户端存储的私钥用于加密消息的客户端签名。在服务器端,包含在服务器密钥库中的客户端公钥证书(在受信任证书条目中)将用于解密消息签名令牌。然后服务器对签名进行验证(如开头所述)。在[第七章中使用的密钥库,在准备配对和对称密钥库中使用。

准备工作

在本教程中,我们有以下两个项目:

  1. LiveRestaurant_R-8.3(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-8.3-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为Signature,并在此拦截器中将validationSignatureCrypto属性设置为CryptoFactoryBean(配置服务器端密钥库位置及其密码)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将安全操作设置为Signature,并在此拦截器中将securementSignatureCrypto属性设置为CryptoFactoryBean(配置客户端密钥库位置及其密码)。

  3. Liverestaurant_R-8.3上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.3-Client上运行以下命令:
mvn clean package 

  • 以下是客户端的输出(请注意突出显示的文本):
Sent request ....
<SOAP-ENV:Header>
<wsse:Security...>
<ds:Signature ...>
<ds:SignedInfo>
.....
</ds:SignedInfo>
<ds:SignatureValue>
IYSEHmk+.....
</ds:SignatureValue>
<ds:KeyInfo ..>
<wsse:SecurityTokenReference ...>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>
CN=MyFirstName MyLastName,OU=Software,O=MyCompany,L=MyCity,ST=MyProvince,C=ME
</ds:X509IssuerName>
<ds:X509SerialNumber>1311686430</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body ...>
<tns:placeOrderRequest ...>
.....
</tns:order>
</tns:placeOrderRequest>
.. Received response
.....<tns:placeOrderResponse....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse> 

它是如何工作的...

服务器端的安全配置要求客户端在消息中包含一个二进制签名令牌。客户端配置文件中的设置将签名令牌包含在传出消息中。客户端使用自己的私钥(包含在客户端密钥库中)对消息的签名进行加密(根据消息的内容计算)。在服务器端,来自服务器端的客户端证书(受信任证书)密钥库用于解密签名令牌。然后将对二进制签名令牌的签名验证(如本章开头所述)进行验证。

在服务器端将validationActions设置为Signature会导致它期望来自客户端配置的签名,并且设置密钥库会导致服务器端密钥库中的客户端公钥证书(受信任证书)用于解密签名。然后服务器对签名进行验证:

<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint. interceptor.PayloadValidatingInterceptor">
<property name="schema" value="/WEB-INF/orderService.xsd" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
<bean class="org.springframework.ws.soap.server.endpoint. interceptor.SoapEnvelopeLoggingInterceptor"/>
<bean id="wsSecurityInterceptor" class="org.springframework.ws. soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Signature" />
<property name="validationSignatureCrypto">
<bean class="org.springframework.ws.soap.security. wss4j.support.CryptoFactoryBean">
<property name="key storePassword" value="serverPassword" />
<property name="key storeLocation" value="/WEB-INF/serverStore.jks" />
</bean>
</property>
</bean>
</sws:interceptors>

代码语句<property name="securementActions" value="Signature" />,并在客户端配置中设置密钥库会导致客户端发送加密签名(使用别名为client的客户端私钥,并且客户端加密从消息生成的哈希(签名)),并随消息一起发送:

<bean id="wss4jSecurityInterceptor" class="org.springframework.ws. soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Signature" />
<property name="securementUsername" value="client" />
<property name="securementPassword" value="cliPkPassword" />
<property name="securementSignatureCrypto">
<bean class="org.springframework.ws.soap.security. wss4j.support.CryptoFactoryBean">
<property name="key storePassword" value="clientPassword" />
<property name="key storeLocation" value="classpath:/clientStore.jks" />
</bean>
</property>
</bean>

另请参阅...

在本章中:

  • 使用 X509 证书对 Web 服务调用进行身份验证

第七章,使用 XWSS 库保护 SOAP Web 服务:

  • 准备配对和对称密钥存储

使用 X509 证书对 Web 服务调用进行身份验证

在本章的前面部分,介绍了如何使用用户名令牌对传入消息进行身份验证。随传入消息一起传来的客户端证书可以用作替代用户名令牌进行身份验证。

为了确保所有传入的 SOAP 消息携带客户端的证书,发送方的配置文件应该签名,接收方应该要求所有消息都有签名。换句话说,客户端应该对消息进行签名,并在传出消息中包含 X509 证书,服务器首先将传入的证书与信任的证书进行比较,该证书嵌入在服务器密钥库中,然后进行验证传入消息的签名。

准备工作

在此配方中,我们有以下两个项目:

  1. LiveRestaurant_R-8.4(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-8.4-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为Signature,并在此拦截器中将属性validationSignatureCrypto设置为CryptoFactoryBean(配置服务器端密钥库位置及其密码)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将安全操作设置为Signature,设置一个属性(securementSignatureKeyIdentifier)以包含二进制X509令牌,并在此拦截器中将属性securementSignatureCrypto设置为CryptoFactoryBean(配置客户端密钥库位置及其密码)。

以下是客户端的输出(请注意突出显示的文本):

Sent request ....
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:BinarySecurityToken....wss-x509-token-profile- 1.0#X509v3" ...>
MIICbTCCAdagAwIBAgIETi6/HjANBgkqhki...
</wsse:BinarySecurityToken>
<ds:Signature ....>
.....
....
</ds:Signature>.... 

工作原理...

签名和验证签名与本章中使用数字签名保护 SOAP 消息的配方相同。不同之处在于配置的以下部分,用于生成包含 X509 证书的BinarySecurityToken元素,并在发送方的传出消息中包含它:

<property name="securementSignatureKeyIdentifier" value="DirectReference" />

在签名消息时将客户端证书嵌入调用者消息中,使服务器验证该证书与密钥库中包含的证书(受信任的证书条目)一致。此验证确认了调用者是否是他/她声称的人。

另请参阅...

在本章中:

  • 使用数字签名保护 Soap 消息

第七章,使用 XWSS 库保护 SOAP Web 服务:

  • 准备配对和对称密钥存储

加密/解密 SOAP 消息

SOAP 消息的加密和解密概念与第七章中描述的加密/解密 SOAP 消息相同。Spring-WS 的Wss4jSecurityInterceptor通过在接收方(这里是服务器端)设置属性validationActionsEncrypt来提供对传入 SOAP 消息的解密。在发送方(这里是客户端)设置属性securementActions会导致发送方对传出消息进行加密。

Wss4jSecurityInterceptor需要访问密钥库进行加密/解密。在使用对称密钥的情况下,Key storeCallbackHandler负责访问(通过设置locationpassword属性)并从对称密钥库中读取,并将其传递给拦截器。然而,在使用私钥/公钥对存储的情况下,CryptoFactoryBean将执行相同的工作。

在这个示例中,在第一种情况下,客户端和服务器共享的对称密钥用于客户端的加密和服务器端的解密。然后,在第二种情况下,客户端密钥库中的服务器公钥证书(受信任的证书)用于数据加密,服务器端密钥库中的服务器私钥用于解密。

在前两种情况下,整个有效载荷用于加密/解密。通过设置一个属性,可以对有效载荷的一部分进行加密/解密。在第三种情况下,只有有效载荷的一部分被设置为加密/解密的目标。

准备工作

在这个示例中,我们有以下两个项目:

  1. LiveRestaurant_R-8.5(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-8.5-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-ws-security-2.0.1.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • spring-expression-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

操作步骤...

按照以下步骤使用对称密钥实施加密/解密:

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为Encrypt,并在此拦截器内配置Key storeCallbackHandler以从对称密钥库中读取(配置服务器端对称密钥库位置及其密码)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将安全操作设置为Encrypt,并配置Key storeCallbackHandler以从对称密钥库中读取(配置客户端对称密钥库位置及其密码)。

  3. Liverestaurant_R-8.5上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.5-Client上运行以下命令:
mvn clean package 

  • 以下是客户端的输出(请注意突出显示的文本):
Sent request...
<SOAP-ENV:Header>
<wsse:Security...>
<xenc:ReferenceList><xenc:DataReference../> </xenc:ReferenceList>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<xenc:EncryptedData ...>
<xenc:EncryptionMethod..tripledes-cbc"/>
<ds:KeyInfo...>
<ds:KeyName>symmetric</ds:KeyName>
</ds:KeyInfo>
<xenc:CipherData><xenc:CipherValue>
3a2tx9zTnVTKl7E+Q6wm...
</xenc:CipherValue></xenc:CipherData>
</xenc:EncryptedData>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> 

按照以下步骤在客户端密钥库(在clientStore.jsk中)上使用服务器信任的证书实施加密,并在服务器端私钥(在serverStore.jks中)上进行解密:

  1. 在服务器端应用程序上下文(spring-ws-servlet.xml)中注册Wss4jSecurityInterceptor,将验证操作设置为Encrypt,并在此拦截器内将属性validationSignatureCrypto设置为CryptoFactoryBean(配置服务器端密钥库位置及其密码)。

  2. 在客户端应用程序上下文(applicationContext.xml)中注册Wss4jSecurityInterceptor,将安全操作设置为Encrypt,并在此拦截器内将securementSignatureCrypto设置为CryptoFactoryBean(配置客户端密钥库位置及其密码)。

以下是服务器端的输出(请注意突出显示的文本):

<SOAP-ENV:Header>
<wsse:Security...>
<xenc:EncryptionMethod ..">
<wsse:SecurityTokenReference ...>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>
CN=MyFirstName MyLastName,OU=Software,O=MyCompany, L=MyCity,ST=MyProvince,C=ME
</ds:X509IssuerName>
<ds:X509SerialNumber>1311685900</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>dn0lokNhtmZ9...</xenc:CipherValue>
</xenc:CipherData><xenc:ReferenceList>
....
</wsse:Security>
</SOAP-ENV:Header><SOAP-ENV:Body>
<xenc:EncryptedData .../>
<ds:KeyInfo ...xmldsig#">
<wsse:SecurityTokenReference ...>
<wsse:Reference .../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData><xenc:CipherValue>
UDO872y+r....</xenc:CipherValue>
</xenc:CipherData></xenc:EncryptedData>
</SOAP-ENV:Body> 

按照以下步骤在有效载荷上实施加密/解密:

  1. 修改第 2 种情况,将Wss4jSecurityInterceptor上的securementEncryptionParts属性设置为有效载荷的特定部分,无论是在服务器端还是客户端。

  2. Liverestaurant_R-8.5上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-8.5-Client上运行以下命令:
mvn clean package 

  • 以下是客户端的输出(请注意突出显示的文本):
..........
<SOAP-ENV:Body>
<tns:placeOrderRequest...>
<xenc:EncryptedData...>
<xenc:EncryptionMethod .../>
<ds:KeyInfo..xmldsig#">
<wsse:SecurityTokenReference ...>
<wsse:Reference.../></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData>
<xenc:CipherValue>
pGzc3/j5GX......
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</tns:placeOrderRequest>
....... 

工作原理...

在第一种情况下,客户端和服务器都共享对称密钥。客户端使用对称密钥加密整个有效载荷,并将其发送到服务器。在服务器端,相同的密钥将用于解密有效载荷。

然而,在第二和第三种情况下,客户端存储中嵌入的服务器证书用于加密有效负载,在服务器端,服务器存储的私钥将用于解密。第二种和第三种情况之间的区别在于第二种情况加密/解密整个有效负载,但在第三种情况下,只有部分有效负载将成为加密/解密的目标。

在第一种情况下,在服务器端将validationActions设置为Encrypt会导致服务器使用对称密钥解密传入消息。拦截器使用ValidationCallbackHandler进行解密,使用在location属性中设置的对称密钥存储。type属性设置密钥的存储类型,password设置对称密钥的密钥存储密码:

<bean class="org.springframework.ws.soap. security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Encrypt"/>
<property name="validationCallbackHandler">
<bean class="org.springframework.ws.soap.security. wss4j.callback.Key storeCallbackHandler">
<property name="key store">
<bean class="org.springframework.ws.soap.security. support.Key storeFactoryBean">
<property name="location" value="/WEB- INF/symmetricStore.jks"/>
<property name="type" value="JCEKS"/>
<property name="password" value="symmetricPassword"/>
</bean>
</property>
<property name="symmetricKeyPassword" value="keyPassword"/>
</bean>
</property>
</bean>

在客户端,将securementActions属性设置为Encrypt会导致客户端加密所有传出消息。通过将securementEncryptionKeyIdentifier设置为EmbeddedKeyName来自定义加密。选择EmbeddedKeyName类型时,加密的秘钥是必需的。对称密钥别名(此处为对称)由securementEncryptionUser设置。

默认情况下,SOAP 标头中的ds:KeyName元素采用securementEncryptionUser属性的值。securementEncryptionEmbeddedKeyName可用于指示不同的值。securementEncryptionKeyTransportAlgorithm属性定义要使用的算法来加密生成的对称密钥。securementCallbackHandler提供了Key storeCallbackHandler,指向适当的密钥存储,即服务器端配置中描述的对称密钥存储:

<bean class="org.springframework.ws.soap. security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Encrypt" />
<property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/>
<property name="securementEncryptionUser" value="symmetric"/>
<property name="securementEncryptionEmbeddedKeyName" value="symmetric"/>
<property name="SecurementEncryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<property name="securementCallbackHandler">
<bean class="org.springframework.ws.soap.security. wss4j.callback.Key storeCallbackHandler">
<property name="symmetricKeyPassword" value="keyPassword"/>
<property name="key store">
<bean class="org.springframework.ws.soap.security. support.Key storeFactoryBean">
<property name="location" value="/symmetricStore.jks"/>
<property name="type" value="JCEKS"/>
<property name="password" value="symmetricPassword"/>
</bean>
</property>
</bean>
</property>
</bean>

在第二和第三种情况下,服务器端配置的validationDecryptionCrypto几乎与第一种情况解密数据的方式相同:

<bean class="org.springframework.ws.soap.security. wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Encrypt" />
<property name="validationDecryptionCrypto">
<bean class="org.springframework.ws.soap.security. wss4j.support.CryptoFactoryBean">
<property name="key storePassword" value="serverPassword" />
<property name="key storeLocation" value="/WEB- INF/serverStore.jks" />
</bean>
</property>
<property name="validationCallbackHandler">
<bean class="org.springframework.ws.soap.security. wss4j.callback.Key storeCallbackHandler">
<property name="privateKeyPassword" value="serPkPassword" />
</bean>
</property>
</bean>

在客户端,将securementActionsvalue="Encrypt"设置为会导致客户端加密所有传出消息。securementEncryptionCrypto用于设置密钥存储位置和密码。SecurementEncryptionUser用于设置服务器证书在客户端密钥存储中的别名:

<bean class="org.springframework.ws.soap.security. wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Encrypt" />
<property name="securementEncryptionUser" value="server" />
<property name="securementEncryptionCrypto">
<bean class="org.springframework.ws.soap.security. wss4j.support.CryptoFactoryBean">
<property name="key storePassword" value="clientPassword" />
<property name="key storeLocation" value="/clientStore.jks" />
</bean>
</property>
</bean>

第 2 种第 3 种之间的区别在于在客户端/服务器端配置中的配置设置仅导致部分有效负载被加密/解密。

---client/server configuration file
<property name="securementEncryptionParts"value="{Content} {http://www.packtpub.com/LiveRestaurant/OrderService/schema} placeOrderRequest"/>

另请参阅...

在本章中:

  • 使用数字签名保护 SOAP 消息

第二章,为 SOAP Web 服务构建客户端

  • 为 WS-Addressing 端点创建 Web 服务客户端

第七章,使用 XWSS 库保护 SOAP Web 服务

  • 准备一对和对称密钥存储*

第九章:RESTful Web 服务

在本章中,我们将涵盖:

  • 使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web 服务

  • 使用RESTClient工具访问 Spring RESTful Web 服务

  • 使用 HTTP 消息转换设置 Spring RESTful Web 服务

  • 为 Spring RESTful Web 服务创建 WS 客户端,使用 Spring 模板类

介绍

简单对象访问协议(SOAP)允许应用程序使用 XML 作为通信格式进行通信(SOAP 很容易理解),但由于它是基于 XML 的,即使对于非常简单的 Web 服务场景,它也往往冗长。

表述性状态转移(REST),由 Roy Fielding 于 2000 年发表的博士论文,旨在简化 Web 服务的使用。

SOAP 使用大量 XML(看起来非常复杂)进行通信,而 REST 使用非常轻量级和易读的数据(例如,请求 URIlocalhost:8080/LiveRestaurant/customerAccount/234返回123-3456)。将此简单请求和响应与 SOAP 请求/响应信封进行比较,这些信封已经在本书的前几章中介绍过。由于 REST Web 服务实现非常灵活且非常简单,因此不需要工具包。但是,基于 SOAP 的 Web 服务需要工具来简化(例如,要调用 SOAP Web 服务,您将使用工具为合同后的 Web 服务类生成客户端代理类,或者使用工具从合同优先的 Web 服务中生成域类)。在前几章中,您将意识到合同优先的 Web 服务有多么严格(它必须与合同匹配)。REST Web 服务的请求/响应格式完全由开发人员决定,并且可以尽可能轻松地设计。在使用 SOAP Web 服务时,使用 JavaScript 并不容易(需要大量代码)。使用 AJAX 技术和 JSON 格式简化了 REST 的使用。

以下是 REST 的一些缺点:REST 仅适用于 HTTP;调用 RESTful Web 服务受到 HTTP 动词的限制:GET、POST、PUT 和 DELETE。

RESTful 是建立在 REST 原则之上的,其中使用 HTTP 的方法基于其概念。例如,HTTP 的GET、POST、PUTDELETE都在 RESTful 架构中使用,与 HTTP 的含义相匹配。

RESTful Web 服务公开其资源的状态。在本章中,例如,RESTful 服务公开了获取在线餐厅中可用订单项目列表和订单对象的服务。要获取可用订单项目列表,使用GET方法,而要下订单,则使用POST方法。PUT方法可用于添加/更新条目,DELETE方法可用于删除条目。

以下是用于进行 RESTful Web 服务调用并获取可用订单项目列表的示例 URL:

localhost:8080/LiveRestaurant/orderItems

以下是返回响应(响应格式不一定是 XML 格式;它可以是 JSON、纯文本或任何格式):

<list>
<orderItem>
<name>Burger</name>
<id>0</id>
</orderItem>
<orderItem>
<name>Pizza</name>
<id>1</id>
</orderItem>
<orderItem>
<name>Sushi</name><id>2</id>
</orderItem>
<orderItem>
<name>Salad</name>
<id>3</id>
</orderItem>
</list> 

RESTful Web 服务有几种实现,例如Restlet、RestEasyJersey。其中,Jersey 是这一组中最重要的实现,是 JAX-RS(JSR 311)的实现。

Spring 作为 Java EE 广泛使用的框架,在 3.0 版本中引入了对 RESTful Web 服务的支持。RESTful 已经集成到 Spring 的 MVC 层中,允许应用程序使用 RESTful 功能构建 Spring。其中最重要的功能包括:

  • 注释,例如@RequestMapping@PathVariable,用于 URI 映射和传递参数。

  • ContentNegotiatingViewResolver,允许使用不同的 MIME 类型(如text/xml、text/jsontext/plain

  • HttpMessageConverter允许基于客户端请求(如 ATOM、XML 和 JSON)生成多种表示。

使用 Spring MVC 中的 RESTful 特性设置 Spring RESTful Web 服务。

Spring 3.0 支持基于 Spring MVC 的 RESTful Web 服务。Spring 使用注解来设置 RESTful Web 服务,并需要在 Spring 应用程序上下文文件中进行配置以扫描注解。需要一个 Spring MVC 控制器来设置 RESTful Web 服务。@Controller注解标记一个类为 MVC 控制器。@RequestMapping注解将传入的请求映射到控制器类中的适当 Java 方法。使用这个注解,你可以定义 URI 和 HTTP 方法,这些方法映射到 Java 类方法。例如,在下面的例子中,如果请求 URI 后跟着/orderItems,那么方法loadOrderItems将被调用,@PathVariable用于将请求参数({cayegoryId})的值注入到方法参数中(String cayegoryId):

@RequestMapping( value="/orderItem/{cayegoryId}", method=RequestMethod.GET )
public ModelAndView loadOrderItems(@PathVariable String cayegoryId)
{...}

在这个示例中,介绍了使用 Spring 3 MVC 实现 RESTful Web 服务。这个 Web 服务的客户端项目在这里实现了,但将在本章的最后一个示例中详细介绍:使用 Spring 模板类为 Spring RESTful Web 服务创建 WS 客户端

准备工作

在这个示例中,项目的名称是LiveRestaurant_R-9.1LiveRestaurant_R-9.1-Client项目包含在代码中用于测试目的),具有以下 Maven 依赖项:

  • com.springsource.javax.servlet-2.5.0.jar

  • spring-oxm-3.0.5.RELEASE.jar

  • spring-web-3.0.5.RELEASE.jar

  • spring-webmvc-3.0.5.RELEASE.jar

  • xstream-1.3.1.jar

  • commons-logging-1.1.1.jar

spring-oxm是 Spring 对对象/XML 映射的支持,spring-webspring-webmvc是对 Seb 和 MVC 支持的支持,xstream是用于对象/XML 映射框架的支持。

如何做...

  1. web.xml文件中配置MessageDispatcherServlet(URL:http://://*将被转发到此 servlet)。

  2. 定义控制器文件(OrderController.java)。

  3. 定义领域 POJO(Order.java,OrderItem.java)和服务(OrderService, OrderServiceImpl)。

  4. 配置服务器端应用程序上下文文件(order-servlet.xml)。

  5. Liverestaurant_R-9.1上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-9.1-Client上运行以下命令:
mvn clean package 

  • 这是客户端输出:
.... Created POST request for "http://localhost:8080/LiveRestaurant/order/1"
.....Setting request Accept header to [application/xml, text/xml, application/*+xml]
.... POST request for "http://localhost:8080/LiveRestaurant/order/1" resulted in 200 (OK)
.....Reading [com.packtpub.liverestaurant.domain.Order] as "application/xml;charset=ISO-8859-1"
.....
.....Created GET request for "http://localhost:8080/LiveRestaurant/orderItems"
.....Setting request Accept header to [application/xml, text/xml, application/*+xml]
.....GET request for "http://localhost:8080/LiveRestaurant/orderItems" resulted in 200 (OK) 

  1. 浏览到此链接:localhost:8080/LiveRestaurant/orderItems,您将得到以下响应:
<list>
<orderItem>
<name>Burger</name>
<id>0</id>
</orderItem>
<orderItem>
<name>Pizza</name>
<id>1</id>
</orderItem>
<orderItem>
<name>Sushi</name><id>2</id>
</orderItem>
<orderItem>
<name>Salad</name>
<id>3</id>
</orderItem>
</list> 

它是如何工作的...

该应用程序是一个 MVC Web 项目,其中一个控制器返回 Spring 的ModelView对象。Spring 的MarshallingView将模型对象编组成 XML,使用marshallerXStreamMarshaller),并将 XML 发送回客户端。

所有请求将到达DispatcherServlet,它将被转发到控制器OrderController,根据请求 URI,将调用适当的方法返回响应给调用者。web.xml中的以下配置将所有请求转发到DispatcherServlet

<servlet>
<servlet-name>order</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>order</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

order-context.xml中的以下设置导致 Spring 检测包中的所有注解(包括OrderServiceOrderController)。BeanNameViewResolver用于将名称(OrderController中的orderXmlView)映射到视图(orderXmlView bean),它是org.springframework.web.servlet.view.xml.MarshallingView的实例:

<context:component-scan base-package= "com.packtpub.liverestaurant.orderservice" />
<bean class= "org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="orderXmlView" class= "org.springframework.web.servlet.view.xml.MarshallingView">
...
</bean>

@Controller标记OrderController类为 MVC 模式中的控制器。所有调用请求将被转发到该类,并根据请求 URI,将调用适当的方法。例如,如果来自调用者请求的 HTTP POST方法的 URI 类似于http://<host>:<port>/<appcontext>/order/1,则将调用placeOrder方法。

@RequestMapping(value = "/order/{orderId}", method = RequestMethod.POST)
public ModelAndView placeOrder(@PathVariable String orderId) {..}

@PathVariable导致从 URI 中注入并传递给placeOrder方法的orderId参数。

方法的主体placeOrder调用OrderService接口的方法并返回Order对象:

Order order = orderService.placeOrder(orderId);
ModelAndView mav = new ModelAndView("orderXmlView", BindingResult.MODEL_KEY_PREFIX + "order", order);
return mav;

然后,它基于将Order对象编组成 XML 格式来构建视图,使用Marshallingview bean(MVC 中的视图使用XStreamMarshaller将模型对象编组成 XML 格式),并将其返回给服务的调用者。

<bean id="orderXmlView" class= "org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="autodetectAnnotations" value="true"/>
</bean>
</constructor-arg>
</bean>

loadOrderItems方法的工作方式相同,只是 URI 应该类似于以下模式:http://<host>:<port>/<appcontext>/orderItems,使用 HTTP GET

@RequestMapping(value = "/orderItems", method = RequestMethod.GET)
public ModelAndView loadOrderItems() {
List<OrderItem> orderItems = orderService.listOrderItems();
ModelAndView modelAndView = new ModelAndView("orderXmlView", BindingResult.MODEL_KEY_PREFIX + "orderItem", orderItems);
return modelAndView;
}

在本教程中,数据库活动未实现。但是,在实际应用中,可以使用 HTTP 方法DELETE从数据库中删除实体(例如orderItem),并且可以使用PUT方法更新记录(例如order)。

另请参阅...

在本书中:

第六章,编组和对象-XML 映射(OXM)

使用 XStream 进行编组

使用 REST 客户端工具访问 Spring RESTful Web-Service

REST Client是一个用于调用和测试 RESTful Web-Services 的应用程序。REST Client 作为 Firefox/Flock 附加组件提供。Firefox REST Client 支持所有 HTTP 方法,RFC2616(HTTP/1.1)RFC2518(WebDAV)。使用此附加组件,您可以构建自己定制的 URI,添加标头,将其发送到 RESTful Web-Services,并获取响应。

在本教程中,我们将学习如何使用 Firefox REST Client 测试 RESTful Web-Service 的呈现方式。本教程使用本章的第一个教程,使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web-Service,作为 RESTful Web-Services。

准备工作

下载并安装 Firefox 的以下附加组件:

addons.mozilla.org/en-US/firefox/addon/restclient/

如何做...

  1. 从本章运行LiveRestaurant_R-9.1

  2. 打开 Firefox 浏览器,转到工具 | Rest Client

  3. 方法更改为GET,并输入 URL:localhost:8080/LiveRestaurant/orderItems,然后单击发送

这是结果:

如何做...

  1. 方法更改为POST,输入 URL:localhost:8080/LiveRestaurant/order/1,然后单击发送

如何做...

另请参阅...

在本章中:

使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web-Service

使用 HTTP 消息转换设置 Spring RESTful Web-Service

HTTP 协议上的客户端和服务器使用文本格式交换数据。最终,需要接受不同的请求格式,并将文本格式转换为有意义的格式,例如对象或 JSON 格式。Spring 提供了提供从相同文本格式到多个请求/演示的功能。

Spring 3 引入了ContentNegotiatingViewResolver,它可以从相同的 URI 选择各种视图,并提供多个演示。

执行相同任务的另一种方法是使用HttpMessageConverter接口和@ResponseBody注解。Spring 中HttpMessageConverter接口的实现将 HTTP 消息转换为多种格式。其广泛使用的实现包括:

  • StringHttpMessageConverter实现从 HTTP 请求/响应中读取/写入文本。这是默认转换器。

  • MarshallingHttpMessageConverter实现从文本 HTTP 请求/响应中编组/解组对象。它获取构造函数参数以指定编组器的类型(如Jaxb,XStream等)。

  • MappingJacksonHttpMessageConverter实现将文本转换为 JSON 数据格式,反之亦然。

在本示例中,使用MarshallingHttpMessageConverter, MappingJacksonHttpMessageConverterAtomFeedHttpMessageConverter进行消息转换。由于此项目类似于本章的第一个示例,使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web 服务,因此它被重用作项目的模板。本示例的区别在于控制器实现和应用程序上下文配置。

这个 Web 服务的客户端项目在这里实现,但将在本章的最后一个示例中详细介绍,使用 Spring 模板类为 Spring RESTful Web 服务创建 WS 客户端

准备工作

在本示例中,项目名称为LiveRestaurant_R-9.2(LiveRestaurant_R-9.2-Client在本示例中包含在代码中以进行测试。但是,它将在最后一个示例中解释),并且具有以下 Maven 依赖项:

  • com.springsource.javax.servlet-2.5.0.jar

  • spring-oxm-3.0.5.RELEASE.jar

  • spring-web-3.0.5.RELEASE.jar

  • spring-webmvc-3.0.5.RELEASE.jar

  • xstream-1.3.1.jar

  • commons-logging-1.1.1.jar

  • jackson-core-asl-1.7.5.jar

  • jackson-mapper-asl-1.7.5.jar

  • rome-1.0.jar

jackson-corejackson-mapper支持 JSON 格式,其他支持 ATOM 格式。

操作步骤...

  1. web.xml文件中配置DispatcherServlet(URL:http://:/`/*将被转发到此 servlet)。

  2. 定义控制器文件(OrderController.java)。

  3. 定义领域 POJOs(Order.java,OrderItem.java)和服务(OrderService, OrderServiceImpl

  4. 配置服务器端应用程序上下文文件(order-servlet.xml)并注册转换器。

  5. 方法更改为POST,并添加请求头:名称 - accept - application/json。输入 URL localhost:8080/LiveRestaurant/orderJson/1 并点击发送:操作步骤...

  6. 方法更改为GET,并添加请求头:名称 - accept - application/atom+xml。输入 URL localhost:8080/LiveRestaurant/orderItemsFeed 并点击发送:

操作步骤...

工作原理...

这个示例与本章的第一个示例几乎相同,只是它使用了消息转换器和@ResponseBody来提供多个表示。

在第一个示例中,MarshallingView负责将响应转换为所选视图的 XML 类型(使用XstreamMarshaller)。然而,在这里,消息转换器负责将数据模型呈现为所选格式,MarshallingHttpMessageConverter负责将List<OrderItem>转换为application/xml格式(使用XstreamMarshaller),MappingJacksonHttpMessageConverter用于将订单转换为application/json格式。AtomFeedHttpMessageConverter用于将Feed(包装来自List<OrderItem>的 XML 内容,使用XStreamMarshaller生成的)转换为application/atom+xml格式:

<context:component-scan base-package= "com.packtpub.liverestaurant.orderservice" />
<bean id="xStreamMarshaller" class= "org.springframework.oxm.xstream.XStreamMarshaller"/>
<bean class="org.springframework. web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework. web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="marshallingConverter" />
<ref bean="jsonConverter" />
<ref bean="atomConverter" />
</list>
</property>
</bean>
<bean id="marshallingConverter" class="org.springframework. http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg>
<bean class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="autodetectAnnotations" value="true"/>
</bean>
</constructor-arg>
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean id="jsonConverter" class="org.springframework. http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
<bean id="atomConverter"class="org.springframework. http.converter.feed.AtomFeedHttpMessageConverter">
<property name="supportedMediaTypes" value="application/atom+xml" />
</bean>

在控制器中,以下代码导致控制器方法接受请求 URI 方法的POST格式 - json:

@RequestMapping(method=RequestMethod.POST, value="/orderJson/{orderId}", headers="Accept=application/json")
public @ResponseBody Order placeOrderJson(@PathVariable String orderId) {
Order order=orderService.placeOrder(orderId);
return order;
}

并以 JSON 格式返回Order对象(使用@ResponseBodyMappingJacksonHttpMessageConverterorder-context.xml中配置):

{"message":"Order Pizza has been placed","ref":"Ref:1","orderItemId":"1"} 

以下代码导致控制器方法接受请求 URI 方法的GET格式 - atom:

@RequestMapping(method=RequestMethod.GET, value="/orderItemsFeed", headers="Accept=application/atom+xml")
public @ResponseBody Feed loadOrderItemsAtom() {
Feed feed = null;
try {
feed= getOrderItemsFeed(orderService.listOrderItems());
} catch (Exception e) {
throw new RuntimeException(e);
}
return feed;
}

它还以Atom格式返回List<OrderItem>对象(使用@ResponseBodyAtomFeedHttpMessageConverterorder-context.xml中配置):

<?xml version="1.0" encoding="UTF-8"?>
<feed >
<title>OrderItems Atom Feed</title>
<entry>
<title>Burger</title>
<id>0</id>
<content type="xml">
&lt;com.packtpub.liverestaurant.domain.OrderItem&gt;&lt;name&gt; Burger&lt;/name&gt;&lt;id&gt;0&lt;/id&gt;&lt;/com.packtpub. liverestaurant.domain.OrderItem&gt;
</content>
</entry>
<entry>
<title>Pizza</title>
<id>1</id>
<content type="xml">&lt;com.packtpub.liverestaurant.domain. OrderItem&gt;&lt;name&gt;Pizza&lt;/name&gt;&lt;id&gt;1&lt; /id&gt;&lt;/com.packtpub.liverestaurant.domain.OrderItem&gt;
</content>
</entry>
...

另请参阅...

在本章中:

使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web 服务

使用 Spring 模板类为 Spring RESTful Web 服务创建 WS 客户端

Spring 提供了各种模板类,使用不同的技术简化了许多复杂性。例如,WebServiceTemplate用于调用基于 SOAP 的 Web 服务,JmsTemplate用于发送/接收 JMS 消息。Spring 还有RestTemplate来简化与 RESTful Web 服务的交互。

使用RestTemplate:

  • 创建一个RestTemplate的实例(可以使用@Autowired特性来完成)

  • 配置一对多的消息转换器(如前面的示例中所述)

  • 调用RestTemplate的方法来调用 RESTful Web 服务并获取响应

在这个示例中,我们将学习如何使用RestTemplate消耗 RESTful Web 服务。这个示例使用了本章的第三个示例,使用 HTTP 消息转换设置 Spring RESTful Web 服务,作为 RESTful Web 服务。

准备工作

在这个示例中,项目的名称是LiveRestaurant_R-9.2-ClientLiveRestaurant_R-9.2包含在这个示例中,用于设置 RESTful 服务器,如前面的示例使用 HTTP 消息转换设置 Spring RESTful Web 服务中所解释的),具有以下 Maven 依赖项:

  • spring-oxm-3.0.5.RELEASE.jar

  • spring-web-3.0.5.RELEASE.jar

  • xstream-1.3.1.jar

  • commons-logging-1.1.1.jar

  • jackson-core-asl-1.7.5.jar

  • jackson-mapper-asl-1.7.5.jar

  • rome-1.0.jar

  • junit-4.6.jar

  • spring-test-3.0.5.RELEASE.jar

如何做...

  1. 定义领域 POJOs(Order.javaOrderItem.java)和服务(OrderServiceOrderServiceImpl)。

  2. 配置客户端应用程序上下文文件(order-servlet.xml)并注册转换器。

  3. 创建一个辅助类(OrderClient),用RestTemplate来调用 RESTful Web 服务。

  4. Liverestaurant_R-9.2上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-9.2-Client上运行以下命令:
mvn clean package 

  • 以下是客户端输出:
....
.. Created GET request for "http://localhost:8080/LiveRestaurant/orderItems"
.. Setting request Accept header to [application/xml, text/xml, application/*+xml, application/json]
.. GET request for "http://localhost:8080/LiveRestaurant/orderItems" resulted in 200 (OK)
.. Reading [java.util.List] as "application/xml" using ....
.. Created POST request for "http://localhost:8080/LiveRestaurant/orderJson/1"
.. Setting request Accept header to [application/xml, text/xml, application/*+xml, application/json]
.. POST request for "http://localhost:8080/LiveRestaurant/orderJson/1" resulted in 200 (OK)
.. Reading [com.packtpub.liverestaurant.domain.Order] as "application/xml" using ...
...Created GET request for "http://localhost:8080/LiveRestaurant/orderItemsFeed"
.. Setting request Accept header to [application/xml, text/xml, application/*+xml, application/json, application/atom+xml]
.. GET request for "http://localhost:8080/LiveRestaurant/orderItemsFeed" resulted in 200 (OK)
.. Reading [com.sun.syndication.feed.atom.Feed] as "application/xml" using ... 

工作原理...

OrderServiceClientTest加载的应用程序上下文加载、实例化和注入RestTemplateOrderClient中。这个类使用RestTemplate调用控制器的方法,并将值返回给测试套件类(OrderServiceClientTest)。

在套件类测试方法中,响应将与期望的值进行比较。

applicationContext.xml定义了restTemplate bean 并设置了一系列消息转换器:

......
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="xmlMarshallingHttpMessageConverter" />
<ref bean="jsonConverter" />
<ref bean="atomConverter" />
</list>
</property>
</bean>
<bean id="xmlMarshallingHttpMessageConverter" class="org.springframework. http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg>
<ref bean="xStreamMarshaller" />
</constructor-arg>
</bean>
<bean id="xStreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="annotatedClasses">
<list>
<value>com.packtpub.liverestaurant.domain.Order</value>
<value>com.packtpub.liverestaurant.domain.OrderItem</value>
</list>
</property>
</bean>
<bean id="atomConverter" class="org.springframework. http.converter.feed.AtomFeedHttpMessageConverter">
<property name="supportedMediaTypes" value="application/atom+xml" />
</bean>
<bean id="jsonConverter" class="org.springframework. http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>

设置在messageConverters内部的转换器负责将不同格式(XML、JSON、ATOM)的请求/响应转换回object类型。XstreamMarshaller使用这些类中的注释标签获取已识别的 POJOs(Order、OrderItem)的列表。

OrderClient.java是一个辅助类,用于调用 RESTful Web 服务,使用RestTemplate:

protected RestTemplate restTemplate;
private final static String serviceUrl = "http://localhost:8080/LiveRestaurant/";
@SuppressWarnings("unchecked")
public List<OrderItem> loadOrderItemsXML() {
HttpEntity<String> entity = getHttpEntity(MediaType.APPLICATION_XML);
ResponseEntity<List> response = restTemplate.exchange(serviceUrl + "orderItems", HttpMethod.GET, entity, List.class);
return response.getBody();
}
.....
...
public String loadOrderItemsAtom() {
HttpEntity<String> httpEntity = getHttpEntity(MediaType.APPLICATION_ATOM_XML);
String outputStr = null;
ResponseEntity<Feed> responseEntity = restTemplate.exchange(serviceUrl + "orderItemsFeed", HttpMethod.GET, httpEntity, Feed.class);
WireFeed wireFeed = responseEntity.getBody();
WireFeedOutput wireFeedOutput = new WireFeedOutput();
try {
outputStr = wireFeedOutput.outputString(wireFeed);
} catch (Exception e) {
throw new RuntimeException(e);
}
return outputStr;
}
private HttpEntity<String> getHttpEntity(MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
HttpEntity<String> httpEntity = new HttpEntity<String>(httpHeaders);
return httpEntity;
}

还有更多

这个示例只使用了RestTemplate的两种方法(exchange 和postForEntity)。然而,RestTemplate支持多种调用方法:

  • exchange:它调用特定的 HTTP(GET、POST、PUTDELETE)方法并转换 HTTP 响应

  • getForObject:它调用 HTTP 的GET方法并将 HTTP 响应转换为对象

  • postForObject:它调用 HTTP 的POST方法并将 HTTP 响应转换为对象

另请参阅...

在本章中:

  • 使用 Spring MVC 中的 RESTful 功能设置 Spring RESTful Web 服务

  • 使用 HTTP 消息转换设置 Spring RESTful Web 服务

书籍《RESTful Java Web Services》,网址为www.packtpub.com/restful-java-web-services/book

第十章:Spring 远程

在本章中,我们将涵盖:

  • 使用 RMI 设置 Web 服务

  • 使用 Hessian/Burlap 设置基于 servlet 的 Web 服务,暴露业务 bean

  • 使用 JAX-WS 设置 Web 服务

  • 使用 Apache CXF 暴露基于 servlet 的 Web 服务

  • 使用 JMS 作为底层通信协议暴露 Web 服务

介绍

Spring-WS 项目是一种基于契约的方法来构建 Web 服务。这种方法已经在前八章中详细介绍过。然而,有时的要求是将现有的业务 Spring bean 暴露为 Web 服务,这被称为契约后方法,用于设置 Web 服务。

Spring 的远程支持与多种远程技术的通信。Spring 远程允许在服务器端暴露现有的 Spring bean 作为 Web 服务。在客户端,Spring 远程允许客户端应用程序通过本地接口调用远程 Spring bean(该 bean 作为 Web 服务暴露)。在本章中,详细介绍了 Spring 的以下远程技术的功能:

  • RMI:Spring 的RmiServiceExporter允许您在服务器端使用远程方法调用(RMI)暴露本地业务服务,而 Spring 的RmiProxyFactoryBean是客户端代理 bean,用于调用 Web 服务。

  • Hessian:Spring 的HessianServiceExporter允许您在服务器端使用 Caucho 技术引入的轻量级基于 HTTP 的协议暴露本地业务服务,而HessianProxyFactoryBean是调用 Web 服务的客户端代理 bean。

  • Burlap:这是 Caucho Technology 的 Hessian 的 XML 替代方案。Spring 提供了支持类,使用 Spring 的两个 bean,即BurlapProxyFactoryBeanBurlapServiceExporter

  • JAX-RPC:Spring 支持设置 Web 服务,基于 J2EE 1.4 的 JAX-RPC Web 服务 API

  • JAX-WS:Spring 支持使用 Java EE 5+ JAX-WS API 设置 Web 服务,该 API 允许基于消息和远程过程调用的 Web 服务开发。

  • JMS:Spring 使用 JMS 作为底层通信协议来暴露/消费 Web 服务,使用JmsInvokerServiceExporterJmsInvokerProxyFactoryBean类。

由于 JAX-WS 是 JAX-RPC 的后继者,因此本章不包括 JAX-RPC。相反,本章将详细介绍 Apache CXF,因为它可以使用 JAX-WS 来设置 Web 服务,即使它不是 Spring 的远程的一部分。

为简化起见,在本章中,将暴露以下本地业务服务作为 Web 服务(领域模型在第一章的介绍部分中已经描述,构建 SOAP Web 服务)。

public interface OrderService {
placeOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest);
}

这是接口实现:

public class OrderServiceImpl implements OrderService{
public PlaceOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest) {
PlaceOrderResponse response=new PlaceOrderResponse();
response.setRefNumber(getRandomOrderRefNo());
return response;
}
...

使用 RMI 设置 Web 服务

RMI 是 J2SE 的一部分,允许在不同的 Java 虚拟机(JVM)上调用方法。RMI 的目标是在单独的 JVM 中公开对象,就像它们是本地对象一样。通过 RMI 调用远程对象的客户端不知道对象是远程还是本地,并且在远程对象上调用方法与在本地对象上调用方法具有相同的语法。

Spring 的远程提供了基于 RMI 技术的暴露/访问 Web 服务的功能。在服务器端,Spring 的RmiServiceExporter bean 将服务器端 Spring 业务 bean 暴露为 Web 服务。在客户端,Spring 的RmiProxyFactoryBean将 Web 服务的方法呈现为本地接口。

在这个示例中,我们将学习使用 RMI 设置 Web 服务,并了解通过 RMI 呼叫 Web 服务的呈现方式。

准备工作

在这个示例中,我们有以下两个项目:

  1. LiveRestaurant_R-10.1(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-context-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-10.1-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-context-3.0.5.RELEASE.jar

  • spring-ws-test-2.0.0.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

如何做...

  1. 在服务器端应用程序上下文(applicationContext.xml)中注册服务器端服务实现在 Spring 的RmiServiceExporter中,并设置端口和服务名称。

  2. 在客户端应用程序上下文(applicationContext.xml)中,使用 Spring 的RmiProxyFactoryBean注册本地接口(与服务器端相同)并设置服务的 URL。

  3. 添加一个 Java 类来加载服务器端应用程序上下文文件(在类的main方法中)以设置服务器。

  4. 在客户端添加一个 JUnit 测试用例类,通过本地接口调用 Web 服务。

  5. Liverestaurant_R-10.1上运行以下命令:

mvn clean package exec:java 

  1. Liverestaurant_R-10.1-Client上运行以下命令:
mvn clean package 

  • 以下是客户端输出:
......
... - Located RMI stub with URL [rmi://localhost:1199/OrderService]
....- RMI stub [rmi://localhost:1199/OrderService] is an RMI invoker
......
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.78 sec
...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
......
[INFO] BUILD SUCCESS 

工作原理...

OrderServiceSetUp是加载服务器端应用程序上下文并设置服务器以将服务器端业务服务暴露为 Web 服务的类。OrderServiceClientTest是客户端测试类,加载客户端应用程序上下文并通过代表远程业务服务的客户端本地接口调用 Web 服务方法。

OrderServiceImpl是要通过 Web 服务公开的服务。在服务器端的应用程序上下文中,在org.springframework.remoting.rmi.RmiServiceExporter Bean 中,OrderService是将在 RMI 注册表中注册的服务的名称。服务属性用于传递RmiServiceExporter和 bean 实例。serviceInterface是表示本地业务服务的接口。只有在此接口中定义的方法才能远程调用:

<bean id="orderService" class="com.packtpub.liverestaurant.service.OrderServiceImpl" />
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="OrderService" />
<property name="service" ref="orderService" />
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService" />
<property name="registryPort" value="1199" />
</bean>

在客户端配置文件中,serviceUrl是 Web 服务的 URL 地址,serviceInterface是本地接口,使客户端可以远程调用服务器端的方法:

<bean id="orderService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value=" rmi://localhost:1199/OrderService" />
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService" />
</bean>

OrderServiceClientTest是加载应用程序上下文并通过本地接口调用远程方法的 JUnit 测试用例类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class OrderServiceClientTest {
@Autowired
OrderService orderService;
@Autowired
private GenericApplicationContext applicationContext;
@Before
@After
public void setUpAfter() {
applicationContext.close();
}
@Test
public final void testPlaceOrder() throws Exception {
PlaceOrderRequest orderRequest = new PlaceOrderRequest();
orderRequest.setOrder(getDummyOrder());
PlaceOrderResponse orderResponse = orderService.placeOrder(orderRequest);
Assert.assertTrue(orderResponse.getRefNumber().indexOf("1234")>0);
}
private Order getDummyOrder() {
Order order=new Order();
order.setRefNumber("123");
List<FoodItem> items=new ArrayList<FoodItem>();
FoodItem item1=new FoodItem();
item1.setType(FoodItemType.BEVERAGES);
item1.setName("beverage");
item1.setQuantity(1.0);
......
}
........
}

使用 Hessian/Burlap 设置基于 servlet 的 Web 服务,暴露业务 bean

Hessian 和 Burlap,由 Caucho 开发(hessian.caucho.com),是轻量级基于 HTTP 的远程技术。尽管它们都使用 HTTP 协议进行通信,但 Hessian 使用二进制消息进行通信,而 Burlap 使用 XML 消息进行通信。

Spring 的远程提供了基于这些技术的 Web 服务的暴露/访问功能。在服务器端,Spring 的ServiceExporter bean 将服务器端 Spring 业务 bean(OrderServiceImpl)暴露为 Web 服务:

<bean id="orderService" class="com.packtpub.liverestaurant.service.OrderServiceImpl" />
<bean name="/OrderService" class="....ServiceExporter">
<property name="service" ref="orderService" />
</bean>

在客户端,Spring 的ProxyFactory bean 通过本地客户端接口(OrderService)暴露远程接口:

<bean id="orderService" class="....ProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/LiveRestaurant/services/OrderService" />
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService" />

准备工作

在这个示例中,我们有以下两个项目:

  1. LiveRestaurant_R-10.2(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-webmvc-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • hessian-3.1.5.jar

  1. LiveRestaurant_R-10.2-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-web-3.0.5.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

  • hessian-3.1.5.jar

如何做...

按照以下步骤设置基于 servlet 的 Web 服务,使用 Hessian 服务:

  1. web.xml文件中配置DispatcherServlet(URL:http://<host>:<port>/<appcontext>/services将被转发到此 servlet)。

  2. 在服务器端应用程序上下文(applicationContext.xml)中注册服务器端服务接口,并设置服务名称和服务接口。

  3. 在客户端应用程序上下文(applicationContext.xml)中,使用 Spring 的HessianProxyFactoryBean注册本地接口(与服务器端相同),并设置服务的 URL。

  4. 在客户端添加一个 JUnit 测试用例类,使用本地接口调用 Web 服务

  5. Liverestaurant_R-10.2上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-10.2-Client上运行以下命令:
mvn clean package 

  • 在客户端输出中,您将能够看到运行测试用例的成功消息,如下所示:
text.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.71 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 

按照以下步骤使用 Burlap 服务设置基于 servlet 的 Web 服务:

  1. 将服务器端服务接口修改为 Spring 的BurlapServiceExporter,在服务器端应用程序上下文(applicationContext.xml)中。

  2. 将客户端应用程序上下文(applicationContext.xml)修改为 Spring 的BurlapProxyFactoryBean

  3. Liverestaurant_R-10.2上运行以下命令:

mvn clean package tomcat:run 

  1. Liverestaurant_R-10.2-Client上运行以下命令:
mvn clean package 

  • 在客户端输出中,您将能够看到运行测试用例的成功消息,如下所示:
text.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.849 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.3.1:jar ..
[INFO] Building jar: ...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS 

它是如何工作的...

Liverestaurant_R-10.2项目是一个服务器端 Web 服务,使用 Spring 远程的 burlap/hessian 出口商设置基于 servlet 的 Web 服务。

Liverestaurant_R-10.2-Client项目是一个客户端测试项目,调用了 Spring 远程的 burlap/hessian Web 服务,使用了 burlap/hessian 客户端代理。

在服务器端,DiapatcherServlet将使用 URL 模式将所有请求转发到BurlapServiceExporter/HessianServiceExporter(http:////):

<servlet>
<servlet-name>order</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>order</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>

这些出口商将内部本地服务实现(OrderServiceImpl)公开为 Web 服务:

<bean name="/OrderService" class="org.springframework.remoting.caucho.BurlapServiceExporter">
<property name="service" ref="orderService" />
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService" />
</bean>

在客户端,BurlapProxyFactoryBean/HessianProxyFactoryBean负责使用本地客户端服务接口(OrderService)向客户端公开远程方法:

<bean id="orderService" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/LiveRestaurant/services/OrderService" />
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService" />
</bean>

OrderServiceClientTest的实现与食谱使用 RMI 设置 Web 服务中描述的相同。

另请参阅...

在本章中:

使用 RMI 设置 Web 服务

使用 JAX-WS 设置 Web 服务

JAX-RPC是 Java EE 1.4 中附带的一个标准,用于开发 Web 服务,在近年来变得越来越不受欢迎。JAX-WS 2.0 是在 Java EE 5 中引入的,比 JAX-RPC 更灵活,基于注解的绑定概念。以下是 JAX-WS 相对于 JAX-RPC 的一些优势:

  • JAX-WS 支持面向消息和远程过程调用(RPC)Web 服务,而 JAX-RPC 仅支持 RPC

  • JAX-WS 支持 SOAP 1.2 和 SOAP 1.1,但 JAX-RPC 支持 SOAP 1.1

  • JAX-WS 依赖于 Java 5.0 的丰富功能,而 JAX-RPC 与 Java 1.4 一起工作

  • JAX-WS 使用非常强大的 XML 对象映射框架(使用 JAXB),而 JAX-RPC 使用自己的框架,对于复杂的数据模型显得薄弱

Spring 远程提供了设置使用 Java 1.5+功能的 JAX-WS Web 服务的功能。例如,在这里,注解@WebService会导致 Spring 检测并将此服务公开为 Web 服务,@WebMethod会导致以下方法:public OrderResponse placeOrder(..),被调用为 Web 服务方法(placeOrder):

@Service("OrderServiceImpl")
@WebService(serviceName = "OrderService",endpointInterface = "com.packtpub.liverestaurant.service.OrderService")
public class OrderServiceImpl implements OrderService {
@WebMethod(operationName = "placeOrder")
public PlaceOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest) {

在这个食谱中,使用 JDK 内置的 HTTP 服务器来设置 Web 服务(自从 Sun 的JDK 1.6.0_04以来,JAX-WS 可以与 JDK 内置的 HTTP 服务器集成)。

准备工作

安装 Java 和 Maven(SE 运行时环境(构建jdk1.6.0_29))。

在这个食谱中,我们有以下两个项目:

  1. LiveRestaurant_R-10.3(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • spring-web-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  1. LiveRestaurant_R-10.3-Client(用于客户端),具有以下 Maven 依赖项:
  • spring-web-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

  1. 为业务服务类及其方法添加注释。

  2. 在应用程序上下文文件(applicationContext.xml)中注册服务,然后配置SimpleJaxWsServiceExporter bean,并创建一个类来加载服务器端应用程序上下文(这将设置服务器)。

  3. 在客户端应用程序上下文(applicationContext.xml)中注册本地接口(与服务器端接口相同的方式),并设置服务的 URL。

  4. 在客户端添加一个 JUnit 测试用例类,该类使用本地接口调用 Web 服务。

  5. Liverestaurant_R-10.3上运行以下命令,并浏览以查看位于http://localhost:9999/OrderService?wsdl的 WSDL 文件:

mvn clean package exec:java 

  1. Liverestaurant_R-10.3-Client上运行以下命令:
mvn clean package 

  • 在客户端输出中,您将能够看到运行测试用例的成功消息,如下所示:
.....
Dynamically creating request wrapper Class com.packtpub.liverestaurant.service.jaxws.PlaceOrder
Nov 14, 2011 11:34:13 PM com.sun.xml.internal.ws.model.RuntimeModeler getResponseWrapperClass
INFO: Dynamically creating response wrapper bean Class com.packtpub.liverestaurant.service.jaxws.PlaceOrderResponse
......
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 

它是如何工作的...

Liverestaurant_R-10.3项目是一个服务器端 Web 服务(通过 Spring 远程的出口器 bean),它使用 DK 内置的 HTTP 服务器设置了一个 JAX-WS。

Liverestaurant_R-10.3-Client项目是一个客户端测试项目,它使用 Spring 远程的客户端代理调用 JAX-WS Web 服务。

在服务器端,applicationContext.xml扫描并检测OrderServiceImpl中的注释标签。然后,SimpleJaxWsServiceExporter将此业务服务公开为 Web 服务:

<context:annotation-config/>
<context:component-scan base-package= "com.packtpub.liverestaurant.service"/>
<bean class= "org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
<property name="baseAddress" value="http://localhost:9999/" />
</bean>

在服务类中,注释@WebService@WebMethod导致 Spring 检测(通过扫描),并通过SimpleJaxWsServiceExporter将此服务类公开为 Web 服务及其方法(placeOrder)公开为 Web 服务方法:

@Service("orderServiceImpl")
@WebService(serviceName = "OrderService")
public class OrderServiceImpl implements OrderService {
@WebMethod(operationName = "placeOrder")
public PlaceOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest) {
PlaceOrderResponse response=new PlaceOrderResponse();
response.setRefNumber(getRandomOrderRefNo());
return response;
}
.......
}

在客户端,JaxWsPortProxyFactoryBean负责将远程方法暴露给客户端,使用本地客户端接口。WsdlDocumentUrl是 Web 服务 WSDL 地址,portName是 WSDL 中的portName值,namespaceUri是 WSDL 中的targetNameSpaceserviceInterface是本地客户端服务接口:

<bean id="orderService" class= "org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value= "com.packtpub.liverestaurant.service.OrderService"/>
<property name="serviceInterface" value= "com.packtpub.liverestaurant.service.OrderService"/>
<property name="wsdlDocumentUrl" value= "http://localhost:9999/OrderService?wsdl"/>
<property name="namespaceUri" value= "http://service.liverestaurant.packtpub.com/"/>
<property name="serviceName" value="OrderService"/>
<property name="portName" value="OrderServiceImplPort"/>
</bean>

OrderServiceClientTest的实现与名为使用 RMI 设置 Web 服务的配方中描述的相同。

另请参阅...

在本章中:

使用 RMI 设置 Web 服务

在本书中:

第二章,构建 SOAP Web 服务的客户端

在 HTTP 传输上创建 Web 服务客户端

使用 Apache CXF 暴露基于 servlet 的 Web 服务

Apache CXF起源于以下项目的组合:Celtix(IONA Technologies)和XFire(Codehaus),它们被整合到Apache 软件基金会中。CXF 的名称意味着它起源于CeltixXFire项目名称。

Apache CXF 提供了构建和部署 Web 服务的功能。Apache CXF 推荐的 Web 服务配置方法(前端或 API)是 JAX-WS 2.x。Apache CXF 并不是 Spring 的远程的一部分,但是,由于它可以使用 JAX-WS 作为其前端,因此将在本配方中进行解释。

准备工作

安装 Java 和 Maven(SE Runtime Environment(构建jdk1.6.0_29))。

在这个配方中,我们有以下两个项目:

  1. LiveRestaurant_R-10.4(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • cxf-rt-frontend-jaxws-2.2.6.jar

  • cxf-rt-transports-http-2.2.6.jar

  • spring-web-3.0.5.RELEASE.jar

  • commons-logging-1.1.1.jar

  1. LiveRestaurant_R-10.4-Client(用于客户端),具有以下 Maven 依赖项:
  • cxf-rt-frontend-jaxws-2.2.6.jar

  • cxf-rt-transports-http-2.2.6.jar

  • spring-web-3.0.5.RELEASE.jar

  • log4j-1.2.9.jar

  • junit-4.7.jar

如何做...

  1. 在业务服务类和方法上进行注释(与您为 JAX-WS 所做的方式相同)。

  2. 在应用程序上下文文件(applicationContext.xml)中注册服务,并在web.xml文件中配置CXFServlet(URL:http://<host>:<port>/将被转发到此 servlet)。

  3. 在客户端应用程序上下文(applicationContext.xml)中注册本地接口(与您为服务器端执行的方式相同),并设置服务的 URL。

  4. 在客户端添加一个 JUnit 测试用例类,使用本地接口调用 Web 服务。

工作原理...

Liverestaurant_R-10.4项目是一个服务器端 Web 服务,它使用 JAX-WS API 设置了一个 CXF。

Liverestaurant_R-10.4-Client项目是一个客户端测试项目,它使用 Spring 的远程调用从 JAX-WS Web 服务调用客户端代理。

在服务器端,applicationContext.xml中的配置检测OrderServiceImpl中的注释标签。然后jaxws:endpoint将此业务服务公开为 Web 服务:

<!-- Service Implementation -->
<bean id="orderServiceImpl" class= "com.packtpub.liverestaurant.service.OrderServiceImpl" />
<!-- JAX-WS Endpoint -->
<jaxws:endpoint id="orderService" implementor="#orderServiceImpl" address="/OrderService" />

OrderServiceImpl的解释与在使用 JAX-WS 设置 Web 服务中描述的相同。

在客户端,JaxWsProxyFactoryBean负责使用本地客户端接口向客户端公开远程方法。address是 Web 服务的地址,serviceInterface是本地客户端服务接口:

<bean id="client" class= "com.packtpub.liverestaurant.service.OrderService" factory-bean="clientFactory" factory-method="create"/>
<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="com.packtpub.liverestaurant.service.OrderService"/>
<property name="address" value="http://localhost:8080/LiveRestaurant/OrderService"/>
</bean>

OrderServiceClientTest的实现与在使用 RMI 设置 Web 服务中描述的相同。

另请参阅...

在本章中:

使用 RMI 设置 Web 服务

使用 JMS 作为底层通信协议公开 Web 服务

Java 消息服务(JMS)由 Java 2 和 J2EE 引入,由 Sun Microsystems 于 1999 年成立。使用 JMS 的系统能够以同步或异步模式进行通信,并且基于点对点和发布-订阅模型。

Spring 远程提供了使用 JMS 作为底层通信协议公开 Web 服务的功能。Spring 的 JMS 远程在单线程和非事务会话中在同一线程上发送和接收消息。

但是,对于 JMS 上的 Web 服务的多线程和事务支持,您可以使用基于 Spring 的 JMS 协议的 Spring-WS,该协议基于 Spring 的基于 JMS 的消息传递。

在这个示例中,使用apache-activemq-5.4.2来设置一个 JMS 服务器,并且默认对象,由这个 JMS 服务器创建的(队列,代理),被项目使用。

准备就绪

安装 Java 和 Maven(SE Runtime Environment(构建jdk1.6.0_29))。

安装apache-activemq-5.4.2

在这个示例中,我们有以下两个项目:

  1. LiveRestaurant_R-10.5(用于服务器端 Web 服务),具有以下 Maven 依赖项:
  • activemq-all-5.2.0.jar

  • spring-jms-3.0.5.RELEASE.jar

  1. LiveRestaurant_R-10.5-Client(用于客户端),具有以下 Maven 依赖项:
  • activemq-all-5.2.0.jar

  • spring-jms-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • spring-test-3.0.5.RELEASE.jar

  • xmlunit-1.1.jar

如何做...

在服务器端应用程序上下文文件中注册业务服务到JmsInvokerServiceExporter bean,并使用activemq默认对象(代理,destination)注册SimpleMessageListenerContainer

  1. 创建一个 Java 类来加载应用程序上下文并设置服务器。

  2. 在客户端应用程序上下文文件中使用activemq默认对象(代理,目的地)注册JmsInvokerProxyFactoryBean

  3. 在客户端添加一个 JUnit 测试用例类,调用本地接口使用 Web 服务。

  4. 运行apache-activemq-5.4.2(设置 JMS 服务器)。

  5. Liverestaurant_R-10.5上运行以下命令并浏览以查看位于http://localhost:9999/OrderService?wsdl的 WSDL 文件:

mvn clean package exec:java 

  1. Liverestaurant_R-10.5-Client上运行以下命令:
mvn clean package 

  • 在客户端输出中,您将能够看到运行测试用例的成功消息。
T E S T S
-------------------------------------------------------
Running com.packtpub.liverestaurant.service.client.OrderServiceClientTest
log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).
log4j:WARN Please initialize the log4j system properly.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.138 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 

工作原理...

Liverestaurant_R-10.5项目是一个服务器端 Web 服务,它通过监听 JMS 队列设置了一个 Web 服务。

Liverestaurant_R-10.5-Client项目是一个客户端测试项目,它向 JMS 队列发送 JMS 消息。

在服务器端,OrderServiceSetUp 类加载 applicationContext.xml 并在容器中创建一个 messageListener(使用 SimpleMessageListenerContainer),等待在特定目的地(requestQueue)监听消息。一旦消息到达,它通过 Spring 的远程调用类(JmsInvokerServiceExporter)调用业务类(OrderServiceImpl)的方法。

<bean id="orderService" class="com.packtpub.liverestaurant.service.OrderServiceImpl"/>
<bean id="listener" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="service" ref="orderService"/>
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService"/>
</bean>
<bean id="container" class= "org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="messageListener" ref="listener"/>
<property name="destination" ref="requestQueue"/>
</bean>

在客户端,JmsInvokerProxyFactory 负责使用本地客户端接口(OrderService)向客户端公开远程方法。当客户端调用 OrderService 方法时,JmsInvokerProxyFactory 会向队列(requestQueue)发送一个 JMS 消息,这是服务器正在监听的队列:

<bean id="orderService" class= "org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queue" ref="requestQueue"/>
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.OrderService"/>
</bean>

posted @ 2024-05-24 10:56  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报