开发人员:Java
   下载
 Oracle SOA 套件(包括 OWSM 和 JDeveloper)
 示例代码
 
   标签
soa, java, 全部
 

使用修饰模式简化 Web 服务开发

了解如何不通过高成本的代码/测试/部署周期而将功能添加到 Web 服务。

作者:Jason Jones Oracle ACE 总监

2007 年 12 月发布

Oracle 融合中间件提供了众多构建 Web 服务的选择:Java、BPEL、ESB、PHP 等等。甚至数据库也通过数据库本地 Web 服务参与其间。此外,大多数机构有异构 IT 基础架构。因此,您的面向服务的体系结构 (SOA) 将可能包含来自不同平台、工具、编程语言和供应商的服务。这使得在您的整个企业中对 Web 服务应用共同的行为和策略变得非常困难。

需要的是一种方法,能够在无需改变底层系统的情况下更改和增强您所有 Web 服务的行为。这一问题十分符合 Gang of Four (GoF) 修饰模式。在本文中,您将学习如何使用 Oracle Web 服务管理器 (OWSM)(Oracle SOA 套件的一个组件)“修饰”您的 Web 服务。

修饰模式

GoF 修饰模式是在面向对象开发中使用的一个设计模式,在这一模式中您可在不更改原对象的情况下更改对象的行为。其实现方法是通过使用修饰程序来封装原对象,该修饰程序实施同一界面但添加行为和/或修改输入输出。通常修饰程序将委托至底层的普通对象,但在某些情形中,如果没有满足一定的条件,修饰程序将使运行短路。

这是对修饰模式最为简单的介绍。我强烈建议您阅读《Design Patterns:Elements of Reusable Object-Oriented Software》(Addison-Wesley 出版)一书了解更多信息。

Oracle Web 服务管理器

修饰程序是了解 OWSM 如何运行的极佳方法。在 OWSM 中,用户以集中化方式定义策略。这些策略通常分为四个管道:PreRequest、Request、Response、和 PostResponse。每一策略管道由策略步骤组成。所有这些都可通过 OWSM 控制台来完成。管道可以简单得只记录消息日志。事实上,预先定义的默认策略由 Request 和 Response 管道组成,它只记录消息日志。

这些策略可作为底层 Web 服务的修饰程序。它们从客户端的角度增强或更改服务的行为,但这一目标的实现无需更改底层服务。

OWSM 允许策略到服务的灵活映射。可以将 OWSM 的策略按服务进行手动映射,或者分配给一个 URL 模式,与 Java EE Servlet Filter 很相似。此外,可以开发管道模板以便于应用一组策略,从而不需针对每个服务或 URL 重复该设置过程。

OWSM 体系结构

OWSM 策略和服务注册集中存储在 OWSM 策略管理器中,OWSM 策略管理器由数据库支持。通过基于 Web 的用户界面来构建策略和服务。尽管 OWSM 中的策略是集中构建和存储的,但它们可以部署到多个实施点。实施点与 OWSM 策略管理器通信以获取最新的策略和服务注册。实施点有两种形式:网关和代理。

OWSM 网关是 HTTP 代理服务器,可以运行于 Java EE 服务器上,如 Oracle 应用服务器。网关作为代理接收 Web 服务请求,应用策略(可能包括对消息进行操作)然后转发请求。如果合适,OWSM 网关能将这些步骤应用于同步响应。网关为实施策略提供了最为灵活的选择。无需对 Web 服务客户端或者服务的代码或配置进行更改,只需要更改客户端调用的 URL。例如,不必调用 http://jj620.ciscoinc.com:7777/gateway/services/SampleService,只需调用 http://jj620.ciscoinc.com:7777/orabpel/default/SampleService/1.0。

OWSM 代理执行同一功能,但是它们直接安装于服务或客户端应用程序中。代理运行于流程中且要求对 Web 服务客户端和/或服务的配置进行更改。幸运的是,如果您使用 Oracle 应用服务器 Web 服务包,这是十分容易的。例如,如果配置一个 Oracle Web 服务客户端,将以下内容添加到 -client-webservices.xml 配置文件中:

<runtime enabled="owsm">
                              


  <owsm init-home="C:\java\oc4j_101330\owsm\config\interceptors\C0003005"<//>
</runtime> 
                            

OWSM 用户要知道的最后一个组件是 OWSM 监控程序。这一组件从实施点中收集信息。系统将综合这些信息以记录性能、错误和统计方面的日志,并控制报表。

简单的管道

作为一个示例,让我们构建一个管道,它基于一个 LDAP 目录(如 Oracle Internet Directory (OID))的内容进行用户授权。假设您已有具特定权限的用户才能调用的 Web 服务。一个常见的方法就是维护 LDAP 组,它表示各访问级别(只读、编辑、管理、管治)。在授予访问权限前需要对照这些组来核对用户。

您可以通过让客户端在 WS-Security SOAP 头部随附发送证书信息来实施这一方法。下面就是一个例子:

<env:Envelope>
  <env:Header>
    <wsse:Security>
      
                              
<wsse:UsernameToken>
<wsse:Username>jjones</wsse:Username>
<wsse:Password>test</wsse:Password>
</wsse:UsernameToken>
</wsse:Security> </env:Header> <env:Body> ... </env:Body> </env:Envelope>

在 OWSM 中,您可以构建一个管道来处理这些证书,并对照 LDAP 目录对它们进行核对。首先,您需要为您在上一部分中注册的服务分配一个策略。要完成这一操作,单击导航栏中的 Manage Policies 链接,然后选择您刚创建的实施点旁的 Policies 链接。

然后,单击您刚注册的服务的编辑图标旁的 edit。默认管道已经填充。

下一步是添加“Extract Credentials”步骤。该步骤从 SOAP 消息中提取证书信息。单击 Request 管道中日志步骤的 Add Step Below,在新步骤对话框中选择 Extract Credentials 并单击 Ok

默认将提取证书步骤配置为查看 HTTP 基本证书。您可以将其更改为用 WS-BASIC 代替 HTTP,以查看 WS-Security UsernameToken 头部。提取用户证书后,您可以添加授权步骤。单击刚创建的 Extract Credentials 步骤上的 Add Step Below 链接,选择 Ldap Authorize 步骤,然后单击 Ok。您的 Request 管道应如下所示:

默认将提取证书步骤配置为查看 WS-Security UsernameToken 头部。提取用户证书后,您可以添加授权步骤。单击刚创建的 Extract Credentials 步骤上的 Add Step Below 链接。选择 Ldap Authorize 步骤,然后单击 Ok。您的 Request 管道应如下所示:

最后,您需要配置 Authorize 步骤。单击授权步骤上的 Configure 链接,然后编辑属性使其指向本地 LDAP 服务器。LDAP baseDN 应当设置为既是用户又是组的容器,如“dc=ciscoinc,dc=com”。ServiceRoles 设置是一组以逗号隔开的 ldap 组,这些组可以调用服务。最后,将 LDAPAdminDN 设为空白,并确保将 LDAPAdminLoginEnabled 设置为 false

单击 Ok,然后单击 Next > Save。最后,确保单击 Commit 保存这些策略。现在您已经修饰了一个 Web 服务,基于用户的证书对它进行保护。

编写定制步骤

OWSM 提供了众多现成的步骤,用于保护和监视您的 Web 服务。它还将成为您应用程序体系结构的一个强大的部分。Java EE 开发人员都会熟悉 Java servlet 筛选器。这些筛选器也可以用于修饰 Java servlet、JSP 和您最喜欢的 Web 应用程序框架。OWSM 定制步骤可以访问 SOAP 头部和主体。可以操作有效载荷,可以添加更改、修改或删除 XML 元素。

在这一部分,您将构建一个定制步骤,以添加一个定制 SOAP 头部,该头部包含 GUID(全局唯一标识符)。GUID 是一个标识符,其实是一个长的随机数字或字符串。尽管可能出现重复,但大量可能的值使得重复不太可能发生。而且许多 GUID 源于 MAC 地址和/或 IP 地址,用以描述同一瞬间创建的消息。下面是一个您要添加的头部的示例:

<env:Envelope>
  <env:Header>
    ...
     
                              
<otn:GUID>
B72C0BF6-F795-5A04-4D2F-AA24F2DD9888
</otn:GUID>
... </env:Header> <env:Body> ... </env:Body> </env:Envelope>

拥有一个全局唯一的消息 ID 十分有用。我见到的最常见的应用是故障排除,单个 Web 服务消息经过多个组件的情况十分常见,而且通常每个组件都有一个单独的日志。通过记录这些消息 ID 的日志,可以使用如 grep 之类的命令行工具轻松搜索这些消息以进行故障排除。对于我们来说幸运的是,OWSM 已生成一个内部 GUID,因此您可以利用它,将其作为 SOAP 头部添加,以使下游服务可以使用它。

第一步是在 Oracle JDeveloper 中新建一个空项目。下一步,转至项目属性,单击 Libraries 选项。添加以下 JAR 至您的类路径:

$SOA_HOME/owsm/lib/coresv-4.0.jar
$SOA_HOME/owsm/lib/extlib/axis.jar
$SOA_HOME/owsm/lib/extlib/saaj.jar

完成后,它应该如下所示:

现在创建一个新的 Java 类,它需要扩展 com.cfluent.policysteps.sdk.AbstractStep。JDeveloper 在这时将提示出错,说明您需要实施一些所要求的方法。单击 Quick Fix 气球,选择 Implement Methods...。. 这将为 IResult execute(IMessageContext context) 方法提供一个桩。当您的定制步骤是管道时,执行方法由 OWSM 引擎运行。您必须返回一个 IResult 实例,指示 OWSM 是否继续下一步。通常,执行方法返回 IResult.SUCCEEDED,它指示 OWSM 继续处理管道。

消息和相关的元数据传送到 IMessageContext 对象中的定制步骤。最为常见的情况是该步骤调用 context.getRequestMessage() 或 context.getResponseMessage() 以获取 SOAP 消息。在这种情况下,因为您需要操作消息,所以您需要将 IMessageContext 实例强制转换为 com.cfluent.pipelineengine.container.MessageContext,如下所示:

&
...
String stage = context.getProcessingStage(); // processing stage indicates if this is a request or a response
MessageContext msgContext = (MessageContext)context;
SOAPMessage message = null;
if (IMessageContext.STAGE_PREREQUEST.equals(stage) || IMessageContext.STAGE_REQUEST.equals(stage)) {
  message = msgContext.getRequest(); // getting the Axis message
} else if (IMessageContext.STAGE_RESPONSE.equals(stage) || IMessageContext.STAGE_POSTRESPONSE.equals(stage)) {
  message = msgContext.getRequest(); // getting the Axis message
} 
...

下一步是将 SOAP 头部添加到消息。首先,您需要为 SOAPEnvelope 添加一个句柄。(注意这是 "Axis" SOAP 封装。)

对于下面所示代码,还需要注意一些事项。首先,您需要将 SOAPEnvelope 和其他元素强制转换为 Axis 版本。只有 Axis 版本才允许我们更改有效荷载。其次,如果您已经更改了有效荷载,您需要调用 setDirty(true) 以让 OWSM 引擎了解有效荷载已经更改。我的经验是,这一方法不会以递归方式将子元素标记为脏。尽管我没有找到任何确认这一情况的文档,但似乎当您更改有四层深的对象时,您需要确保对链中的每一元素都调用 setDirty(true)。

...
// now get the GUID and build a SOAPElement
try {
  SOAPEnvelope env = message.getAxisMessage().getSOAPEnvelope();
  // next build header element with a name of GUID
  SOAPHeaderElement element =
    (SOAPHeaderElement)env.getHeader().addChildElement("GUID", "otn", "http://otn.oracle.com/owsmarticle");
  element.addTextNode(context.getGUID()); // add the guid that is generated by OWSM

  env.setDirty(true); // need to let OWSM know that we've changed the payload.
} catch (Exception e) {
  logger.log(Level.SEVERE, "exception occurred while adding GUID header", e);
}
...

最后,将成功消息记录到日志并返回 SUCCEEDED IResult。应当通过专用的 OWSM 库完成日志记录。您制定的步骤可以在众多机器上运行,因此写入您自己的日志文件或 System.out/err 中是不合适的。OWSM 提供了一个 ILogger 类,它与 Log4J 或 Java 的内置日志记录类的行为十分相似。最后,通过调用超级类中的 AbstractStep.createMethod(...),您可以生成一个结果,以返回 OWSM。

...
logger.log(Level.INFO, "successfully added GUID {" + context.getGUID() + "} to SOAP message.");
return createResult(IResult.SUCCEEDED);
...

该步骤的全部源代码在示例下载 zip 中。

将定制步骤打包

为部署定制步骤,您需要准备两个构件:上面准备的类的 jar 文件和 OWSM 的定制步骤模板。步骤模板是一个 XML 描述程序,它描述了使用的类、显示给管理员的一些信息,最为重要的是,它描述了可用于配置您的步骤的属性。下面是一个简化的示例:

<csw:StepTemplate
  ..
  name="GUIDStep" 
  ...
>
  <csw:Description>Custom step that adds GUID SOAP header</csw:Description>
  <csw:Implementation>oracle.otn.guidstep.GUIDStep</csw:Implementation>

  <csw:PropertyDefinitions>
    <csw:PropertyDefinitionSet name="Basic Properties">
      <csw:PropertyDefinition name="Enabled" type="boolean">
        <csw:Description>If set to true, this step is enabled</csw:Description>
        <csw:DefaultValue>
          <csw:Absolute>true</csw:Absolute>
        </csw:DefaultValue>
      </csw:PropertyDefinition>
    </csw:PropertyDefinitionSet>
  </csw:PropertyDefinitions>
</csw:StepTemplate>

部署定制步骤

要部署定制步骤,您需要部署 jar 文件,然后上载步骤模板。要部署 jar 文件,只需将其置于 $OWSM_HOME/lib/custom 目录中。因为您的 OWSM 步骤可以部署到多个网关和/或代理,所以您需要在每个服务器上完成该步骤,以实施您的策略管道。完成这些操作后,您必须重启应用服务器。

接下来,您需要上载策略模板。要完成这一任务,首先登录 Web 服务管理器控制台,然后单击您的实施点旁边的 Steps 链接。单击 Add New Step 按钮,然后上载 XML 步骤模板。

同样,您需要为每一个实施点完成该步骤。您现在可以将该步骤添加到您的策略管道。

定制步骤开发技巧

开发定制步骤的障碍之一是要经历一个周期,即代码-部署-测试。一般经过更改后,定制步骤都要求您重启 OC4J 以获取新类。安装完整的 Oracle SOA 套件后,这意味着需要很长的重启等待时间。

加快速度的一个方法是使用 Java 平台调试体系结构 (JPDA)。这允许 JDeveloper 连接到 OC4J,从而提供了一系列好处。首先,您可以逐步执行代码并在运行定制步骤时查看变量值。其次,也是最重要的,您可以在 JDeveloper 中更改代码,而且通过热交换代码部署的魔力,JDeveloper 将把代码输送到 JVM 并立即投入使用。较之构建 jar,将其传送到定制 lib 目录然后重启服务器,这一做法大幅提升了速度。

为此,首先您需要将 OC4J 配置为监听 JPDA 调试器:

  1. 打开企业管理器 (http://{hostname}:{port}/em),然后单击 oc4j_soa 实例。
  2. 单击 Administration 选项卡。
  3. 单击 Server Properties
  4. 在命令行选项下添加以下行: -Xrunjdwp:transport=dt_socket,server=y,address=9901,suspend=n
  5. 单击 Apply,然后一定要重启 OC4J 服务器。

接下来,您需要将 JDeveloper 项目配置为可远程进行调试:
  1. 右键单击该项目,选择 Project Properties
  2. 单击 Run/Debug 选项。
  3. 单击默认的运行配置,然后单击 Edit...
  4. Launch Settings 选项下,确保 Remote Debugging and Profiling 复选框处于选中状态。
  5. 选择 Debug > Remote 选项。
  6. 按下面所示完成选项的输入:

  7. 单击 OK
现在要连接到 OC4J 实例,只需右键单击该项目,然后选择 Start Remote Debugger

JDeveloper 现在已连接到 OC4J,如果您在代码中加入断点并运行这一服务,执行将会停止,因此您可以逐步运行代码。而且,如果您作出了更改并按下“Alt-Shift-F9”(编译),代码将送达 JVM,您可以重新测试。

本示例的全部源代码在示例下载 zip 中。

结论

开发 Web 服务可能非常复杂。设计允许您重用公共代码并对公共功能进行抽象的 Web 服务实施则更加复杂。修饰模式是无须经过高成本的代码/测试/部署周期而将功能添加到您的服务上的一种灵活方法。通过开发定制步骤并将他们部署到策略中,可使您的开发人员将精力集中于业务功能,而不是模板代码,从而简化您的 Web 服务项目。


Jason Jones (jason.jones@zirous.com) 是 Oracle 合作伙伴 Zirous 的系统架构师。他专攻企业 Java 和 SOA 解决方案,是 Oracle SOA 客户顾问委员会成员。Jason 是 Oracle ACE 总监,并有一个关于 Java、SOA 和 Oracle 内容的网志