内容


使用 Spring 的 Web 服务模拟器框架解决方案

Comments

简介

复杂的开发项目往往要求各个阶段的开发流程尽可能相互独立。假设流程之间的接口(不同子系统间的约定)已经在软件开发生命周期(SDLC)中尽可能地提前完成,一个好的基于模拟器的开发策略能在开发过程中实现显著的优化。

这点对于将新应用程序和系统部署到现有环境期间尤其重要,比如在电信领域,该领域混合了大量的 Independent Software Vendor (ISV),并且非功能性需求相当重要,尤其是管理系统延迟和吞吐量方面。在这些复杂环境中,许多因素使得在现有环境中实际交付新应用程序变得很复杂。其中一些因素包括:

  1. 需要开发和发布某些功能,但是缺乏新的应用程序所依赖的所有系统和服务
  2. 需要最大程度地同时进行与不同流程相关的开发活动,同时又要尽量减小流程之间的相互依赖
  3. 需要在发布功能时证明新应用程序能够满足端到端的吞吐量和延迟需求
  4. 需要使开发团队避免对子系统进行修改,他们需要在 SDLC 的编码和单元测试阶段依赖这些子系统
  5. 需要在一个可重复、稳定、可靠的流程内轻松地提供各种测试数据场景

在所有这些领域中,一个功能健壮的模拟器框架将会很有价值。本文将描述这样一个模拟器框架,并解释如何使用 WebSphere® Application Server 来构造一个模拟器框架。此处将重点讲解一个使用 Service Oriented Architecture (SOA) 的应用程序,可将此扩展到其他类型的环境和架构中。

模拟器最佳实践

模拟器概览

韦氏词典对模拟器的定义是 “能在试验条件下重现或表示出有可能会在真实情况中发生的现象的设备 ”。模拟器可在 SDLC 过程中使用,通过为后台服务提供端点实现更加敏捷的开发环境,后端服务呈现为一个调用应用程序,但实际上只是模拟真实的系统。模拟器以与实时后台服务相同的形式进行响应,并使用相同的请求/响应约定,但只是简单地从配置数据返回预置数据。这样就可以对支持系统的功能性和非功能性特性建模,即使您没有访问系统本身。

比较常见的和通用的编程实践是使用 stub 接口,这种接口绕过了本地客户端接口调用,而是执行另外的本地代码路径并返回预置数据。很多情况下,stub 可加速初始开发,但无法实现执行整个代码路径和调用工作接口所能获得的功能。使用 stub 方法时,应用程序需要知道它正处在 “stub 模式” 下,这样才可以执行不同的代码路径。通过使用模拟器,应用程序只改变端点,而不会知道响应来自于模拟的后台系统。

图 1 和图 2 显示出两种方法的区别

图 1. 应用程序使用本地 stub 代码,绕过 Web 服务调用
图片显示应用程序使用 STUB 方法, 对 web 服务客户端的调用被截断
图片显示应用程序使用 STUB 方法, 对 web 服务客户端的调用被截断
图 2. 应用程序使用模拟器作为 Web 服务端点,取代活动的后台系统
图片显示应用程序流程使用模拟器,未发生截断,客户端接收到模拟器的请求和响应。
图片显示应用程序流程使用模拟器,未发生截断,客户端接收到模拟器的请求和响应。

大多数情况下,应用程序会与各种 Web 服务交互,您可能最初希望在活动接口不可用时使用 stub 方法。这会在用户接口快速生成所需的可视流程,但客户端的 stub 不会测试数据的内部连接与编组。因此,模拟越深入,获益越大,因为可以运行更多的系统功能。图 2 演示了创建模拟器的最佳位置。

为了更深入地演示这个概念,让我们查看一个 Web 2.0 示例,它包含一个来自客户端的 Asynchronous JavaScript and XML (Ajax) 请求和一个来自服务器的 JavaScript Object Notation (JSON) 响应,该服务器依赖后台系统构建响应。

图 3. Web 2.0 交互示例
图片显示了一个 3 层应用程序,其中客户端与应用服务器交互,后者又与后台服务器交互。
图片显示了一个 3 层应用程序,其中客户端与应用服务器交互,后者又与后台服务器交互。

建立的第一个约定是客户端与应用程序间的 JSON 对象。第二个约定位于应用程序与使用 Web Service Definition Language (WSDL) 的后台服务器之间。在初始开发阶段,JSON 约定和 Web Service 约定表示的是可以创建 stub 接口的共同位置点,这样就可通过维护约定来保持不同开发人员的独立性。

理论上,当每个开发小组按照约定完成开发任务后,就可以顺利地集成应用程序。然而,在实际中,当集成多个外部系统时,情况并非如此。在接口集成期间经常会返回意外结果,或者服务到服务之间出现一系列依赖调用,这需要对调用进行编排。一个被破坏的或不可用的接口可能阻塞多个下游开发团队。

在开发中使用模拟器的优点

当与多个团队开发复杂系统(存在许多依赖关系)时,几乎不可避免地会出现接口延迟的风险。服务模拟将会成为重要的风险缓解工具,可以在开发复杂系统时采用该工具来显著减少风险与延迟。

在决定模拟程度时需要考虑几个关键因素。很多情况下,最简单的模拟是不管输入是什么都返回相同的响应。这对于初始单元测试有效,但很难保证获得优质的代码。更常见的做法是,对模拟器加以扩展,能够根据请求数据返回更复杂的响应。某些情况下,用请求数据在 XML 文件中查找响应。对于其他的情况,在计算中重用或处理请求数据以用于响应。

为了能实现更复杂的响应,需要构建简单、灵活和轻量级的模拟器模式,以便能被开发人员轻松采用,并且还提供了深入的模拟。

图 4. 模拟器可访问的数据将影响其对于开发团队的可用性以及最后的成功
图片显示的是一个 3 层应用程序架构,客户端是 Ajax,与应用服务器交互,应用服务器再通过 Web 服务与另一应用服务器交互。最后的应用服务器有一个 Web 服务模拟器。
图片显示的是一个 3 层应用程序架构,客户端是 Ajax,与应用服务器交互,应用服务器再通过 Web 服务与另一应用服务器交互。最后的应用服务器有一个 Web 服务模拟器。

使用模拟器进行功能测试的优点

在 SLDC 中使用模拟的好处超越了开发阶段。更新的技术,如 scrum、极限编程、特征驱动开发,可以促进迭代开发,但可能与传统的瀑布驱动方法冲突,在瀑布驱动方法中,一些连续的接口(gate)需要汇合以移到下一阶段。如果快速开发组件依赖于关键接口且开发周期较长,迭代测试就难以实现。在这种情况下,模拟器将会在活动接口可用前测试组件,并能提前发现主要问题。当试图向后台系统提供测试数据以支持可能发生的错误场景时,通常会遇到挑战。模拟器会提供有效的替代方法测试不常见的场景或难以使用活动后台系统实现的反向(negative)测试案例。

模拟器也提供了一个强大的风险缓解策略。随着系统接口数量和复杂性的增加,故障和延期的风险也会增加。以下的情况经常会出现:接口不可用,因为需要使用补丁修复错误;或者开发人员必须安排宕机时间来处理活动测试窗口中的接口。这种情况下,开发和测试团队需要付出更多宝贵的时间才能实现一个完整的、有效的系统。通过在测试环境中使用模拟器,应用程序可以为特定的接口重新配置接口端点,将其从不可用的服务调整到正常的模拟服务,从而避免出现停机。测试系统可以继续对所有工作中的系统使用活动接口,只对不可用的服务使用模拟器。

通常会开发一个服务指示板来结合用于模拟器,从而支持开发人员与测试人员测试模拟服务的请求和响应。这可以对请求数据配置进行快速验证。由于模拟器模仿活动的后台服务,服务指示板也可以在活动后台服务可用时测试和解决故障。某些情况下,服务指示板会揭示出一些在模拟器中不可用的独特响应场景。服务指示板可包含记录原始请求和响应的功能,能够扩展模拟器以使其将该场景添加到模拟集中。

图 5 演示了由一组后台服务和模拟器共同支持的系统的逻辑视图。图中显示了一个外部应用程序通过 Enterprise Service Bus (ESB) 连接到各个服务。黄颜色的服务(服务 1、2 和 7)指示服务此刻不可用,由模拟器提供。红颜色服务(服务 3 和 4)表示服务正在部署或是不可用。指向红颜色服务的请求将通过 ESB 重新路由到模拟服务上。

图 5. 同时利用模拟服务和活动后台服务的系统的逻辑视图
图片显示应用程序通过 Enterprise Service Bus 交互,根据资源情况,将服务分别路由到活动系统和模拟器。
图片显示应用程序通过 Enterprise Service Bus 交互,根据资源情况,将服务分别路由到活动系统和模拟器。

使用模拟器进行非功能性测试的优点

性能测试是 SDLC 中的重要方面。许多情况下,用于测试的环境无法模拟出生产环境的性能特点。经常有这样的情况,公司的系统由于过于昂贵而无法在测试环境中完全重现。这种情况下,模拟器可以很好地支持性能测试,因为它们能模拟出期望的生产环境。

要理解为什么模拟器会成为很好的性能测试工具,需要对负载有所理解。Little 的 负载定律(Law of Load)指出,平均负载(L)等于到达率(λ)乘以完成事务的平均处理时间(T)。即,L = λT。

如果一个应用程序需要 1 秒的时间来响应请求,那么每秒的请求数必须是 1,负载才能均衡。图 6 演示的是到达率上升而持续时间不变的情况。许多前端负载测试工具强调提高到达率以产生负载。

图 6. 增加每秒请求数而响应时间不变
图片显示应用程序在不同的每秒请求负载下运行,以及如何需要额外的资源实例以保持理想的 1 响应/秒。
图片显示应用程序在不同的每秒请求负载下运行,以及如何需要额外的资源实例以保持理想的 1 响应/秒。

反过来也一样,并且展示了到达率在注册时未改变,但应用程序的响应时间会降低。图 7 显示的是响应率下降而请求率不变的情况。

图 7. 每秒请求数不变,响应时间下降
图片显示了具有相同负载的相同资源可能会带来不同的响应时间。
图片显示了具有相同负载的相同资源可能会带来不同的响应时间。

图 6 和图 7 在复杂系统中都很常见。值得注意的是请求率和响应时间都对负载有显著影响。

负载测试通常会强调通过增加客户端访问应用程序的到达率增加负载(λ)。这通常可由 Mercury Load Runner 或 IBM Rational Performance Tester 之类的负载生成工具实现,但没有处理后台系统用于返回结果的预期平均处理时间(T)。作为推荐的最佳实践,模拟器可以和性能模型联合使用以生成更贴近生产环境的性能测试,通过使用性能模型决定每个接口的平均响应,并将这些响应时间编入到模拟器中。

可根据生产中现有的或预期的响应时间,用性能模型文档记录每个生产系统的 Service Level Agreements(SLA)。这包括不同级别的请求率的响应时间。通常,每个请求的响应时间将会随着每秒事务量 (TPS) 的增加而减少。这些变化的响应时间也会被包含到模拟器内,以更好地对预期生产环境建模。

通过在服务中模拟暂时的 “hiccup” 或暂停,或者通过模拟预计的 SLA 以外的响应来衡量对依赖系统的影响,模拟器还可以用于实现反向性能测试场景。这种额外的测试级别允许在 SDLC 中尽早发现并解决问题。

技术实现概览

以下内容将介绍模拟器框架的具体实现。它利用 Spring 2.0 Framework 将以 XML 格式存储的响应数据转换成 Plain Old Java Objects (POJO),以用来表示相关的服务响应。该框架支持使用模拟器和指示板进行单元测试。模拟器框架包含以下几个已定义的层:

Web 服务

模拟器解决方案的底层框架包括 Web 客户端和 Web 服务。主应用程序内部运行的 Web 客户端和模拟器的接收 Web 服务都是根据所提供的 WSDL 生成的。WSDL 允许自动生成测试业务功能所必需的请求和响应对象。

指示板层

指示板层含有用户接口组件,用来收集数据供客户端服务层生成请求对象。在收集数据时,指示板同样会显示与被调用的模拟器服务相关的结果。指示板也可在测试真实 Web 服务时用作健康检查工具来验证连通性和结果。

客户端服务层

客户端服务层定义了具体的实现类,负责从指示板传递数据、生成请求对象和调用特定的模拟器服务。

模拟器服务层

模拟器服务层负责生成请求对象,该对象会返回到进行调用的客户端服务。该层从请求中提取关键值,请求将会传递到处理层,后者用于检索 XML 文件中相关的测试数据。在处理层中,将会在返回响应前应用生成正确响应所必需的全部业务逻辑。

处理层

处理层将会使用模拟器服务提供的键值来确定需要检索的 Spring XML 文件。检索完成后,Spring BeanFactory 将会使用该 XML 文件并将数据转换成已定义的 POJO,后者将被返回给模拟器服务层。处理层的另一个作用是引入处理延迟,这样可以模拟不同类型的事务延迟。

数据层

数据层是 XML 文件的储存库,这些文件用来表示 Web 服务响应对象。这些 XML 文件定义了简单、一致的方法来生成 JavaBean 对象,这些对象可由符合 spring-beans-2.0.dtd 标准的 Spring BeanFactory 管理。对 XML 文件的检索由 Spring File System Resource 处理,并使用 Spring XmlBeanFactory 类将 XML 文件转换为已定义的 POJO。

WebSphere 资源提供者

WebSphere 资源提供者被用作一个存储解决方案,用于维护模拟器解决方案中使用的动态配置值。可使用管理控制台添加、修改、部署值,从而可以在单独或集群环境中实现轻松的维护。

图 8. 模拟器框架图解
图片显示的是基础架构,其中涉及各种复杂应用程序元素,包括 Web Service Simulator、Data Generator 和 Spring XML File Repository。
图片显示的是基础架构,其中涉及各种复杂应用程序元素,包括 Web Service Simulator、Data Generator 和 Spring XML File Repository。

技术实现步骤

以下是一个创建模拟器应用程序示例的详细步骤:

  1. 从提供的 WSDL 中生成 Web 服务工件。
  2. 生成指示板 UI 组件以捕获输入。
  3. 在 RPC 配置中映射服务的实现、方法、输入参数。
  4. 生成客户端实现以收集请求对象并将其传递给模拟器服务。
  5. 生成模拟器服务,将业务规则运用到生成的响应对象上。
  6. 生成 Spring XML 数据文件,此文件将会被转换成响应对象。
  7. 配置 WebSphere Environment Provider 值以支持应用程序。

Web 客户端和服务

Web 服务是模拟器框架的基础。此前已定义的 WSDL 用来生成 Web 服务工件,将在 Web 服务工件之上进行构建来提供执行模拟器业务用例所需的功能。用客户端实现这些内容后,就创建了最基本的 WSDL 和基于 WSDL 的 Web 服务客户端。以下步骤将指导您根据一个 WDSL 示例生成 Web 服务。

使用 Rational Application Developer (RAD) 平台,通过向导工具快速生成 Web 服务和客户端。

Web 服务创建

  1. 在 RAD 内,创建一个包含 Web 服务和模拟器支持工件的新 Web 项目。
    图 9. 创建动态 Web 项目
    在 Rational Application Developer 中生成 Simulator 项目的屏幕截图。
    在 Rational Application Developer 中生成 Simulator 项目的屏幕截图。
  2. 在项目的 “src” 树下添加 “wsdl” 文件夹。添加表示要模拟的 Web 服务的 WSDL。添加 WSDL 中提供的相关的 namespace-to-package 属性文件以用于生成服务和客户端。
    图 10. 支持资源文件
    在项目树下创建文件夹和资源的屏幕截图
    在项目树下创建文件夹和资源的屏幕截图
  3. 右键单击 WSDL 并选择 Web Services > Generate Java bean skeleton,创建 Web Service。将打开 Web Service Wizard。
    图 11. 创建 Java bean 骨架
    在 RAD 中打开菜单以生成 Java bean 骨架的屏幕截图
    在 RAD 中打开菜单以生成 Java bean 骨架的屏幕截图
  4. 确认 Web Service 向导设置。通常使用默认值。
    图 12. 验证 Server、Service Project 和 EAR
    在 RAD 中验证服务组件的屏幕截图
    在 RAD 中验证服务组件的屏幕截图
  5. 选择将名称空间映射到包的选项,这在生成的对象中提供了一种更友好的包结构。
    图 13. 验证源文件夹并选择自定义映射
    选中 define custom mapping from namespace to package 选项的屏幕截图
    选中 define custom mapping from namespace to package 选项的屏幕截图
  6. 单击 Import 导入名称空间和包,然后单击 Finish。
    图 14. 将名称空间导入到包定义中
    在 RAD 中为项目名称空间定义自定义映射的屏幕截图
    在 RAD 中为项目名称空间定义自定义映射的屏幕截图

单击 Finish 后,Web 服务的客户端和服务端所需的对象就生成了。新的对象将会显示在选中包名下创建的项目中。

模拟器指示板

模拟器指示板提供用于测试模拟器服务的 UI 组件。对于要进行测试的每个 Web 服务,将会创建一个 JSP 来捕获在客户端服务层内生成 SOAP 请求对象所需的所有数据。

指示板层利用了一些工具,这些工具提供了快速的开箱即用功能,可以帮助在模拟器解决方案中收集、检索和显示数据。将使用两个部分组成指示板:

  • Ajax— 用 Ajax 组件来收集信息、与服务层通信、呈现响应数据。
  • IBM Web 2.0 Feature Pack – RPC Adapter Servlet—RPC Adapter Servlet 将 Ajax 请求映射到所需的服务实现和管理 Http 请求及其内部数据的方法。生成的 bean 将由 RPC Adapter Servlet 转换成 JSON 对象,该对象以后可使用 Ajax 在 JSP 中呈现。(参见 参考资料 获得下载。)
图 15. RPC Adapter 与 Web 客户端调用流的 JSP 之间的关系
图片显示的是 Webphere Environment 提供者如何与 JSP Ajax 回调交互以提供模拟器端点
图片显示的是 Webphere Environment 提供者如何与 JSP Ajax 回调交互以提供模拟器端点

特定于模拟器的 JSP

JSP 将定义客户端服务实现所需的输入,以形成传递给模拟器的请求对象。在捕获所需的输入值的同时,JSP 将用 callback 函数与 RPC Adapter 配置中定义好的指定实现进行交互。模拟器返回的数据将通过 RPC Adapter servlet 转换成 JSON 对象并在其中显示。

JSP 包含以下内容:

  1. 从 WebSphere Resource Provider 中检索默认的模拟器端点(WebSphere Resource Environment Provider 中有关于 Resource Provider 配置的讨论)。
    <% String acctEndPoint=(String)EnvironmentProviderUtility
                		.getWebServiceEnvironmentAttribute
                		("AccountSearch.Endpoint"); %>
  2. 定义客户端服务所需的所有 HTML 输入字段以生成 SOAP 请求。
  3. 创建 Ajax 回调函数,这些回调函数被映射到所需的处理服务实现和方法。
    getRpcDataAccountSearch ('AccountSearchImpl','getAccountsBy
                		Equipment', $('accountEndPoint').value,
                		 $('equipId').value,'getAccountsByEquipment')
    图 16. 简单的指示板 JSP
    指示板的屏幕截图,其中包含用于输入模拟器端点和将传递给客户端的输入值的字段
    指示板的屏幕截图,其中包含用于输入模拟器端点和将传递给客户端的输入值的字段

RPC Adapter servlet 配置

RPC Adapter Servlet 必须在模拟器项目中使用 IBM Web 2.0 Feature Pack 提供的文件按照以下步骤配置:

  1. IBM Web 2.0 Feature Pack – RPC Adapter Servlet— 通过编辑 web.xml 文件并添加 RPX Adapter Servlet 定义,在模拟器项目中注册 IBM WEB 2.0 Feature Pack – RPC Adapter Servlet。清单 1 显示的是在 web.xml 文件中注册 BM RPC Adapter servlet。
    清单 1. 向 web.xml 文件添加 RPC Adapter Servlet 定义
    <servlet>
        <description> IBM WEB 2.0 Feature Pack - RPC Adapter </description>
        <display-name>RPCAdapter</display-name>
        <servlet-name>RPCAdapter</servlet-name>
        <servlet-class>com.ibm.websphere.rpcadapter.RPCAdapter</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>RPCAdapter</servlet-name>
        <url-pattern>/RPCAdapter/*</url-pattern>
    </servlet-mapping>
  2. 向 WEB-INF 文件夹添加 RpcAdapterConfig.xml 文件。RpcAdapterConfig.xml 文件在 IBM Web 2.0 Feature Pack 中提供。
    图 17. 向 WEB-INF 文件夹添加 RpcAdapterConfig.xml 文件。
    屏幕截图显示 RpcAdapterConfig.xml 位于项目树中
    屏幕截图显示 RpcAdapterConfig.xml 位于项目树中
  3. 在 RpcAdapterConfig.xml 文件中映射模拟器服务实现和方法

在整个配置过程中,RPCAdapter 知道从调用 JSP 中调用哪个实现。在清单 2 中,<pojo> 下的 <name> 值注册了将在 JSP 中引用的实现;<method> 下的 <name> 值注册了将由 JSP 使用的实现方法;<parameter> 下的 <name> 值注册了将被传递到实现方法的输入值。

清单 2. 定义所有 pojo、实现、方法和参数
<services>
    <pojo>
        <name>AccountSearchImpl</name>
        <description>com.simulator.service.AccountSearchImpl</description>
        <methods filter="whitelisting">
            <method>
                <name>getAccountsByEquipment</name>
                <description>web service method to retrieve account information by
                equipment id</description>
                <parameters>
                    <parameter>
                        <name>request</name>
                        <description>end point for account 
                        search simulator service</description>
                    </parameter>
                    <parameter>
                        <name>equipId</name>
                        <description>the equipment id used as key for retrieving simulator
                        data</description>
                    </parameter>
                </parameters>
            </method>
        </methods>
    </pojo>
</services>

客户端服务层(模拟器)

模拟器客户端服务层包含与模拟器 Web 服务交互所需的 Web 服务客户端和实现类。实现类根据从相关 JSP 中收集的数据构造一个特定于模拟器的 SOAP 请求,并将信息发送到已映射的模拟器 Web 服务上,该服务由作为已映射的 WebSphere Environment Provider 值检索的端点确定。响应对象将由 RPC Adapter servlet 转换成 JSON 对象,并回送给调用 JSP 中的用户。

在清单 3 中,equipment.setSim(equipId) 将来自输入值的请求对象填充到 JSP 中。endPoint 值被定义为 WebSphere Environment Provider 值。

清单 3. 传递到模拟器服务的请求对象的例子
public ESIGetAccountsByEquipmentResponseType getAccountByEquipment (
    HttpServletRequest request, String endPoint, String equipId) {

String method = "getAccountsByEquipment";
ESIGetAccountsByEquipmentResponseType rc = new ESIGetAccountsByEquipmentResponseType();
ESIGetAccountsByEquipmentResponseType req = new ESIGetAccountsByEquipmentRequestType();
Equipment equipment = new Equipment();
equipment.setSim(equipId);
req.setEquipment(equipment);

try {

    InitialContext jndiContext = new InitialContext();
    AccountSearch service = (AccountSearch) jndiContext
        .lookup("java:comp/env/service/AccountSearch");
        
    AccountSearchPort port = (AccountSearchPort)service.getPort(AccountSearchPort.class);
    ((Stub)port)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, endPoint);

    rc = port.getAccountsByEquipment(req);
    
} catch (java.lang.Exception e) {
    String[] variables = new String[] { e.getLocalizedMessage() };
    java.text.MessageFormat form = new java.text.MessageFormat(
        "Remote system error occurred, message is [{0}]");
    String formattedText = form.format(variables);
    logger.logp(WsLevel.SEVERE, CLASS_NAME, method, formattedText, e);
    return rc;
}
logger.exiting(CLASS_NAME, method, rc);
return rc;

}

模拟器服务层

在模拟器服务层内部,从请求对象中提取出 “transaction” 键,然后使用它来指定将要检索、使用并作为 POJO 响应返回的 XML 文件。然后,在清单 4 中,从请求对象中提取出 Equipment ID,然后作为 XML 检索键传递给处理层。在该层中,会应用验证用例所需的业务逻辑,然后返回响应。

同样在清单 4 中,第一个 try 语句从请求中传递一个键,生成器将使用它查找将被转换为响应 POJO 的 XML 文件。

清单 4. 从请求对象中构造键供数据生成器层定位 Spring XML 文件
    public ESIGetAccountsByEquipmentResponseType getAccountsByEquipment(
        ESIGetAccountsByEquipmentRequestType getAccountsByEquipmentInput)
            throws java.rmi.RemoteException {
            
    String methodName = "getAccountsByEquipment";
    
    // return object
    ESIGetAccountsByEquipmentResponseType rc = new
    ESIGestAccountsByEquipmentResponseType();
    
    // request value used for simulator xml retrieval key
    String sim = getAccountsByEquipmentInput.getEquipment().getSim();
    
    if (log.isLoggable(WsLevel.FINER) {
        log.entering(className, methodName);
    }
    
    try {
    
        rc = (ESIGetAccountsByEquipmentResponseType) com.simulator.DataGenerator
                .getDataByBeanId(
                        "ESIGetAcountByEquipmentResponseType",
                        ACTION_KEY_EQUIPMENT, sim);
        return rc;
    } catch (org.springframework.beans.factory.BeanDefinitionStoreException ioe) {
        
        // return default equipment response
        rc = (ESIGetAccountsByEquipmentResponseType) com.simulator.DataGenerator
                .getDataByDefault("ESIGetAccountsByEquipmentResponseType",
                        ACTION_KEY_EQUIPMENT, DEFAULT_EQUIPMENT_CODE);
        log.logp(WsLevel.SEVERE, className, methodName, ioe
                .getResourceDescription());
        return rc;
    } catch (Exception e) {
        rc.setStatus(getCommonStatusType(e));
        return rc;
    } finally {
        if (log.isLoggable(WsLevel.FINER)) {
                log.exiting(className, methodName);
        }
    }
}

处理层

处理层获得 inbound 键,该键将用于确认将要检索的 Spring XML 文件。检索到文件之后,SpringBeanFactory 将使用 XML 文件并将数据转换成已定义的 POJO。Spring 将会在所请求的 XML 文件无法定位时抛出一个封装的 IO Exception。在这种情况下,可以定义并返回默认的 XML 文件,当场景中已经确定了 “默认” 行为时,这项处理将很有用。另一方面是引入处理延迟来模拟事务延迟。这种情况下,常量延迟值会以 WebSphere Environment Provider 值的形式存储。

在清单 5 中,bf.getBean(clazzName) 根据模拟器服务中的键检索数据 XML。注意此处还有一个延迟注入检测,使用的是由 WebSphere Envrionment Provider 定义的值。

清单 5. 数据 XML 与延迟注入的检索示例
// Method used for retrieving spring xml
public static Object getDataByBeanId(String clazzName, String action, String key) throws
Exception {
    
    String dataFile = null;
    Object response = null;
    
    // check if service is "enabled"
    if ((ACCOUNT_SEARCH_ENABLED_KEY != null) && (ACCOUNT_SEARCH_ENABLED_KEY != "")
            && (ACCOUNT_SEARCH_ENABLED_KEY.equalsIgnoreCase("FALSE"))) {
        throw new Exception("service is disabled");
    }
    
    dataFile = getFile(action, key);
    
    com.simulator.SpringContext stsc = new com.simulator.SpringContext (
            dataFile);
            
    org.springframework.beans.factory.BeanFactory bf = stsc
            .getBeanFactory();
            
    // retrieve pojo
    response = bf.getBean(clazzName);
    
    // check i latency has been injected
    if ((ACCOUNT_SEARCH_LATENCY_KEY != NULL) && (ACCOUNT_SEARCH_LATENCY_KEY.length() !=
    0)) {
        sleep(ACCOUNT_SEARCH_LATENCY_KEY;
    }
    
    return response;

数据层

数据层包含已定义的 XML 文件,Spring 框架会使用这些文件并作为 POJO 返回到模拟器服务层。清单 6 中的 POJO 是一个典型的 JavaBean 例子,它可用于在 Spring 框架内连接数据层与 XML。POJO 利用了简单的 getter 和 setter,使用框架将 XML 绑定到 Java bean。

清单 6. 简单的 JavaBean 定义的例子
* AccountSummaryType.java..

package com.simulator.eo.account.accountsearch;

public class AccountSummaryType {
    private java.lang.String ssn;
    
    publicAccountSummaryType() {
    }
    
    public java.lang.String getSsn() {
        return ssn;
    }
    
    public void setSsn(java.lang.String ssn) {
        this.ssn = ssn;
    }

清单 7 提供了一个示例 Spring XML,表示清单 6 中所示的 JavaBean。

清单 7. 用 Spring XML 表示 JavaBean 的例子
<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns=http://www.springframework.org/schema/beans
        xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
        xsi:schemaLocatin="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean name="ESIGetAccountsByEquipmentResponseType"
        class="ESIGetAccountsByEquipmentResponseType"
            <property name="accountSummary">
           <bean class="com.simulator.eo.account.accountsearch.AccountSummaryType">
             <property name="ssn" value="ssn"></property>
           </bean>
            </property>
    </bean>
</beans>

WebSphere Application Server 配置

需要更改 WebSphere Application Server 配置以支持模拟器框架。模拟器利用 WebSphere 的 Resource Environment Provider 存储模拟器应用程序中使用的值。Resource Environment Provider 的优势是它允许在 Application 服务器中存储特定于环境的变量,而不是在属性文件中存储。以下的例子演示了模拟器的帐户查找服务属性的配置。这些步骤定义了模拟器 Resource Environment Provider 及其属性值。可参考 “Using resource environment providers in WebSphere Application Server”(参见 参考资料 获得链接)。

WebSphere Resource Environment Provider

执行以下步骤安装 WebSphere Resource Environment Provider:

  1. 登录到 WebSphere 管理控制台并使用左边的导航功能导航到 Resources >Resource environment providers。
  2. 单击 New 并输入 Environment Provider 的名称(即 Simulator Environment Provider)。
    图 18. 定义 Simulator Environment Provider
    显示 wsadmin 屏幕的屏幕截图,为模拟器定义了 resource enviornment provider
    显示 wsadmin 屏幕的屏幕截图,为模拟器定义了 resource enviornment provider

执行以下步骤安装 WebSphere Resource Environment Provider – Referenceables:

  1. 单击新的 Environment Provider 的名称并导航到 Resources > Resource environment providers > Simulator Environment Provider > Referenceables。
  2. 单击 New 以输入 Factory 和 Class Name。输入以下内容:
    • com.simulator.EnvironmentProviderFactory 作为工厂类名称
    • com.simulator.EnvironmentProvider 作为类名
    图 19. 定义 Environment Provider Referenceables 中的工厂和类
    wsadmin 的屏幕截图,显示了 referenceables 的配置
    wsadmin 的屏幕截图,显示了 referenceables 的配置

按以下步骤安装 WebSphere Resource Environment Provider - Resource env entries:

  1. 单击 Simulator Environment Provider 的 breadcrumb 条目并导航到 Resources > Resource environment providers > Simulator Environment Provider > Resource env entries。
  2. 单击 New 并输入以下信息:
    • 使用 Simulator Configuration 作为名称
    • 使用 rep/SimulatorConfig 作为 JNDI 名
    • 使用 Simulator configuration properties 作为描述
    图 20. 定义 resource environment 条目
    wsadmin 的屏幕截图,显示了 Simulator Configuration 的 Resource env 条目的配置
    wsadmin 的屏幕截图,显示了 Simulator Configuration 的 Resource env 条目的配置

使用以下步骤配置 WebSphere Resource Environment Provider – Custom Properties:

  1. 单击新创建的 Resource env 条目 “Simulator Configuration” 的标题并导航至 Resources > Resource environment providers > Simulator Environment Provider > Resource env entries > Simulator Configuration >Custom properties。
  2. 单击 New 并输入表 1 中的信息。结果将如图 21 所示。
表 1. 自定义属性定义的值
名称描述是否必需
AccountSearch.Endpointhttp://localhost:10000/Simulator/services/AccountSearch 模拟器端点
Simulator.Directory C:\\simulator\\xml\\ 模拟器数据目录
AccountSearch.Latency 500 延迟值
AccountSearch.Folder Account\ 数据文件夹
AccountSearch.Prefix ACT 文件前缀
AccountSearch.Enabled true 支持的状态
图 21. 定义应用程序使用的自定义属性
wsadmin 中完成的自定义属性的屏幕截图
wsadmin 中完成的自定义属性的屏幕截图
  1. 注册 Resource Environment Reference:WebContent > WEB-INF > edit web.xml adding Resource Environment Reference。
    图 22. web.xml 中的 Resource Environment Reference
    显示由 web 应用程序引用的 ResourceEnvRef rep/Simulator 的屏幕截图
    显示由 web 应用程序引用的 ResourceEnvRef rep/Simulator 的屏幕截图

现在可以从 Java 对象或 JSP 的代码中检索配置值。在本例中可用来代替属性文件,因为可通过管理控制台来管理这些值并使用控制台在整个集群中同步。

结束语

模拟器提供了一种方便的方式来针对并不总是可用(或者无法支持完整的系统测试需求)的系统进行开发和测试。模拟器框架提供了一种可快速采用模拟器最佳实践的机制,包括使用服务指示板监控模拟的和活动的后台服务。在开发过程中,当真实服务不可用或不可靠时,模拟器是一项显而易见的选择,此外,模拟器还可用于功能与性能测试,包括使用延迟注入对 SLA 建模。模拟器框架最初可能令人感觉过于复杂和耗时,但是历经多个项目生命周期后,最终证明它在开发过程中能够显著降低项目的整体投入和风险,同时提高单元测试、功能测试、性能测试的质量。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=491448
ArticleTitle=使用 Spring 的 Web 服务模拟器框架解决方案
publish-date=05242010