文章
开发人员:Java
使用修饰模式简化 Web 服务开发了解如何不通过高成本的代码/测试/部署周期而将功能添加到 Web 服务。 作者:Jason Jones 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>
在 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>
...
拥有一个全局唯一的消息 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 调试器:
本示例的全部源代码在示例下载 zip 中。 结论开发 Web 服务可能非常复杂。设计允许您重用公共代码并对公共功能进行抽象的 Web 服务实施则更加复杂。修饰模式是无须经过高成本的代码/测试/部署周期而将功能添加到您的服务上的一种灵活方法。通过开发定制步骤并将他们部署到策略中,可使您的开发人员将精力集中于业务功能,而不是模板代码,从而简化您的 Web 服务项目。 Jason Jones (jason.jones@zirous.com) 是 Oracle 合作伙伴 Zirous 的系统架构师。他专攻企业 Java 和 SOA 解决方案,是 Oracle SOA 客户顾问委员会成员。Jason 是 Oracle ACE 总监,并有一个关于 Java、SOA 和 Oracle 内容的网志。 |
||||||||||||||||||||||||||||||||||