IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Architecture | SOA and Web services | WebSphere  >

在 WebSphere Application Server V6.1 中实现 WS_Notification

将面向服务的体系结构与事件驱动的体系结构融合

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 高级

Boris Lublinsky, 企业架构师, ALDION Consulting Pte Ltd

2007 年 3 月 22 日

面向服务的体系结构 (SOA) 的好处之一就是,它支持通过构建组合服务来创建新解决方案。在本文中,您将了解如何使用事件构建组合服务以及 IBM® WebSphere® Application Server 6.1 中提供的新 WS-Notification 支持。文中还提供了一个简单的代码示例,可通过其了解如何使用发布/订阅引擎进行服务组合。

服务组合与事件

面向服务的体系结构 (SOA) 正逐渐占据企业体系结构舞台,成为用于进行企业解决方案设计、实现和交付的首要方法。通过将现有服务组装为组合企业解决方案(组合服务),可实现大部分 SOA 好处。调用组合服务时,它将与所属服务进行交互,以执行一组相关的业务功能。组合服务对所使用的其他服务进行编排,控制用于确定调用顺序的规则。服务组合允许创建与现有服务一起运行的解决方案。此类解决方案可迅速部署,提供重用可能性,且提供对各种现有服务的无缝访问。

Ali Arsanjani 在文章“Toward a pattern language for Service-Oriented Architecture and Integration, Part 2: Service compositon”中定义了创建组合服务的多个业务驱动因素。(请参见参考资料中提供的这篇文章的链接。)

  • 替换。对于特定服务在多个组合服务(流程)中使用的情况,可组合性允许同时将一个服务中的更改引入到使用此服务的所有企业业务流程中。
  • 合并或单一访问点。在这种情况下,组合服务可以作为一系列独立活动的单一访问点,如为不同的业务部门执行保险风险计算或保费计算等。通过使用组合服务,使用者可不必注意计算的差异和定义特定计算路径的规则。使用者调用单个(组合)服务;例如,保单保费计算(根据输入数据)将调用特定业务部门的保单计算服务。
  • 重新组合或上下文更改。在这种情况下,组合服务的实现可以让使用者不必注意旨在满足新业务目标而进行的更改。通过这样,可以快速对新情况和服务功能的动态变化做出响应,并能够在两者之间建立联系,以帮助解决问题。此类更改通常隐藏在组合服务的接口之后,其对服务使用者的影响很小甚至没有影响。

尽管目前最流行的组合服务实现策略是编排,但人们也越来越多地关注基于事件的服务组合实现。(请参见参考资料中列出的文章“Service-Oriented Composition in BPEL4WS”和“Tools for Composite Web Services: A Short Overview”。)

事件驱动的体系结构

正逐渐变得像 SOA 一样流行(如果不是更流行的话)的新术语之一就是事件驱动的体系结构(Event-driven Architecture,EDA)。(请参见参考资料中列出的“Event-Driven Architecture Complements SOA”。)Gartner Group 定义了 SOA 和 EDA 以下的不同之处:

  • 基本 SOA
    • 具有一对一连接
    • 具有由客户机(发送方)定向的流路由
    • 通过模块层次结构采用线性执行路径
    • 流启动后,就不接受新的非预见输入
  • 基本 EDA
    • 支持多对多连接
    • 具有由接收方基于消息本身确定的控制流
    • 通过模块网络支持动态、并行、异步流
    • 能够对在不可预测的时间到来的新的外部输入做出反应

将 SOA 与 EDA 融合,可得到目前大受欢迎的另一个体系结构样式——SOA 2.0。简单来说,这个新样式需要对使用发布/订阅 (pub/sub) 样式通信的的典型点到点服务交互进行增强,如图 1 中所示。


图 1. 通过发布/订阅的服务交互
通过发布/订阅的服务交互

(请注意,点到点通信的概念并不是 SOA 本身的特征。实际上,很多 SOA 专业人员都已经将服务注册中心作为用于进行后期绑定和动态路由的机制加以引入了。它表明了用于设计 SOA 解决方案的主流方法的不足之处。)

在这种情况下,使用者将事件发布到 pub/sub 引擎(特定的主题),而该引擎会随后将这些事件交付给服务使用者(订阅了此主题)。与任何中间层类似,pub/sub 引擎提供了服务使用者和提供者之间的分离层,从而能够异常灵活地实现组合服务。可以有任意数量的使用者向相同的主题提交请求,而也可以有任意数量的提供者侦听(通过订阅)相同的主题。因此,基于 pub/sub 的服务交互能够支持真正的多对多关系。

在这种情况下,组合服务可以按照图 2 中所示加以实现。


图 2. 通过事件实现组合服务
通过事件实现组合服务

服务使用者发送初始化事件,该事件会随后交付(通过 pub/sub 引擎)给订阅此事件的一组服务。每个服务可以随后发送另一条消息,从而再次调用(仍然通过 pub/sub 引擎)另一组服务。这样的事件序列有效地定义了组合服务。通过 pub/sub 引擎实现组合服务的方式提供了非常灵活的实现。通过更改订阅到特定主题的一组服务,可以完全更改组合服务的实现。或者,以可以通过更改使用者向其发送原始事件的主题来达到相同的效果。

为了说明使用事件进行服务组合的优势,让我们看一个简单的示例。假定某家保险公司针对不同的部门有不同的索赔系统。图 3 给出了一个典型的组合服务,用于为这些索赔处理系统提供单一的入口点。


图 3. 组合索赔处理服务
组合索赔处理服务

在这种情况下,索赔处理使用者将调用组合索赔处理服务,该组合服务将应用一组规则来确定调用哪个索赔处理系统。如果引入了其他索赔系统(由于收购或增加了新的业务部门),必须修改组合索赔处理服务,以添加用于调用新索赔处理系统的规则。

如果使用事件实现相同的组合索赔服务,则解决方案的情况将有很大的不同,如图 4 中所示。


图 4. 使用事件的组合索赔处理服务
使用事件的组合索赔处理服务

在此实现中,组合索赔服务的角色由 pub/sub 引擎充当。在这种情况下,服务使用者将索赔处理请求发布到预先的主题上,而所有索赔处理系统都在侦听此主题。此请求将交付给所有索赔处理系统。(另一个选项是根据请求内容为特定索赔处理系统筛选请求。大部分 pub/sub 实现都支持这样的选项。)每个索赔处理系统都会进行检查,确定是否能够处理特定的请求,如果能够处理,则对其进行处理,并将响应发送回使用者。在这种情况下,引入其他系统并不要求对现有实现进行任何更改。新索赔系统唯一需要的就是,能够识别传入的请求,并对其进行处理。

WS-Notification 规范

直到最近,大部分 pub/sub 实现还仅限于面向消息的中间件。WebSphere MQ 和 Microsoft® MSMQ 之类的产品都直接打包了 pub/sub 引擎。企业应用程序集成消息代理的发展以及 Java 2 Platform Enterprise Edition (J2EE) 应用服务器中的 Java™ Message Service (JMS) 实现都带来了各种 pub/sub 实现。不幸的是,这些实现之间的互操作性相当差:每个实现都提供了专有实现,需要特定供应商支持。随着 Web 服务方面的不断发展,pub/sub 范式的重要性得到了进一步的重视。

得益于此,OASIS Web 服务通知技术委员会 (OASIS Web Services Notification Technical Committee) 制定了以下三个相互关联的规范(有关更多信息,请参见参考资料):

  • WS-BaseNotification。定义在插件 pub/sub 场景中使用的两个常用接口,通知生成方和订阅管理器。该规范定义了这两个接口的角色以及各自应该支持的方法。
  • WS-BrokeredNotification。定义通知代理,以便作为通知提供者和使用者之间的中间层使用。(请参见图 1。)该规范定义了通知代理接口以及此接口应该提供的方法。它还定义了发布器注册接口(及方法),以允许发布器显式地向代理注册自己。
  • WS-Topics。定义用于指定和访问发布主题的规则。

“Events and service-oriented architecture: The OASIS Web Services Notification specifications”中对各个规范及其彼此如何结合进行了很好的概述。(有关更多信息,请参见参考资料部分。)

这一组规范将 pub/sub 实现引入了 Web 服务领域。任何人都可根据其构建基于 Web 服务标准的 pub/sub 实现。

WebSphere Application Server 6.1 中的 WS-Notification 实现

新 pub/sub 实现之一就是 WebSphere Application Server (Application Server) 6.1 中的 WS-Notification 实现。我们将在本文剩下的部分中对该实现进行说明。

尽管 WebSphere Application Server 6.1 实现支持刚刚列出的所有三个规范,但本文将重点讨论作为 Application Server 的平台消息传递机制一部分实现的通知代理。(IBM 红皮书“Web Services Handbook for WebSphere Application Server Version 6.1”对 Application Server 实现进行了很好的说明,并提供了一组体系结构关系图来说明此实现如何工作。请参见参考资料。)

为 WS-Notification 设置资源

WS-Notification 服务的实现依赖于 Services Data Object (SDO) Repository(IBM 提供的应用程序),必须在设置 WS-Notification 前安装该应用程序。

在最简单的情况下,设置 WebSphere Application Server 6.1 来使用 WS-Notification 的过程非常简单。(请参见参考资料。)可以使用 Application Server 管理控制台接口进行此工作。(或者,可以通过命令行 wsadmin 脚本配置 WS-Notification。)使用管理控制台配置需要进行以下步骤:

  1. 由于 WS-Notification 是作为 Application Server 服务集成总线(Service Integration Bus,SIB)的一部分实现的,因此必须首先创建 SIB 实例和 SIB 成员(现有服务器)。图 5 显示了一个示例总线配置。

    图 5. 示例总线配置
    示例总线配置

  2. 创建了 SIB 后,可以使用该应用服务器提供的向导来创建 WS-Notification 服务器。可以从总线面板通过单击 WS-Notification services 并添加一个新通知服务来调用此向导。向导将随后完成以下步骤:
    1. 定义 WS-Notification 服务名(这唯一的作用是设置用于提供该服务的 URL 部分)和承载消息传递资源的 SIB(从下拉菜单选择)。应该在此处选择前面步骤中创建的 SIB。
    2. 为通知服务指定 jax-rpc 列表和安全设置。(我们未在示例中使用任何此类设置。)
    3. 为 WS-Notification 创建或选择服务点。如果已经定义了服务点,则可以从中选择一个。否则就需要创建一个新服务点。对于我们的示例,因为是从头创建总线实例,因此需要创建服务点。服务点创建可分为两个步骤。首先,选择 SIB 成员(从下拉菜单中选择)并为其指定一个名称。然后,选择或创建一个通知侦听器。对于我们的示例,要创建一个新侦听器。为了创建侦听器,需要指定侦听器名称、绑定(在我们的示例中为 SOAP over HTTP)、URL 根(我们使用缺省值)和 Web 服务描述语言服务端口(我们使用缺省值)。
    4. 配置 WS-Notification 永久主题命名空间。在我们的示例中,并未使用永久主题命名空间。
    5. 检查所提供的值。

运行向导完成 WS-Notification 的配置后,将看到一个与图 6 中所示类似的窗口。


图 6. WS-Notification 服务的配置
WS-Notification 服务的配置

如图 6 中所示,WS-Notification 服务包含三个实际的服务:

  • Notification Broker 是此实现的核心组件。它充当事件生成和使用应用程序间的分隔点。Application Server 提供了代理实现,支持所需的所有功能,包括活动订阅器列表的维护、订阅主题的解析和匹配、事件通知到订阅器的分发以及对订阅生命周期中的某些方面的处理。此接口是由 WS-BrokeredNotification 规范定义的。
  • Subscription Manager 支持管理使用者订阅所必需的功能。它定义消息交换,以便操作订阅资源。有两种样式的 Subscription Manager 接口:基本接口和可暂停接口。WebSphere Application Server 6.1 同时实现了这两种样式。此接口是由 WS-BaseNotification 规范定义的。
  • Publisher Registration 支持管理发布器注册的生命周期所需的功能。此接口是由 WS-BrokeredNotification 规范定义的。

在我们的代码示例中,仅使用 Notification Broker 和 Subscription Manager 服务。为了访问这些服务,我们将需要其对应的 Web 服务描述语言(Web Service Descripiton Language,WSDL)文件。幸运的是,Application Server 管理控制台支持为每个创建的服务创建以下四个 WSDL 文件:

  • xxxPortTypes.wsdl 包含给定服务的端口定义。
  • xxxBinding.wsdl relies on xxxPortTypes.wsdl 包含给定端口的绑定定义。
  • xxxService.wsdl relies on xxxBinding.wsdl 包含给定绑定的服务定义(带创建服务时的正确 URL 地址)。
  • xxx NonBound.wsdl 依赖于 xxxPortTypes.wsdl,包含给定端口的绑定和服务定义(带服务的通用 URL 地址)。

有了所需的 WSDL 文件后,我们就可以对示例代码进行处理,以了解如何使用 WS-Notification 服务。

(请注意:从技术上来说,WebSphere Application Server 所产生的 WSDL 文件可以直接用于生成代码(见下文),但只有在连接到 Internet 的情况下才能够这样做。文件使用的是所导入文件的完全限定计算机名称和 URL 位置。如果您和我一样,喜欢在断开网络连接的情况下编写代码(例如在火车上或晚上在家里的电视机前),这将无法工作。为了能在断开网络的情况下使用这些示例,我在 xxxService.wsdl 文件中的服务端点地址定义中将完全限定计算机名称替换为了本地主机。我还将所有所需的文件打包到代码的 WSDL 项目中,并对相应文件中的位置参数进行了更改,以使其指向本地版本。)

WS-Notification 使用者实现

使用者实现包含以下三个主要部分:

  • 通知使用者
  • 通知订阅器
  • 通知退订器(可选)

下面的部分将对其分别进行介绍。

通知使用者

通知使用者是接收通知(所发布信息)的 Web 服务。此服务必须实现 WS-BaseNotification 规范定义的 notify 方法。此服务的 WSDL 文件必须基于 OASIS 提供的 WSDL 文件创建,以定义 WS-BaseNotification 功能,如清单 1 中所示。(在本文随附的代码中也提供了此文件。)


清单 1. 通知使用者的 WSDL 文件
				
<?xml version="1.0" encoding="UTF-8">
<wsdl:definitions targetNamespace="com.cna.wsn/consumer" 
    xmlns="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:wsn-bw="http://docs.oasis-open.org/wsn/bw-2" 
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" 
    xmlns:tns="com.cna.wsn/consumer" 
    xmlns:wsdl1="http://docs.oasis-open.org/wsn/bw-2">
    <wsdl:import namespace="http://docs.oasis-open.org/wsn/bw-2" location="bw-2.wsdl" />
    <wsdl:binding name="NotificationConsumerBinding" type="wsn-bw:NotificationConsumer">
        <wsdlsoap:binding style="document" 
                                    transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="Notify">
            <wsdlsoap:operation soapAction="" />
            <wsdl:input>
                <wsdlsoap:body use="literal" />
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="NotificationConsumerService">
        <wsdl:port name="NotificationConsumerPort" 
                                          binding="tns:NotificationConsumerBinding">
            <wsdlsoap:address location="http://myserver.cna.com/Consumer" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
      

可以使用此 WSDL 文件进行标准 Web 服务提供者生成(JavaBeans 框架)。清单 2 给出了通知使用者的一个非常简单的实现(删除了重复代码)。

(请注意:我使用了 WebSphere Application Server Toolkit (AST) 6.1 来生成本文所需的代码。)


清单 2. 通知使用者的实现
				
package wsn.consumer.cna.com;

import java.util.Iterator;
import javax.xml.rpc.server.ServletEndpointContext;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFactory;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.oasis_open.docs.wsn.bw_2.NotificationConsumer;
import com.ibm.websphere.sib.wsn.NotificationMessage;
import com.ibm.websphere.sib.wsn.TopicExpression;
import com.ibm.websphere.wsaddressing.EndpointReference;

public class NotificationConsumerBindingImpl implements NotificationConsumer{

    public void notify(NotificationMessage[] notificationMessage, SOAPElement[] any) 
                                                     throws java.rmi.RemoteException {
	
        System.out.println("Receiving Notification ...");
				
        // Process each NotificationMessage
        for (int i=0; i<notificationMessage.length; i++) {                
            NotificationMessage message = notificationMessage[i];
                
            // Get the contents of the message
            SOAPElement messageContent = message.getMessageContents();
            System.out.println("Notification message:");
            try{
                OutputFormat ouf = new OutputFormat();
                XMLSerializer serializer = new XMLSerializer(System.out,ouf);
                serializer.serialize(messageContent);
                System.out.println(" ");
            }
            catch(Exception e){
                e.printStackTrace();
            }
    		
            // Get the expression indicating which topic the message is associated with
            TopicExpression topic = message.getTopic();
            System.out.println("Notification topic:");
            System.out.println(topic.getTopic());
                
            // Get a reference to the producer (this is optional and so may be null)
            EndpointReference producerRef = message.getProducerReference();
            if(producerRef != null){
                System.out.println("Producer Reference: " + producerRef);
                try{
                    com.ibm.wsspi.wsaddressing.EndpointReference ref = 
                          (com.ibm.wsspi.wsaddressing.EndpointReference)producerRef;
                    SOAPFactory sFactory = SOAPFactory.newInstance();
                    SOAPElement sel = sFactory.createElement("root");
                    SOAPElement root = ref.getSOAPElement(sel);
                    ...................................
                }
                catch(Exception e){
                    e.printStackTrace();
                }
            }
                
            // Get a reference to the subscription (this is optional and so may be null)
            EndpointReference subscriptionRef = message.getSubscriptionReference();
            if(subscriptionRef != null){
                System.out.println("Subscription Reference: " + subscriptionRef);
                .............................................
            }
        }
        
        // Get information associated with the message
        if(any != null){
            for(int i=0; i < any.length; i++){
                SOAPElement extra = any[i];
                if(extra != null){
                    System.out.println("Additional information:");
					
                .........................................
                }
            }
         }    
     }
}
      

清单 2 中所示的类的完整版本可以在本文随附的示例代码中找到。

按照服务通知使用者 WSDL 的定义,notify 方法接受两个参数:包含通知消息的数组和包含其他信息的数组。清单 2 中所示的实现将直接输出两个数组的内容。每个通知消息都具有以下四个部分:

  • 业务消息。通知发布器提交的消息。
  • 主题。业务消息所关联的主题。
  • 订阅引用。消息代理中定义给定订阅的引用。
  • 生成方引用。定义发送消息的生成方的引用;只有生成方显式地向代理注册才会出现。

按照 Web Services Base Notification 1.3. 规范(请参见参考资料)中的定义,其他信息表示开放内容,用于放置构建于 BaseNotification 之上的扩展可能需要的元素,包括提供额外筛选机制的扩展。我们的实现目前不使用此数组。

在我们的 WS-Notification 实现中,业务消息和其他消息都表示为 SOAPElement。为了打印(处理)SOAPElement,我使用了 WebSphere Application Server 6.1 支持的新版本 JAX-RPC,其中的 SOAPElement 派生自 XML 节点。可以使用现有 Apache XERCES 中的支持直接打印节点。两个引用都作为 EndpointReference 类实现。EndpointReference 类不透明,并不提供任何用于进行其处理的方法。幸运的是,在 WebSphere Application Server 6.1 实现中,此类派生自 IBM 特有的 com.ibm.wsspi.wsaddressing.EndpointReference 类,而后者提供了将其内容转换为 SOAPElement 的方法。我就是使用此 SOAPElement(表示 EndpointReference 的内容)来处理和打印引用的。

通知订阅器

为了通知使用者能够开始接收通知,代理应该对此有所了解。订阅是向代理提供使用者信息的过程——其端点地址和所感兴趣的主题。订阅方法由 NotificationBroker 接口提供支持,这意味着通知订阅器能够作为 NotificationBroker 服务的服务使用者实现。我是基于 NotificationBroker WSDL 文件创建的这个使用者。(请参见为 WS-Notification 设置资源,以了解有关从应用服务器下载 WSDL 文件的说明。)

清单 3 给出了通知订阅器的一个非常简单的实现(删除了重复代码)。


清单 3. 通知订阅器实现
				
package com.cna.notification.tester;

import java.net.URI;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.xml.rpc.holders.CalendarHolder;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFactory;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.oasis_open.docs.wsn.b_2.holders.AnyArrayHolder;
import org.w3.www.holders.EndpointReferenceTypeHolder;
import com.cna.services.container.epContainer;
import com.ibm.www.NotificationBroker;
import com.ibm.www.NotificationBrokerProxy;
import com.ibm.websphere.sib.wsn.AbsoluteOrRelativeTime;
import com.ibm.websphere.sib.wsn.Filter;
import com.ibm.websphere.sib.wsn.TopicExpression;
import com.ibm.wsspi.wsaddressing.EndpointReference;
import com.ibm.wsspi.wsaddressing.EndpointReferenceManager;


public class subscriberBasic {
	
    private NotificationBroker stub = null;
	
    public subscriberBasic(){

       try{

//          Get a stub for the port on which you want to invoke operations
            NotificationBrokerProxy proxy = new NotificationBrokerProxy();
            stub = proxy.getNotificationBroker();
			
        }
        catch(Exception e){
            System.out.println("Error creating service stub");
            e.printStackTrace();
        }
    }

    public void subscribe(String ep){
		
        if(stub == null)
            return;
		
//      Create the ConsumerReference. 
//      This contains the address of the consumer Web service which is being subscribed
		EndpointReference consumerEPR = null;
        try{
            URI epURI = new URI(ep);
            consumerEPR = EndpointReferenceManager.createEndpointReference(epURI);			
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
        }
        catch(Exception e){
            System.out.println("Error creating notification URI");
            e.printStackTrace();
        }
        if(consumerEPR == null)
            return;
		
//      Create the Filter. 
//      This provides the name of the topic to which you want to subscribe the consumer
        Filter filter = new Filter();

//      Create a topic expression and add it to the filter. The prefixMappings are 
//      mappings between namespace prefixes and their 
//      corresponding namespaces for prefixes used in the expression
        Map prefixMappings = new HashMap();
        prefixMappings.put("test", "http://www.cna.com/test/topic");
        TopicExpression exp = 
		      new TopicExpression(TopicExpression.SIMPLE_TOPIC_EXPRESSION, 
		                                      "test:TestTopic", prefixMappings);        
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
        filter.addTopicExpression(exp);
        
//      Create the InitialTerminationTime. 
//      This is the time when the subscription will terminate. 
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.YEAR, 1);
        AbsoluteOrRelativeTime initialTerminationTime = new AbsoluteOrRelativeTime(cal);
		        
//      Create the Policy information
        SOAPElement[] policyElements = null;
		        
//      Create holders to hold the multiple values returned from the broker:
//      The subscription reference
        EndpointReferenceTypeHolder subscriptionRefHolder = 
		                                         new EndpointReferenceTypeHolder();
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
		        
//      The current time at the broker
        CalendarHolder currentTimeHolder = new CalendarHolder();
		        
//      The termination time for the subscription
        CalendarHolder terminationTimeHolder = new CalendarHolder();
		        
//      Any additional elements
        AnyArrayHolder anyOtherElements = new AnyArrayHolder();

        try{
//          Invoke the Subscribe operation by calling the associated method on the stub
            System.out.println("Sending message to the Broker... ");
            stub.subscribe(consumerEPR,
		                       filter,
		                       initialTerminationTime,
		                       policyElements,
		                       anyOtherElements,
		                       subscriptionRefHolder,
		                       currentTimeHolder,
		                       terminationTimeHolder);
		        
//          Get the returned values:
//          An endpoint reference for the subscription is required for subsequent 
//          lifetime management of the subscription, for example pausing or canceling
//          of the subscription  
            com.ibm.websphere.wsaddressing.EndpointReference subscriptionRef = 
			                                            subscriptionRefHolder.value;
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
            System.out.println("Subscription Reference: " + subscriptionRef);
            epContainer.saveReference(subscriptionRef,0);
	..............................................        	
//          The current time at the broker
            Calendar currentTime = currentTimeHolder.value;
            System.out.println("Current Broker time: " + currentTime.toString());
		        
//          The termination time of the subscription
            Calendar terminationTime = terminationTimeHolder.value;
            System.out.println("Subscription termination time: " + 
                                                    terminationTime.toString());
		        
//          Any other information
            SOAPElement[] otherElements = anyOtherElements.value;
            if(otherElements != null){
                  ........................................
            }
        }
        catch(Exception e){
            System.out.println("Error invoking notification broker");
            e.printStackTrace();
        }
    }
}
     

清单 3 中所示的类的完整版本可以在本文随附的示例代码中找到。

在此实现中,subscriber 类具有两个方法——一个简单的构造函数(创建用于与 Notification Broker 交互的本地存根)和 subscribe 方法(对代理调用订阅方法)。

按照 Web Services Base Notification 1.3. 规范(请参见参考资料)的定义,subscribe 方法将向通知代理提交以下参数:

  • 使用者端点引用元素,采用 EndpointReference 的形式。可以使用 WebSphere Application Server 实现提供的 EndpointReferenceManager 类来根据通知使用者用于侦听通知消息的 URL 创建此引用。
  • 订阅筛选器,允许订阅器定义通知使用者感兴趣的主题列表。此列表是作为附加到该筛选器的主题表达式列表定义的。主题表达式包含表达式类型、完全限定主题名称和前缀映射。主题前缀名是一个映射,将定义一组前缀及与这些前缀关联的 URL。
  • 订阅终止时间,包含请求方对订阅初始终止时间的建议。如果这个时间为空,则认为订阅是无期限的。
  • 策略元素,一组以应用程序特定的方式使用的开放组件,用于指定策略相关的需求/与订阅请求关联的断言。WebSphere Application Server 实现如何使用此参数并不十分清楚。

此方法返回以下参数:

  • 订阅端点引用,包含定义通知代理中的订阅的值。此值用于控制订阅生命周期,例如用于暂停订阅或暂停退订状态(请参见通知退订器)。清单 3 所示的代码将打印此值,并会存储此值以供通知退订器稍后使用。
  • 当前时间和订阅终止时间,基于当前订阅请求在代理中定义。清单 3 中所示的代码直接打印这两个值。
  • 其他参数是用于将来对实现进行扩展的可扩展机制。

清单 3 中给出了一个简单的订阅实现,该实现以通知使用者 URL 作为输入参数,并会为使用者订阅预先定义的主题。它将随后获取订阅响应并打印(和上面的通知使用者一样使用相同的方法)。

通知退订器

为了让通知使用者停止接收通知,应该显式地从代理退订。unsubscribe 方法由 SubscriptionManager 接口提供支持,而这意味着可以将通知退订器作为 SubscriptionManager 服务的服务使用者加以实现。我基于 SubscriptionManager WSDL 文件创建了此使用者。(请参见为 WS-Notification 设置资源,以了解有关从应用服务器下载 WSDL 文件的说明。)

清单 4 给出了通知退订器的一个简单实现。


清单 4. 退订实现
				
package com.cna.notification.tester;

import java.net.URI;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFactory;
import com.cna.services.container.epContainer;
import com.ibm.ws.wsaddressing.WSAConstants;
import com.ibm.websphere.wsaddressing.EndpointReference;
import com.ibm.www.SubscriptionManager;
import com.ibm.www.SubscriptionManagerProxy;

public class unsubscriber {
	
    private SubscriptionManager stub = null;
	
    public unsubscriber(){

        try{

//          Get a stub for the port on which you want to invoke operations
            SubscriptionManagerProxy proxy = new SubscriptionManagerProxy();
            stub = proxy.getSubscriptionManager();
        }
        catch(Exception e){
            System.out.println("Error creating service stub");
            e.printStackTrace();
        }
    }

    public void unsubscribe(int subs){
		
        if(stub == null)
            return;
	
//      Get the subscriptionReference. 		
        EndpointReference consumerEPR = epContainer.getReference(subs);
        if(consumerEPR == null){
            System.out.println("Unable to get ID");		        
            return;
        }
		
        try{
//          Invoke the Unsubscribe operation by calling the associated method
            System.out.println("Sending message to the Subscription Manger... ");
            ((javax.xml.rpc.Stub)stub)._setProperty(
            		WSAConstants.WSADDRESSING_DESTINATION_EPR, consumerEPR);
            stub.unsubscribe(null);
            System.out.println("Done unsbscribing ");		        
        }
        catch(Exception e){
            System.out.println("Error invoking notification broker");
            e.printStackTrace();
        }
    }
}
     

与通知订阅器类似,此类包含两个方法——一个构造函数(创建用于与 SubscriptionManager 服务进行通信的本地存根)和一个 unsubscribe 方法(实现实际的退订操作)。此操作接受端点地址(由订阅操作返回)和可扩展参数数组(本实现中未使用)作为参数。此操作会在通知代理中删除订阅。端点地址并不是作为有效负载提交的,而是作为 Web 服务寻址信息提交到 Header 中。会在 SubscriptionManager 服务存根上将其设置为属性,并随后由 IBM Web 服务运行时实现转换为 Header。

WS-Notification 发布器实现

WS-Notification 发布器是用于发布通知消息的应用程序。发布对 NotificationBroker 中的特定主题进行,而 NotificationBroker 会随后将此通知发送到对给定主题感兴趣的所有订阅器。publish 方法由 NotificationBroker 接口提供支持,而这意味着可以将通知订阅器作为 NotificationBroker 服务的服务使用者加以实现。我已基于 NotificationBroker WSDL 文件创建了这个使用者。(请参见为 WS-Notification 设置资源,以了解有关从应用服务器下载 WSDL 文件的说明。)

清单 5 给出了通知订阅器的一个非常简单的实现(删除了重复代码)。


清单 5. 发布器实现
				
 package com.cna.notification.tester;

import java.util.HashMap;
import java.util.Map;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPElement;
import com.ibm.websphere.sib.wsn.NotificationMessage;
import com.ibm.websphere.sib.wsn.TopicExpression;
import com.ibm.www.NotificationBroker;
import com.ibm.www.NotificationBrokerProxy;

public class publisher {

    private NotificationBroker stub = null;
	
    public publisher(){

        try{
//          Get a stub for the port on which you want to invoke operations
            NotificationBrokerProxy proxy = new NotificationBrokerProxy();
            stub = proxy.getNotificationBroker();
        }
		catch(Exception e){
            System.out.println("Error creating service stub");
            e.printStackTrace();
        }
    }

    public void publish(){
		
        if(stub == null)
            return;
//      Create the message contents for a notification message
        SOAPElement messageContents = null;
        try{
            SOAPFactory soapFactory = SOAPFactory.newInstance();
            messageContents = soapFactory.createElement("MyData", 
			                            "cnaData", "http://www.cna.com/data");
            messageContents.addTextNode("Some notification data");
        }
        catch(Exception e){
            System.out.println("Error creating service stub");
            e.printStackTrace();
        }
                
//      Create a notification message from the contents
        NotificationMessage message = new NotificationMessage(messageContents);

//      Add a topic expression to the notification message 
        Map prefixMappings = new HashMap();
        prefixMappings.put("test", "http://www.cna.com/test/topic");
        TopicExpression exp = 
		      new TopicExpression(TopicExpression.SIMPLE_TOPIC_EXPRESSION, 
		                                   "test:TestTopic", prefixMappings);        
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
        message.setTopic(exp);

//      Invoke the Notify operation by calling the associated method on the stub
        try{
            System.out.println("Publishing Notification ...");
            stub.notify(new NotificationMessage[] { message }, null);
            System.out.println("Publishing successful");
        }
        catch(Exception e){
            System.out.println("Error publishing message");
            e.printStackTrace();
        }
    }
}
      

与本文中已经给出的前面两个类(请参见清单 3清单 4)相似,该类也有两个方法:构造函数(创建用于与 NotificationBroker 服务通信的本地存根)和 unsubscribe publish 方法(实现实际的发布操作)。

按照 Web Services Base Notification 1.3. 规范(请参见参考资料)的定义,publish 方法将向通知代理提交以下参数:

  • 包含一条或多条通知消息的数组。(通知消息的内容按照上面通知使用者部分对应的部分中所述定义。)
  • 可选元素数组保留用于放置扩展可能需要的元素。当前 WebSphere Application Server 6.1 实现并不使用这些元素。

打包并运行示例代码

除了本文前面描述的代码外,该实现(请参见清单 2清单 3清单 4清单 5)还包括另外两个类:debug handlerepContainer,这两个类组织在名为 DebugHandler 的简单 Java 项目中。DebugHandler 是一个简单的 JAX-RPC 处理程序,用于打印服务使用者或提供者发出的 SOAP 消息。该处理程序的代码如清单 6 中所示。


清单 6. DebugHandler 代码
				
package com.cna.service.handler;

import javax.xml.namespace.QName;
import javax.xml.rpc.handler.GenericHandler;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.SOAPMessage;

public class debugHandler extends GenericHandler {

    public QName[] getHeaders() {
         return null;
    }

    public boolean handleRequest(MessageContext mc){
		
        System.out.println("Handling message request");
        printMessage(mc);
        System.out.println("Done handling message request");
        return true;
    }

    public boolean handleResponse(MessageContext mc){

        System.out.println("Handling message response");
        printMessage(mc);
        System.out.println("Done handling message response");
        return true;
	}
	
    private void printMessage(MessageContext mc){
		
        try{
            SOAPMessageContext smc = (SOAPMessageContext) mc;
            SOAPMessage sMessage = smc.getMessage();
            sMessage.writeTo(System.out);
            sMessage.saveChanges();
        }
        catch(Exception e){
            System.out.println("Error printing SOAP message");
            e.printStackTrace();
        }
    }
}
      

此处理程序实现两个公共方法:handleRequesthandleResponse。这些方法由 WebSphere Application Server Web 服务运行时作为服务调用管道(使用者端和提供者端)的一部分进行调用。其中的每个方法都会获取 SOAP 消息(请求或响应),并将其输出到控制台。(可以不使用 DebugHandler,而转而使用 TCP Monitor 来捕获 SOAP 消息的内容。)

epContainer 是一个简单类,用于在订阅器和退订器实现间传递订阅引用。(请参见清单 3清单 4)。epContainer 类的实现如清单 7 中所示。


清单 7. epContainer 实现
				
package com.cna.services.container;

import com.ibm.websphere.wsaddressing.EndpointReference;

public class epContainer {
		
    private static EndpointReference[] references = new EndpointReference[10];
	
    public static void saveReference(EndpointReference subscriptionRef, int id){
		
        references[id] = subscriptionRef;
    }

    public static EndpointReference getReference(int id){
		
        return references[id];
    }
}
      

此类包含订阅引用数组和两个方法(用于根据其编号保存和检索引用)。

我们的简单示例构建为四个动态 Web 项目:notificationProducer、notificationConsumer、notificationSubscribernotificationUnSubscriber。其中的每个项目都引用 DebugHandlers。此引用必须在两个地方定义——在项目属性/Java 构建路径中和项目清单中。

虽然 notificationConsumer 实现了一个通知 Web 服务,而将由 WebSphere Application Server 通知代理调用此服务,但还必须显式调用本文所给出的其他代码。我创建了一组 JavaServer Page (JSP),以用于调用各个类(从清单 3清单 5)——每个类对应一个 JSP。清单 8 给出了此类 JSP(调用 notificationProducer 的 JSP)的一个示例。


清单 8. 用于调用 notificationPublisher 的 JSP
				
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<%@ page 
language="java"
%>
<jsp:useBean id="publisher" scope="session" 
                             class="com.cna.notification.tester.publisher" />
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>WSN Publisher</title>
</head>
<body>
<P>Publishing to WSN ... </P>
<% publisher.publish(); %>
</body>
</html>
      

使用了类似的 JSP 页来调用 notificationSubscribernotificationUnSubscriber 类。

这些 Web 项目组织为两个企业应用程序——notificationProducerEAR(包含第一个 Web 项目,即 notificationProducer)和 notificationConsumerEAR(包含其他三个 Web 项目)。

对我们创建的软件进行的最简单测试是按以下顺序运行设计的所有代码,即:

  • 提交到代理
  • 发布通知
  • 退订

接下来将对每个步骤的执行进行说明。

提交到代理

为了通知使用者提交到代理,需运行 notificationSubscriber JSP。当调用此 JSP 时,会将相应的 SOAP 消息(如清单 9 中所示)发送给代理。


清单 9. 订阅请求消息
				
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Header />
    <soapenv:Body>
        <p38:Subscribe xmlns:p38="http://docs.oasis-open.org/wsn/b-2">
           <ConsumerReference xmlns="http://docs.oasis-open.org/wsn/b-2">
                <wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing">
          http://localhost:9080/notificationConsumer/services/NotificationConsumerPort
                </wsa:Address>
            </ConsumerReference>
            <Filter xmlns="http://docs.oasis-open.org/wsn/b-2">
                <b2:TopicExpression
                    Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple"
                    xmlns:test="http://www.cna.com/test/topic"
                    xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                    test:TestTopic
                </b2:TopicExpression>
            </Filter>
            <InitialTerminationTime xmlns="http://docs.oasis-open.org/wsn/b-2">
                2007-09-11T13:07:18.332Z
            </InitialTerminationTime>
        </p38:Subscribe>
    </soapenv:Body>
</soapenv:Envelope>
	  

接收到此消息时,代理将建立一个订阅,并发送响应,响应中包含对应的订阅引用(关于订阅的完整信息)。我们将稍后使用此订阅引用来从代理退订。清单 10 给出了表示建立订阅时的代理响应的 SOAP 消息。


清单 10. 订阅响应消息
				
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
    <soapenv:Header />
    <soapenv:Body>
        <p38:SubscribeResponse
            xmlns:p38="http://docs.oasis-open.org/wsn/b-2">
            <SubscriptionReference
                xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                xmlns="http://docs.oasis-open.org/wsn/b-2"
                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:wsa="http://www.w3.org/2005/08/addressing"
                xmlns:p38="http://docs.oasis-open.org/wsn/b-2">
                <wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing">
        http://MXPCHWKAJXS.cna.com:9080/NotificationListener/soaphttpengine
        /myBus%2FMyPubSubSubscriptionManager%2FWSNServicePointSubscriptionManagerPort
                </wsa:Address>
                <wsa:ReferenceParameters
                    xmlns:wsa="http://www.w3.org/2005/08/addressing">
                    <wsaucf:RoutingInformation
                        xmlns:wsaucf="http://ucf.wsaddressing.ws.ibm.com">
                        <wsaucf:HAClusterId>
                            00000000020004747970650010575341465f5349425f4d455f
                            55554944000475756964001036383741343237323035383844423638
                        </wsaucf:HAClusterId>
                    </wsaucf:RoutingInformation>
                    <wsaucf:VirtualHostName
                        xmlns:wsaucf="http://ucf.wsaddressing.ws.ibm.com">
                        default_host
                    </wsaucf:VirtualHostName>
                    <subscriptionId
                        xmlns="http://www.ibm.com/websphere/wsn/reference">
                        sub_8bb445adce37a29598f6d3e2_10d9cfd2503_3
                    </subscriptionId>
                    <messagingEngineUuid
                        xmlns="http://www.ibm.com/websphere/wsn/reference">
                        687A42720588DB68
                    </messagingEngineUuid>
                </wsa:ReferenceParameters>
            </SubscriptionReference>
            <p38:CurrentTime>2006-09-11T13:07:18.403Z</p38:CurrentTime>
            <p38:TerminationTime>
                2007-09-11T13:07:18.332Z
            </p38:TerminationTime>
        </p38:SubscribeResponse>
    </soapenv:Body>
</soapenv:Envelope>
	  

此外,还可以使用 WAS 管理控制台使得订阅生效,如图 7 中所示。


图 7. 管理控制台中的通知订阅
管理控制台中的通知订阅

发布和接收通知

通过调用 notificationProducer JSP,可将消息发布到代理。当调用此 JSP 时,会将清单 11 中所示的 SOAP 消息发送给代理。


清单 11. 发布消息
				
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Header />
    <soapenv:Body>
        <p38:Notify xmlns:p38="http://docs.oasis-open.org/wsn/b-2">
            <NotificationMessage xmlns="http://docs.oasis-open.org/wsn/b-2">
                <b2:Topic
                    Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple"
                    xmlns:test="http://www.cna.com/test/topic"
                    xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                    test:TestTopic
                </b2:Topic>
                <b2:Message xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                    <cnaData:MyData xmlns:cnaData="http://www.cna.com/data">
                        Some notification data
                    </cnaData:MyData>
                </b2:Message>
            </NotificationMessage>
        </p38:Notify>
    </soapenv:Body>
</soapenv:Envelope>
	  

代理接收到此消息时,会检查对消息所发布到的主题的订阅,并将与清单 12 所示类似的通知消息发送给所有活动订阅器。


清单 12. 通知消息
				
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
    <soapenv:Header/>
    <soapenv:Body>
        <p38:Notify xmlns:p38="http://docs.oasis-open.org/wsn/b-2">
            <NotificationMessage xmlns="http://docs.oasis-open.org/wsn/b-2">
                <b2:SubscriptionReference
                    xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                        <wsa:Address 
                        xmlns:wsa="http://www.w3.org/2005/08/addressing">
    http://MXPCHWKAJXS.cna.com:9080/NotificationListener/soaphttpengine/
    myBus%2FMyPubSubSubscriptionManager%2FWSNServicePointSubscriptionManagerPort
                    </wsa:Address>
                    <wsa:ReferenceParameters
                        xmlns:wsa="http://www.w3.org/2005/08/addressing">
                        <wsaucf:RoutingInformation
                            xmlns:wsaucf="http://ucf.wsaddressing.ws.ibm.com">
                            <wsaucf:HAClusterId>
                                00000000020004747970650010575341465f5349425f4d455f555549
                                44000475756964001036383741343237323035383844423638
                            </wsaucf:HAClusterId>
                        </wsaucf:RoutingInformation>
                        <wsaucf:VirtualHostName
                            xmlns:wsaucf="http://ucf.wsaddressing.ws.ibm.com">
                            default_host
                        </wsaucf:VirtualHostName>
                        <subscriptionId
                            xmlns="http://www.ibm.com/websphere/wsn/reference">
                            sub_8bb445adce37a29598f6d3e2_10d9cfd2503_3
                        </subscriptionId>
                        <messagingEngineUuid
                            xmlns="http://www.ibm.com/websphere/wsn/reference">
                            687A42720588DB68
                        </messagingEngineUuid>
                    </wsa:ReferenceParameters>
                </b2:SubscriptionReference>
                <b2:Topic
                    Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple"
                    xmlns:wsntopprefix="http://www.cna.com/test/topic"
                    xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                    wsntopprefix:TestTopic
                </b2:Topic>
                <b2:Message
                    xmlns:b2="http://docs.oasis-open.org/wsn/b-2">
                    <cnaData:MyData
                        xmlns:cnaData="http://www.cna.com/data">
                        Some notification data
                    </cnaData:MyData>
                </b2:Message>
            </NotificationMessage>
        </p38:Notify>
    </soapenv:Body>
</soapenv:Envelope>
	  

从代理退订

最后,正如前面提到的,通知使用者必须显式地从代理退订,否则在订阅过期前都会继续接收到代理发出的通知。notificationUnsubscriber JSP 允许显式地从代理退订使用者。调用此 JSP 时,会向代理发送清单 13 中所示的 SOAP 消息。


清单 13. 退订消息
				
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Header />	
    <soapenv:Body>
        <p38:Unsubscribe xmlns:p38="http://docs.oasis-open.org/wsn/b-2" />	
    </soapenv:Body>
</soapenv:Envelope>
	  

清单 13 的代码并不完整。其中仅包含消息主体,但不包含 Header。这是因为在 WebSphere Application Server Web 服务管道实现中,会在将存根属性(请参见清单 4)转换为附加 Header 前调用调试处理程序(清单 6)。

其他增强功能和选项

WebSphere Application Server 6.1 的 WS-Notification 规范实现并未尝试发现并消除完全相同的订阅请求。因此,即使多个订阅完全相同,仍然会在通知代理中注册为不同的订阅。这意味着单个发布消息可能会导致相同通知使用者接收到多个通知。在有些情况下这可能是个缺点,而在有些情况下则是非常有用的功能。

例如,当实际通知使用者无法支持 WebSphere Application Server 6.1 使用的协议(HTTP 或 JMS)或编码 (SOAP) 时,可以使用通知使用者作为代理(请参见图 8)。在这种情况下,使用者可以使用自己特有的(私有)通信通道来与通知代理 (proxy) 通信,而后者可以代表“实际”使用者订阅和接收事件。所有通知的发布都提交到通知代理 (proxy),后者在此情况下负责将通知交付到恰当的使用者(通常会首先将通知转换为特定使用者所需的格式和/或传输协议)。


图 8. 基于代理 (proxy) 的分布/订阅
基于代理 (proxy) 的分布/订阅

Application Server 6.1 允许将自定义参数与使用者引用关联,从而提供了实现此设计模式的一流解决方案。清单 14 显示了用于创建自定义参数并将其与使用者引用关联的代码片段。


清单 14. 将自定义参数与端点引用关联
				
EndpointReference consumerEPR = null;
QName pname = new QName("http://www.cna.com/data/name","name", "name");
try{
    URI epURI = new URI(ep);
    consumerEPR = EndpointReferenceManager.createEndpointReference(epURI);
			
    // Add custom parameter to the consumer reference
    SOAPFactory soapFactory = SOAPFactory.newInstance();
    SOAPElement param = soapFactory.createElement("param", "param", 
										"http://www.cna.com/data/param");
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
    param.addTextNode("Some parameter data");
    consumerEPR.setReferenceParameter(pname,param);
}
catch(Exception e){
    System.out.println("Error creating notification URI");
    e.printStackTrace();
}
	  

通过将清单 14 中所示的代码添加到订阅实现(清单 3),可向 consumerReference 添加一个额外的元素,如清单 15 中所示。


清单 15. 使用者引用请求中的自定义元素
				
<ConsumerReference xmlns="http://docs.oasis-open.org/wsn/b-2">
    <wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing">
        http://localhost:9080/notificationConsumer/services/NotificationConsumerPort
    </wsa:Address>
    <wsa:ReferenceParameters xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <name:name xmlns:name="http://www.cna.com/data/name">
            <param:param xmlns:param="http://www.cna.com/data/param">
                Some parameter data
            </param:param>
        </name:name>
    </wsa:ReferenceParameters>
</ConsumerReference>
	  

随后会通过(此订阅的)每个通知消息以 SOAP Header 的形式传播此自定义属性,如清单 16 中所示。


清单 16. 通知消息中采用 SOAP Header 形式的自定义属性
				
<soapenv:Header>
	<name:name wsa:IsReferenceParameter="true"
			xmlns:name="http://www.cna.com/data/name"
			xmlns:wsa="http://www.w3.org/2005/08/addressing">
		<param:param xmlns:param="http://www.cna.com/data/param">
			Some parameter data
		</param:param>
	</name:name>
</soapenv:Header>
	  

为了访问此 Header,通知使用者实现应该实现 JAX-RPC 定义的 javax.xml.rpc.server.ServiceLifecycle 接口中的方法,如清单 17 中所示。


清单 17. 更改通知使用者以访问自定义属性
				
public class NotificationConsumerBindingImpl 
                           implements NotificationConsumer, ServiceLifecycle{

    protected MessageContext context;

    public void init(Object ctx){

        System.out.println("Inside Service init method");
        // Initialize message context for the service use
        ServletEndpointContext sc = (ServletEndpointContext)ctx;
        if(ctx != null)
            context = sc.getMessageContext();
        else
            context = null;
    }

    public void destroy(){

        System.out.println("Inside Service destroy method");
    }
	
    public void notify(NotificationMessage[] notificationMessage, SOAPElement[] any) 
	                                                throws java.rmi.RemoteException {

        System.out.println("Recieving Notification ...");
		
        try{
            SOAPMessageContext smc = (SOAPMessageContext) context;
            SOAPMessage sMessage = smc.getMessage();
            SOAPHeader header = sMessage.getSOAPHeader();
            if(header != null){
                Iterator elements = header.getChildElements();
                while(elements.hasNext()){
                    SOAPElement hElement = (SOAPElement)elements.next();
                    System.out.println("Additional SOAP header:");
                    OutputFormat ouf = new OutputFormat();
                    XMLSerializer serializer = new XMLSerializer(System.out,ouf);
                    serializer.serialize(hElement);
                    System.out.println(" ");
                }
            }
        }
        catch(Exception e){
            System.out.println("Error getting additional SOAP headers");
            e.printStackTrace();
        }
    .................................
    }
}
	  

由于额外的参数是作为常规 SOAPElement 的一部分表示的(在 XML 树中有效),因此可以表示所需的任何信息,包括实际通知使用者的端点地址(图 8)、映射要求和系数等等。这样就能构建非常轻量级的无状态代理 (proxy) 来支持多种传输和转换选项。

结束语:有价值的折衷带来更大的灵活性

通过使用 WebSphere Application Server 6.1 中的 WS-Notification 代理,可以在 SOA 中创建用于发布和订阅消息传递的基于标准的灵活可扩展实现。它非常便于配置、使用和编程。

共享本文...

digg Digg 本文章
del.icio.us发布到 del.icio.us
Slashdot Slashdot 一下!

还需要对一些现有 SOA 实践进行反思。尽管许多书籍文章都支持使用其他方式,但很多当前的 SOA 实现都使用强类型通信替代语义消息传递。用于设计服务接口的常见方法(并不一定是正确的方法)是以 Java 类的形式对其进行定义,并随后基于其生成 WSDL 文件。此类方法可最小化开发人员处理 XML 和 WSDL 的需求,将 XML 封送/取消封送工作转移到 Web 服务运行时,从而简化了开发人员的工作。不过,这通常会对服务实现的可操作性造成影响。

直接公开本地应用程序模型通常会导致在整个系统中进行大规模转换工作。WS-Notification 规范将业务消息定义为 SOAPElement(XML 树),如果使用此规范,则需要采用不同的方法处理接口定义。与企业应用程序集成(Enterprise Application Integration,EAI)最佳实践类似,这种体系结构要求使用可供所有使用者和提供者共享的规范消息定义。此类规范消息通常会定义企业语义(业务对象定义的公共集),从而支持不同使用者和提供者之间实现无缝的互操作能力。

此外,此方法避免了使用 Web 服务运行时进行 XML 处理。开发人员需要负责以编程的方式在 SOAPElement 和基础应用程序对象之间进行转换。尽管乍看起来似乎是个复杂的服务实现,但最终会带来灵活得多的系统。此类方法不再使用 XML 为对象封送提供开销巨大的支持,从而释放了 XML 语义消息传递的强大功能。各个参与者能够自由地按照所需的方式准确地处理消息。(他们可以仅选取关心的元素。)而且,发布方引入的对消息传递的任何扩展并不会影响现有通知使用者。






回页首


下载

描述名字大小下载方法
J2EE codear-wasnotcode.zip750KBHTTP
关于下载方法的信息


参考资料



关于作者

author photo

Boris Lublinsky 是 CNA Insurance 的一位企业架构师,参与了 CAN 的集成策略的设计和实现、应用程序框架构建及面向服务的体系结构 (SOA) 的实现。Boris 有 20 多年的软件工程和技术体系结构经验。您可以通过 boris.lublinsky@cna.com 与 Boris 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款