 | 级别: 中级 Lucas Partridge, 软件工程师, IBM Japan, Software Group James M. Siddle, 软件工程师, IBM Japan, Software Group
2009 年 4 月 14 日
了解如何开发 JAX-WS 应用程序,以与 IBM® WebSphere® Application Server V7.0 中包括的更新后的 WS-Notification 功能结合使用。本文的目标读者应该对基于 Java™ 的 Web 服务、WS-Notification 1.3 系列规范有一定了解,而且在使用 WebSphere Application Server 的管理控制台方面有一定经验。
来自 IBM WebSphere Developer Technical Journal。
引言
WS-Notification 1.3 系列规范一起定义了 Web 服务的发布-订阅消息传递模式。IBM WebSphere Application Server V6.1 使用 Java API for XML-based RPC (JAX-RPC) Web 服务提供了 WS-Notification 的实现。WebSphere Application Server V7 继续提供对此 JAX-RPC 实现的支持。不过,V7.0 还支持新的 JAX-WS 2.1 实现。在 WebSphere Application Server V7 中创建新 WS-Notification 服务时,必须确定要使用其中的哪个实现;“V6.1”(JAX-RPC) 类型的服务或“V7.0”(JAX-WS) 类型的服务。
您要如何选择呢?如果选择 V7.0 类型的服务,则将能够使用策略集来将 WS-Notification 服务与其他开放 WS 标准(如 WS-ReliableMessaging 和 WS-Security)结合使用。通过这样,就可以更为可靠且/或安全的方式发送消息。还可以将 JAX-WS 处理程序应用到 WS-Notification 服务。不过,如果不希望将 WS-Notification 与其他 WS 标准结合使用,或仍然希望将 JAX-RPC 处理程序应用到服务,则转而创建 V6.1 类型的服务。
本文将说明如何编写基于 JAX-WS 的 WS-Notification 客户端和服务来使用新的 V7.0 类型的服务。通过对此的学习,还可以编写使用 V6.1 类型的服务的 JAX-WS 客户端和服务。同样,还可以编写使用 V7.0 类型的服务的 JAX-RPC 客户端。不过,如果希望在消息发布后使用者接收到之前充分利用与其他 Web 服务标准的组合,则应该编写针对 V7.0 类型的服务的 JAX-WS 客户端和服务。
关于本系列
本系列介绍如何将基于 JAX-WS 的 WS-Notification 应用程序与其他 WS 标准结合使用,本文是其中的第一部分。后续文章将说明如何将 WS-Notification 应用程序与 WS-ReliableMessaging 标准结合来实现通知消息的可靠传输,如何在集群化环境中配置新的 WS-Notification 实现,以及如何将 WS-Notification 与 WS-Security 结合使用来安全地传输消息。
在继续阅读之前,您可以参阅一下 IBM developerWorks 文章 WebSphere Application Server Version 6.1 中的 WS-Notification,其中对基本概念和 WS-Notification 的 V6.1 实现进行了介绍。对于首次接触 WS-Notification 的读者,这篇介绍性的文章将特别有用。
本文剩下的内容分为三个部分。首先,将介绍 WebSphere Application Server V7 中的 WS-Notification 基本配置。以此为基础,下一部分介绍开发基于 JAX-WS 的发布者、使用者和订阅者组件所需的步骤,以便与 WS-Notification 实现交互。本文最后是示例方案演练,将使用随本文提供的的示例企业应用程序存档文件和脚本。
针对 WS-Notification 配置应用服务器
在编写与新的基于 JAX-WS 的 WS-Notification Web 服务交互的组件前,需要配置应用服务器来公开这些 Web 服务。下列步骤说明了如何在 WebSphere Application Server V7 进行此工作,这对于已经熟悉 WS-Notification 配置的人员将非常有用。有关配置的详细说明,请参考 WebSphere Application Server V7 信息中心(或在本系列的第 2 部分推出后参考该部分的内容)。或者,还可以下载随本文提供的示例脚本 WAS7_WSN_JAXWS_Part1_Setup.py,将其用于执行这些配置步骤。
这些说明假定您已经在应用服务器安装中创建了单个服务器配置文件。除非特别说明,否则应该为配置选项选择缺省设置。
-
创建服务集成总线。
将单个服务器实例作为总线成员。服务集成总线为 WS-Notification 实现提供基本消息传递基础设施。
-
创建新的 V7.0 WS-Notification 服务。
用于创建 WS-Notification 服务的向导中的第二步将要求选择创建哪种服务类型。正如前面提到的,要使用新功能(如可靠的消息传递),必须在此步骤选择 Version 7.0。在此示例中,服务名为 WSNService1,以下将使用此名称对其进行指代。
-
创建与前面创建的服务关联的服务点。
这是创建 WS-Notification 服务时必须进行的步骤。在本例中的服务端点名为 WSNServicePt1。只要在创建新服务时选择了启用动态主题命名空间的缺省选项,就不需要创建永久主题命名空间。
-
启动所部署的服务点企业应用程序。
每个 V7.0 服务点都与专门部署的企业应用程序关联,在应用程序使用服务点前必须先启动该企业应用程序。企业应用程序的名称从为服务和服务点指定的名称自动派生。这里,它的名称为 WSN_WSNService1_WSNServicePt1。
使用管理控制台启动已部署的企业应用程序,或者重新启动应用服务器也可以实现相同的效果。
创建了 V7.0 WS-Notification 服务和服务点后,就打好了基础,可以开始编写应用程序来使用 Web 服务可靠、安全地发布和使用消息。
编写发布者、使用者和订阅者组件
在此部分,您将了解如何使用三个关键组件编写应用程序:发布者、使用者和订阅者。编写这些组件的顺序并不重要。不过,为了简单起见,将从发布者开始。还可以选择实现组合订阅者和使用者组件,但这里出于保持条理清楚的原因,将二者分开了。
在此示例中,所有这三个组件都实现了 Java EE Web 应用程序。每个应用程序的核心功能都在单个 Bean 中实现。还为每个 Bean 提供了一个 Servlet,以便控制关联的 Bean 的行为(对于发布者和订阅者组件而言),或查询其状态(对于使用者组件)。同样也可以将发布者和订阅者组件作为 Java EE 应用程序客户端或 EJB 实现,不过实现 WS-Notification 功能的关键步骤将与下面所描述的一样。(这里忽略了 Servlet 的细节;如果希望了解关于这些 Servlet 的更多信息,请浏览可下载代码。)
A. 编写用于发布通知消息的发布者客户端
要编写发布者客户端,请执行以下步骤:
-
从通知代理服务获得 WSDL 文件。
首先,获得 V7.0 WS-Notification 服务点公开的 NotificationBroker Web 服务的 WSDL。这是用于发布通知消息的 Web 服务。
最简单的方法是通过 WebSphere Application Server 管理控制台进行此工作。从控制台中,导航到 WS-Notification services => WSNService1 => WS-Notification service points => WSNServicePt1 => Publish WSDL files。此时,实际上可以访问特定服务点的所有 WSDL 文件,在本文稍后将会证明这非常有用。
从得到的 .zip 文件提取 NotificationBroker.wsdl 文件,并将其移动(或复制)到临时文件夹中,为下一步骤做好准备。
-
对 NotificationBroker.wsdl 运行 wsimport,以生成客户端存根。
接下来,运行随应用服务器提供的 wsimport 命令,以生成供发布者使用的客户端存根。此命令使用 WSDL 文件内的信息生成用于进行客户端和服务开发的 JAX-WS 可移植构件。可以通过这个 WebSphere Application Server V7 信息中心页面了解关于这些构件的更多信息。
以下是说明如何在 Microsoft® Windows® 环境中运行此命令的示例。<WAS> 指 WebSphere Application Server 安装的根目录(例如,C:\was)。其中假定您当前已经打开了命令提示符,且处于包含 NotificationBroker.wsdl 副本的目录中。
<WAS>\bin\wsimport -keep -b <WAS>\util\ibm-wsn-jaxb.xml -wsdllocation "WEB-INF/wsdl/NBModule/NotificationBroker.wsdl" NotificationBroker.wsdl
下表描述了这些参数:
| 参数 | 描述 |
|---|
-keep
| 保留生成的源代码 (*.java) 文件。这在参考方面非常有用。 |
-b
<WAS>\util\ibm-wsn-jaxb.xml
| 对于特定的 WSDL 元素使用 IBM Helper 类而不是缺省类(见下)。 |
-wsdllocation
"WEB-INF/wsdl/NBModule/NotificationBroker.wsdl"
| 告知生成的 Web 服务客户端类对应的 WSDL 位于何处(见下)。 |
NotificationBroker.wsdl
| 要解析的 WSDL。 |
第二个参数告知 wsimport 对 WSDL 引用的某些 WS-Notification 模式元素使用 IBM Helper 类,而不是生成缺省的 JAXB 类。这是绕开在应用 JAXB 标准解释 WS-Notification WSDL 时出现的一些特定限制的关键步骤。通过包括此选项,将确保在您的 JAX-WS 发布者、使用者和订阅者组件中充分利用 WS-Notification 功能。
wsdlLocation 参数指定生成的文件之一的 @WebServiceClient 标注的 wsdlLocation 属性的值(稍后将对此标注进行更详细的讨论)。其中应该引用描述 Web 服务将与之交互的服务的 WSDL 文件的位置,而且此文件必须能够在 Web 服务运行时在此位置进行访问。可能的值包括文件系统位置或企业应用程序存档(Enterprise Application aRchive,EAR)文件内的相对位置。如果此参数从 wsimport 命令省略,则生成的构件将引用 WSDL 文件在硬盘上的完整路径。如果您希望在其他计算机上运行发布者组件,或者 WSDL 文件的位置要发生变化,则请将 WSDL 文件打包在 EAR 文件中,并按照这里的方法使用相对 URL 进行引用。这样,您的发布者组件的可移植性将大大提高。
请注意,应用程序开发工具(如 IBM Rational® Application Developer)内也支持 wsimport 命令。在 Rational Application Developer V7 中,请记得在使用 Web Service Client 向导来确保所生成的构件使用 IBM Helper 类时指定 <WAS>\util\ibm-wsn-jaxb.xml JAXB 绑定文件。(请参见 Rational Application Developer V7 信息中心主题“Generating a Web service client from a WSDL document using the IBM WebSphere JAX-WS runtime environment”,以了解如何指定绑定文件的详细信息。)
-
确保生成的构件位于开发类路径中。
wsimport 执行完成后,将会在包含 WSDL 文件的目录下的包中发现很多 .class 和 .java 文件。其中一些构件值得一提:
-
com.ibm.websphere.wsn.notification_broker.NotificationBroker——此接口使用 @WebService 进行标注,表示 NotificationBroker Web 服务的服务端点接口(Service Endpoint Interface,SEI)。其中描述了 Web 服务公开的所有操作,如 Notify 和 Subscribe 以及调用时可能需要的任何参数。
-
com.ibm.websphere.wsn.notification_broker.WSNService1WSNServicePt1NB——这个类的名称取自 NotificationBroker.wsdl 文件中的 service 元素的 name 属性,而后者又派生自 WS-Notification 服务和服务端点的名称。这个类对 javax.xml.ws.Service 进行扩展,并提供了便利包装方法供客户端用于获取 NotificationBroker Web 服务的存根。返回的存根将实现上面已经提到的 NotificationBroker 服务端点接口。还记得我们前面讨论的 wsimport 的 wsdlLocation 参数吧?该参数指定此类的 @WebServiceClient 标注中的 wsdlLocation 的值。
-
在有些包中,例如 org.oasis_open.docs.wsn.b_2 中,可能会看到名为 ObjectFactory 的类。这些类提供用于创建 wsimport 生成的多个 JAXB 对象的便利工厂方法。您并非必须使用这些类,但通过这些类可以不必指定命名空间 URI 和 javax.xml.namespace.QName 对象。
为了使用生成的构件,必须确保这些构件出现在开发类路径中。例如,使用 Rational Application Developer 时,必须导入生成的构件。从命令行编译发布者代码时,确保生成的构件出现在编译器使用的类路径中即可。
不要尝试从项目中删除所生成的、使用 @WebServiceClient 进行标注的类,即使 Java 代码不直接使用这些类也应如此。在此示例中,此类名为 WSNService1WSNServicePt1NB。如果从 EAR 文件中或具体的 Web 应用程序模块中将其删除,应用服务器的容器将无法检测您的 JAX-WS Web 服务客户端,您将无法把策略集附加到客户端。
-
确保 WebSphere Application Server V7 的 JAX-WS 瘦客户端位于开发类路径中。
与前一步骤类似,必须确保 JAX-WS 瘦客户端位于开发类路径中。此 JAR 文件包含前面提到的 IBM Helper 类。WebSphere Application Server V7 在 Microsoft Windows 上打包的 JAR 文件的完整文件名为:
<WAS>\runtimes\com.ibm.jaxws.thinclient_7.0.0.jar
Rational Application Developer 等开发工具还提供了在类路径中包括此 JAR 文件的方法。例如,Rational Application Developer V7 提供了用于通过 Build Path 对话框将特定的应用服务器运行时库添加到项目的类路径的方法。
为开发环境设置了必要的构件后,接下来开始编写发布者组件。通过 WS-Notification,发布者可以在允许发布消息前向代理进行注册。不过,发布者注册不在本文讨论范围之列,而且,只要在管理控制台中创建 WS-Notification 服务时确保禁用了 Requires registration(缺省设置),就不需要在这里进行此工作了。
-
查找通知代理服务和端口。
编写通知消息发布者的第一步是查找通知代理服务,然后获得服务端口,以将其作为通知消息的目的地。
清单 1 中所示的代码片段中包括 WS-Addressing 功能,这些代码说明了如何通过 JAX-WS 2.1 构件和 API 实现此功能。在本文中,每个代码清单中的相关导入声明都放在首次引用新类之前。
清单 1
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.soap.AddressingFeature;
import com.ibm.websphere.sib.wsn.jaxb.base.TopicExpressionType;
import com.ibm.websphere.wsn.notification_broker.NotificationBroker;
/**
* Publishes one WS-Notification notify message.
*
* @param brokerPortAddress - URL of WSN broker port.
* @param brokerWSDLTargetNamespace - target namespace value
* for the broker's NotificationBroker WSDL.
* @param topicNamespace - URI of topic namespace.
* @param topicNamespacePrefix - prefix for topic namespace.
* @param topicDialect - topic expression dialect.
* @param topicExpression - topic expression which describes
* the topic to publish on.
* @param messageContents - message to be published.
*/
public void publishOneMessage(String brokerPortAddress,
String brokerWSDLTargetNamespace, String brokerServiceName,
String brokerPortName, String topicNamespace,
String topicNamespacePrefix, String topicDialect,
String topicExpression, SOAPElement messageContents) {
// Get hold of a stub for the broker web service:
Service broker = null;
try {
broker = Service.create(
new URL(brokerPortAddress + "?wsdl"),
new QName(brokerWSDLTargetNamespace, brokerServiceName));
} catch (MalformedURLException e) {
e.printStackTrace();
}
// When we get the port object, remember to enable WS-Addressing:
NotificationBroker port = (NotificationBroker) broker.getPort(
new QName(brokerWSDLTargetNamespace, brokerPortName),
NotificationBroker.class,
new AddressingFeature()); |
-
创建通知消息。
接下来,创建通知消息容器类型对象。此对象直接映射到单个通知消息,用于存储消息、主题、订阅引用和发布者引用。只有消息是必需的,其他三个组件都是可选的。清单 2 中包括了一条消息和一个主题。
清单 2
import org.oasis_open.docs.wsn.b_2.NotificationMessageHolderType.Message;
import org.oasis_open.docs.wsn.b_2.NotificationMessageHolderType;
// Create the message and put the contents in its 'any' element:
Message message = new Message();
message.setAny(messageContents);
NotificationMessageHolderType notificationHolder =
new NotificationMessageHolderType();
// Put the message in the holder:
notificationHolder.setMessage(message); |
请注意,消息有效负载 (messageContents) 是一个 javax.xml.soap.SOAPElement。(严格来说,传入 Message.setAny() 的消息有效负载必须实现 org.w3c.dom.Element 接口;javax.xml.soap.SOAPElement 对此接口进行了扩展。)清单 3 显示了创建 SOAPElement 的一种方法。
清单 3
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
/**
* Returns a SOAPElement containing some message data for
* the specified namespace.
*
* @param nsPrefix - namespace prefix.
* @param elementName - name of the element.
* @param nsURI - URI for the namespace.
* @param data - message data. Must be valid XML.
* @return the SOAPElement containing the message data.
* @throws SOAPException -
* if the SOAPElement cannot be created properly.
*/
public SOAPElement createSOAPElement(String nsPrefix, String elementName,
String nsURI, String data) throws SOAPException {
SOAPFactory soapFactory = SOAPFactory.newInstance();
SOAPElement element = soapFactory.createElement(elementName, nsPrefix,
nsURI);
element.addTextNode(data);
return element;
} |
-
将消息与主题关联。
使用 WS-Notification 进行消息发布的一个重要部分是将消息与特定主题关联,以便使用者能够接收感兴趣的消息。清单 4 显示了如何指定发布主题。
清单 4
import com.ibm.websphere.sib.wsn.jaxb.base.TopicExpressionType;
// Prepare a topic expression to describe the topic we want to
// publish on:
topicExpression = topicNamespacePrefix + ":" + topicExpression;
// - this is the actual topic!
// The topic expression, its associated dialect and its namespace
// definition are all held within a TopicExpressionType object. This
// object maps directly to the Topic child element of the
// NotificationMessage element in the message that’ transmitted.
TopicExpressionType topicExpressionType = new TopicExpressionType();
// Set the topic expression:
topicExpressionType.setExpression(topicExpression);
// Specify the mapping from the namespace prefix to the topic
// namespace URI:
topicExpressionType.addPrefixMapping(
topicNamespacePrefix,
topicNamespace);
// Specify the TopicExpression dialect to be used:
topicExpressionType.setDialect(topicDialect);
// Set the topic in the notification message holder:
notificationHolder.setTopic(topicExpressionType); |
请注意,这里使用了 IBM Helper 类(来自 com.ibm.websphere.sib.wsn.jaxb.base 包)来将主题表达式中使用的前缀与特定的 XML 命名空间关联。使用从 WS-Notification WSDL 生成的缺省构件时,很难进行此关联。
-
发出通知请求。
发布消息的最后一步是,通过将创建的通知消息对象传递到之前获得的端口来发出请求(清单 5)。
清单 5
import org.oasis_open.docs.wsn.b_2.Notify;
// Add the holder to the list of notifications to be sent with this
// Notify request. Remember a single Notify request may contain
// more than one NotificationMessage.
Notify notify = new Notify();
notify.getNotificationMessage().add(notificationHolder);
// Send the Notify request to the broker:
port.notify(notify); |
好了!您已经了解了如何将通知消息发布到应用服务器提供的 WS-Notification Web 服务。
随本文提供了完全能工作的示例发布者组件。您可以下载并安装 WSNPublisher.ear 来试用此组件。本文稍后的示例方案部分提供了进一步的说明。
B. 编写使用者服务来接收通知消息
WS-Notification 应用程序所需的第二个组件是接收和处理消息的使用者。以下是使用 JAX-WS 编写使用者组件的关键步骤:
-
编写 WSDL 来描述使用者服务。
这里,您将了解如何编写在 NotificationBroker 端口类型的 NotificationConsumer 接口上实现 Notify 操作的 Web 服务。(原始使用者和 Pullpoint 风格的使用者本文中将不予讨论。)
实现此目标的第一步是使用 WSDL 编写 Web 服务描述。清单 6 是 WS-Notification 的完整 WSDL 的一个示例。您可以将此作为使用者组件;您可以将自由更改的关键字段或元素以粗体显示。
清单 6
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://wsn.test/PushNotificationConsumer/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://wsn.test/PushNotificationConsumer/"
xmlns:bw2="http://docs.oasis-open.org/wsn/bw-2">
<!-- Import the 'base notification' WSDL definitions; this
includes the port type (a.k.a. interface definition)
that the consumer will implement -->
<wsdl:import namespace="http://docs.oasis-open.org/wsn/bw-2"
location="http://docs.oasis-open.org/wsn/bw-2.wsdl">
</wsdl:import>
<!-- Define a binding from the NotificationConsumer port type
to SOAP over HTTP, document literal style -->
<wsdl:binding name="PushNotificationConsumerSOAP"
type="bw2:NotificationConsumer">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="Notify">
<soap:operation soapAction="" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<!-- Define the web service and associated ports; only one port
in this case - the SOAP/HTTP port defined above -->
<wsdl:service name="PushNotificationConsumer">
<wsdl:port name="PushNotificationConsumerSOAP"
binding="tns:PushNotificationConsumerSOAP">
<soap:address location=
"http://localhost:9080/ctx/svcs/consumerSOAP" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions> |
请注意,Notify 操作及其关联的消息类型已经由 WS-Notification 规范(位于 http://docs.oasis-open.org/wsn/bw-2.wsdl)进行了定义。您所要做的就是,定义用于实现 Notify 操作的服务、端口和绑定。因此,为了对上面的 WSDL 进行自定义来满足您的需求,只需修改命名空间、服务和端口信息即可。您可能还需要更新端口的地址,不过对 WebSphere Application Server V7 中部署的使用者组件不需要如此,因为任何时候需要 WSDL 时,地址将实际替换为正确的值。
最后,请注意使用了 SOAP/HTTP 作为传输协议。在 WebSphere Application Server V7 中,同时支持对 V6.1 (JAX-RPC) 类型的 WS-Notification 服务使用 SOAP/HTTP 和 SOAP/JMS 传输协议。不过,对于 V7.0 (JAX-WS) 类型的服务,仅仅支持 SOAP/HTTP。
-
运行 wsimport 生成服务实现类。
与发布者组件类似,需要通过对使用者 WSDL 运行 wsimport 命令,以生成 JAX-WS 构件,即使用以下 Windows 命令行:
<WAS>\bin\wsimport -keep -b <WAS>\util\ibm-wsn-jaxb.xml -wsdllocation "WEB-INF/wsdl/PushNotificationConsumer.wsdl" PushNotificationConsumer.wsdl
wsimport 将生成的两个构件为:
-
test.wsn.pushnotificationconsumer.NotificationConsumer——这是用于 PushNotificationConsumer Web 服务的 SEI。
-
test.wsn.pushnotificationconsumer.PushNotificationConsumer——这是使用者 Web 服务的任何客户端可以用于获得存根的类,以便随后对此存根调用 Notify 操作。
-
确保生成的构件和 JAX-WS 瘦客户端 JAR 位于开发类路径中。
与发布者类似,使用者代码必须在生成的构件和 JAX-WS 瘦客户端 JAR 可用的环境中进行编译。
-
实现 notify 方法,以接收和处理通知消息。
现在就可以编写代码来实现 WS-Notification 使用者了。为了在使用者中接收通知消息,需要编写具有 @WebService 标注的 Java 类。此标注告知应用服务器,在部署的 Web 模块中检测到关联的类时,应该将其作为 Web 服务对待。这里,实现类名为 PushNotificationConsumerSOAPImpl,下面是其中最重要的部分:
清单 7
@javax.jws.WebService(
endpointInterface = "test.wsn.pushnotificationconsumer.NotificationConsumer",
targetNamespace = "http://wsn.test/PushNotificationConsumer/",
serviceName = "PushNotificationConsumer",
portName = "PushNotificationConsumerSOAP",
wsdlLocation = "WEB-INF/wsdl/PushNotificationConsumer.wsdl")
public class PushNotificationConsumerSOAPImpl
public void notify(Notify notify)
{
List<NotificationMessageHolderType> notificationMessages =
notify.getNotificationMessage();
for (NotificationMessageHolderType notificationMessage :
notificationMessages)
{
// Your code here
}
}
} |
关于上述代码,有一些事情需要注意。
-
获取主题方言、表达式和命名空间映射。
处理通知消息时,可能会希望检查消息被接收到哪个主题下。清单 8 显示了如何获取与特定通知消息关联的主题的方言、表达式和命名空间。
清单 8
import com.ibm.websphere.sib.wsn.jaxb.base.TopicExpressionType;
import java.util.Map;
// List topic dialect and expression
TopicExpressionType topicExpressionType =
notificationMessage.getTopic();
System.out.println("-- Topic dialect: "
+ topicExpressionType.getDialect());
String topicExpression = topicExpressionType.getStringExpression();
System.out.println("-- Topic expression, including the namespace prefix: "
+ topicExpression);
// List all the mappings between the prefixes that can appear in
// topic expressions and their corresponding namespace URIs
Map<String, String> namespaceMappings =
topicExpressionType.getNamespacePrefixesMap();
System.out.println(
"-- Number of namespace mappings in the topic expression: "
+ namespaceMappings.size());
for (String topicNamespacePrefix : namespaceMappings.keySet())
{
System.out.println("---- Prefix '"
+ topicNamespacePrefix
+ "' maps to namespace '"
+ topicExpressionType
.getNamespaceForPrefix(topicNamespacePrefix)
+ "'");
} |
请注意,这里再次使用了来自 com.ibm.websphere.sib.wsn.jaxb.base 包的 IBM Helper 类,这一次是为了获得主题表达式中使用的任何命名空间的前缀信息。和前面一样,使用 wsimport 生成的缺省构件很难进行此工作。
-
获取主题表达式。
对于主题表达式包括命名空间的情况,可能还希望去掉命名空间前缀(清单 9)。
清单 9
// The topic expression (in our example) will be of the form
// topicNamespacePrefix:topicExpression, so we need to extract the
// topicExpression part:
int colonPos = topicExpression.indexOf(':');
if (colonPos > -1)
{
topicExpression = topicExpression.substring(colonPos + 1);
}
System.out.println("-- Topic expression, without the prefix: "
+ topicExpression); |
-
查询通知消息有效负载。
通知消息最重要的部分是有效负载。清单 10 显示了如何获取有效负载以及有效负载根元素的命名空间、前缀和名称。
清单 10
import org.oasis_open.docs.wsn.b_2.NotificationMessageHolderType.Message;
import org.w3c.dom.Element;
// Get the message content as a DOM Element.
Message message = notificationMessage.getMessage();
Element messageContents = (Element) (message.getAny());
String messagePayload = messageContents.getTextContent();
System.out.println("-- Message payload: " + messagePayload);
System.out.println("-- Payload namespace: "
+ messageContents.getNamespaceURI());
System.out.println("-- Payload namespace prefix: "
+ messageContents.getPrefix());
System.out.println("-- Payload element name: "
+ messageContents.getLocalName()); |
现在应该有了一个可正常工作的基于 JAX-WS 的 WS-Notification 使用者组件。另外也随本文提供了完全可正常工作的示例使用者 WSNConsumer.ear。
最后一步是编写基于 JAX-WS 的订阅者。
C. 编写订阅者客户端将使用者订阅到发布者
到目前为止,您已经编写的发布者通过 WS-Notification 服务发布消息,并编写了使用者来使用来自 WS-Notification 服务的消息。但是如果不发出订阅请求,所发布的任何消息都不会传输到使用者。下面的步骤将说明如何代表您的使用者组件发出订阅请求。
-
获得通知代理和订阅管理器服务的 WSDL 文件。
事实上,从前面创建的 WS-Notification 服务点发布 WSDL 文件时已经完成了此工作。
除了 NotificationBroker.wsdl 文件外,还将需要 SubscriptionManager.wsdl 文件。这是因为在 WebSphere Application Server 中,订阅是从通知代理 Web 服务请求的,而取消订阅是从订阅管理器服务请求的。因此,您的订阅者组件必须为这两个 Web 服务的客户端。
-
运行 wsimport 以生成客户端存根。
您应该已经在编写发布者时已经获得了通知代理服务的客户端存根,因此这里不再重复此命令了。不过,将需要运行 wsimport 为订阅管理器服务生成客户端存根,如下面的示例中所示:
<WAS>\bin\wsimport -keep -b <WAS>\util\ibm-wsn-jaxb.xml -wsdllocation "WEB-INF/wsdl/SMModule/SubscriptionManager.wsdl" SubscriptionManager.wsdl
-
确保生成的构件和 JAX-WS 瘦客户端 JAR 位于开发类路径中。
与发布者和使用者一样,订阅者代码也将需要生成的构件和 JAX-WS 瘦客户端 JAR。
-
查找通知代理服务。
要发出订阅请求,您的订阅者将需要获得对通知代理服务和端口的引用。创建发布者组件的说明的第 5 步说明了如何进行此工作。
-
创建订阅请求对象并设置使用者引用。
在发出订阅请求前,您需要创建 Subscribe 对象来获得关于订阅的详细信息,如对应作为通知消息发送目的地的使用者的引用。清单 11 显示了如何进行此工作。(为了清楚起见,从订阅相关代码片段中省略了异常处理。)
清单 11
import org.oasis_open.docs.wsn.b_2.Subscribe;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
// Create the subscription request object which maps to the Subscribe
// request. This MUST contain a ConsumerReference and MAY also contain
// a Filter, an InitialTerminationTime and a SubscriptionPolicy.
Subscribe subscribeRequest = new Subscribe();
// Tell the broker to whom the notifications are to be sent:
W3CEndpointReference consumerReference =
new W3CEndpointReferenceBuilder()
.address(consumerURI).build();
subscribeRequest.setConsumerReference(consumerReference); |
上面的使用者端点引用对象是使用使用者 Web 服务的端点的 URI(在清单 11 中使用字符串 consumerURI 指示)构造的。对于在 WebSphere Application Server 中部署的使用者组件,可以通过发布使用者服务所在的 Web 模块的 WSDL 文件获得。您的使用者服务的 WSDL 文件将包括 Web 服务的有效 URI。请注意,此 URI 只应该在可从配置了 WS-Notification 的应用服务器直接访问使用者的情况下使用。如果使用者服务将通过非直接路由(如代理)进行访问,应该对 URI 进行相应的更新。
-
将主题表达式定义为订阅上的筛选器。
除了设置使用者引用外,还可能希望将 Filter 对象与订阅请求关联。筛选器告知通知代理应该将哪些消息转发到使用者。消息可以基于主题和/或消息内容进行筛选。此示例显示了如何按主题对消息进行筛选。在清单 12 中,主题表达式、前缀映射、方言和 Filter 对象在与订阅关联前定义:
清单 12
import com.ibm.websphere.sib.wsn.jaxb.base.FilterType;
import com.ibm.websphere.sib.wsn.jaxb.base.TopicExpressionType;
// Prepare a topic expression to describe the topic to which we wish to
// subscribe (topicExpression was a String passed into this method):
topicExpression = topicNamespacePrefix + ":" + topicExpression;
TopicExpressionType topicExpressionType = new TopicExpressionType();
topicExpressionType.setExpression(topicExpression);
// Specify the mapping from the namespace prefix to the topic namespace
// URI:
topicExpressionType.addPrefixMapping(
topicNamespacePrefix,
topicNamespace);
// Specify the TopicExpression dialect to be used:
topicExpressionType.setDialect(topicDialect);
// Create the filter. This tells the broker which messages are to be
// sent to the consumer.
FilterType filter = new FilterType();
// Add the topic expression to the filter, and set the filter on the
// subscribe request
filter.addTopicExpression(topicExpressionType);
subscribeRequest.setFilter(filter); |
请注意,这里又使用了 IBM Helper 类来简化工作。
-
指定订阅持续期。
订阅的另一个重要特征是应该持续多长时间。您的订阅请求可以向通知代理指示此信息,如清单 13 中所示。
清单 13
import javax.xml.bind.JAXBElement;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
// Specify the duration of the subscription from the current time (one
// year hence in this case). It is also possible to specify an absolute
// termination time using javax.xml.datatype.XMLGregorianCalendar
// instead.
DatatypeFactory factory = DatatypeFactory.newInstance();
// See section 3.2.6.1 in XML Schema 1.0 at
// http://www.w3.org/TR/xmlschema-2/#duration
// for an explanation of string representations of duration.
// "P1Y" means one year.
Duration duration = factory.newDuration(”1Y”;
// You can either: (1) new up a JAXBElement directly, like so...
JAXBElement<String> initialTerminationTime =
new JAXBElement<String>(
new QName("http://docs.oasis-open.org/wsn/b-2",
"InitialTerminationTime"), String.class, duration.toString());
// Or: (2) if you prefer to avoid namespace URIs and QNames, you can
// use the relevant JAXB ObjectFactory artefact that was generated by
// wsimport to create the appropriate JAXBElement:
org.oasis_open.docs.wsn.b_2.ObjectFactory objectFactory = new
org.oasis_open.docs.wsn.b_2.ObjectFactory();
initialTerminationTime = objectFactory
.createSubscribeInitialTerminationTime(duration.toString());
// Whichever way you created 'initialTerminationTime', you can now pass
// it in to the subscribe request object:
subscribeRequest.setInitialTerminationTime(initialTerminationTime); |
在清单 13 中,可以看到定义订阅的初始终止时间的两种不同方式;这就是您的订阅者客户端希望订阅过期的时间。如果不习惯直接使用命名空间 URI 和 QName 对象(清单 13 中的选项 1),则可以使用相应的 JAXB ObjectFactory 提供的便利工厂方法(选项 2)。
-
发出订阅请求。
最后,构建了订阅请求后,可以将其通过通知代理端口发送到 Web 服务:
// Send the SubscribeRequest to the broker:
org.oasis_open.docs.wsn.b_2.SubscribeResponse subscribeResponse = port.subscribe(subscribeRequest);
现在应该已经有了 JAX-WS 发布者、使用者和订阅者组件,这些组件一起支持通过 WebSphere Application Server V7 中提供的 WS-Notification 代理 Web 服务发布和使用特定主题的通知消息。
剩下最后一步。您可以依赖于订阅过期,以防止消息无限期地交付到使用者。这个方法假定您在原始订阅请求中指定了初始终止时间。或者,可以采用更好的做法,即在使用者接收到所需的所有消息后显式地终止订阅。
-
发出取消订阅请求,以停止通知消息的交付。
为了清理订阅相关的资源,可直接为已进行订阅的使用者发送取消订阅请求到应用服务器公开的订阅管理器服务。清单 14 显示了如何进行此工作:
清单 14
import com.ibm.websphere.wsn.subscription_manager.SubscriptionManager;
import org.oasis_open.docs.wsn.b_2.UnsubscribeResponse;
// Get the port, remembering to enable WS-Addressing.
SubscriptionManager port = subscribeResponse.getSubscriptionReference()
.getPort(SubscriptionManager.class, new AddressingFeature());
// Issue the unsubscribe request:
UnsubscribeResponse unsubscribeResponse =
port.unsubscribe(new Unsubscribe()); |
要注意的一点是,要取消订阅,并不需要按照查找通知代理服务进行订阅时的相同方式查找订阅管理器服务。相反,可以从发出原始订阅请求时收到的响应中提取订阅管理器服务的端点引用。(请务必确保获得了此响应对象,否则就无法使用其进行取消订阅!)此端点引用包含(在其引用参数中)要销毁的特定订阅的详细信息。获得了端点引用后,可以获得订阅管理器 Web 服务的端口,并直接对其发出取消订阅请求。
到目前为止,您应该已经获得了完全能够正常工作的基于 JAX-WS 的 WS-Notification 应用程序,其中包含用于发布通知消息的发布者组件、用于接收这些消息的使用者组件和用于代表使用者创建和销毁订阅的订阅者组件。对于其他组件,您可以在下载文件中找到完全可以正常工作的示例订阅者组件 WSNSubscriber.ear。
示例方案:订阅、发布、使用和取消订阅
本文的最后一部分将描述一个示例方案,其中使用了本文中提供的示例发布者、使用者和订阅者组件来发布和使用通知消息。
准备系统
要使用示例应用程序,您将需要进行以下准备步骤:
-
安装具有单个服务器配置文件的 WebSphere Application Server V7,然后启动服务器。
-
解压缩本文所提供的下载文件,并发出以下命令来运行示例 V7.0 WS-Notification 配置脚本:
<WAS>\bin\wsadmin -f WAS7_WSN_JAXWS_Part1_Setup.py SIBus WSNService1 WSNServicePt1
请记住,配置 V7.0 WS-Notification 服务点也会创建和部署一个必须首先启动才能调用 WS-Notification Web 服务的企业应用程序。上面的脚本将启动此应用程序。
-
将三个 EAR 文件(也包括在下载文件中)安装到应用服务器中。安装应用程序时,请直接接受缺省选项。
这些文件包含示例发布者、使用者和订阅者组件,并包括允许组合和调用 WS-Notification 请求的 HTTP Servlet。
-
启动三个企业应用程序。
-
打开 Web 浏览器,并创建三个浏览器选项卡(或打开三个 Web 浏览器),并将其指向以下这些 URL:
http://localhost:9080/WSNSubscriberWeb/SubscriberServlet http://localhost:9080/WSNPublisherWeb/PublisherServlet http://localhost:9080/WSNConsumerWeb/ConsumerServlet
这些网页显示用于调用 WS-Notification 功能的输入字段和按钮,以及任何请求的结果和所发布及使用的消息。
现在我们已经准备好了运行示例方案,接下来我们就要进行此工作。
运行示例方案
示例方案描述了将示例使用者服务订阅到主题、通过示例发布者发布通知消息并检查接收到通知的使用者所需的关键步骤。方案的最后说明了如何对使用者进行取消订阅。
下面的说明包括示例应用程序的发布者、使用者和订阅者组件公开的网页的图片、管理控制台图片以及从应用服务器的系统输出捕获的输出。此方案还包括多个可选步骤,用于说明系统的行为。除了显示 V7.0 WS-Notification 实现如何工作的一些细节外,这些可选步骤还可能在测试您自己的 WS-Notification 应用程序时非常有用。
-
打开标题为 WS-Notification Consumer 的浏览器选项卡,并检查列表中显示最近接收到的消息列表是否为 None(可选),如图 1 中所示。
图 1. WS-Notification Consumer 页面,显示尚未接收到任何消息
这确认了使用者尚未接收到任何消息,与预期相符。
-
打开标题为 WS-Notification Subscriber 的浏览器选项卡,在端口和服务相关的字段输入值,然后单击 Subscribe 按钮。大部分订阅相关的字段都将填充有效的缺省值,但是将需要根据 WS-Notification 配置完成 Broker port address 和 Broker service name 字段。假定您已经运行了前面部分中描述的脚本,请按照图 2 中所示输入值。
图 2. 发出订阅请求前的 WS-Notification Subscriber 页面
还应该检查 Consumer URI 字段中所示的主机和端口值。不过,缺省值 localhost 和 9080 将对大部分配置有效。
通过单击 Subscribe 按钮,将向表单中的前两个字段中指定的通知代理服务和端口发送订阅请求。此请求将包含其他字段提供的使用者和主题信息。
-
检查订阅请求的结果(可选)。图 3 显示了发出了订阅请求后的 WS-Notification Subscriber 浏览器选项卡。
图 3. 发出订阅请求后的 WS-Notification Subscriber 页面
Current known subscriptions 部分显示所进行的订阅的详细信息,并提供了一个链接,可通过此链接发出取消订阅请求。(订阅者组件仅仅列出在当前会话期间通过其创建的订阅。它不会反映其他订阅,例如由任何其他应用程序创建的任何订阅。)
-
检查系统输出日志,以获得订阅相关的消息(可选)。
示例订阅者组件将多条诊断消息输出到了系统输出日志中,您可能想要检查这些日志内容。打开 SystemOut.log 文件,此文件位于我们所使用的单个应用服务器配置文件的 logs\server1 目录中。
清单 15
[19/10/08 21:00:09:977 BST] 00000025 SystemOut O Subscribe response details:
[19/10/08 21:00:09:997 BST] 00000025 SystemOut O Address of SubscriptionManager
port: http://vespa.hursley.ibm.com:9080/WSNService1WSNServicePt1SM/Service
[19/10/08 21:00:09:997 BST] 00000025 SystemOut O Subscription ID:
sub_1ae9adc025057716a14fa615_11d16b17c8b_3
[19/10/08 21:00:10:007 BST] 00000025 SystemOut O Current time: 19-10-2008
21:00:09
[19/10/08 21:00:10:007 BST] 00000025 SystemOut O Termination time: 19-10-2009
21:00:09 |
-
验证 V7.0 WS-Notification 服务中存在订阅(可选)。
打开应用服务器的管理控制台,并导航到 WS-Notification services => WSNService1 => Subscriptions,以查看对 WS-Notification 服务的活动订阅的列表。(如果无法看到 Subscriptions 链接,请确保在导航到 WS-Notification 服务后单击 Runtime 选项卡。)应该看到一个订阅(图 4),此订阅的详细信息应与 WS-Notification Subscriber 浏览器选项卡中列出的订阅(图 3)匹配:
图 4. 在管理控制台中查看订阅
-
打开 WS-Notification Publisher 选项卡,为 Broker port address 和 Broker service name 字段输入值,然后单击 Publish。
此步骤将使用后面几个字段提供的主题和有效负载信息向前面两个字段指示的 WS-Notification 服务发出发布请求。主题命名空间值必须与为订阅请求使用的值匹配,主题方言和主题表达式的值还必须与订阅请求中使用的对应值相一致。如果希望发送不同的有效负载,请直接在 Message payload 字段中输入其他字符串即可。图 5 显示了发出发布请求后的 WS-Notification Publisher 浏览器选项卡。
图 5. 发出发布请求后的 WS-Notification Publisher 页面
-
打开 WS-Notification Consumer 选项卡,以验证使用者接收到了消息。您将需要刷新浏览器,以确保显示最新的使用者状态(图 6)。
图 6. WS-Notification Consumer 页面,显示接收到了消息
成功了!面板显示所接收到的通知消息的细节,包括通过 WS-Notification Publisher 选项卡输入的有效负载。
-
检查系统输出日志,以验证调用了使用者的 notify 方法(可选)。示例使用者组件还将向系统输出日志输出诊断消息(清单 16)。
清单 16
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O The consumer has received 1
message(s).
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O Details for message 1:
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Topic dialect: http://docs.
oasis-open.org/wsn/t-1/TopicExpression/Simple
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Topic expression, including
the namespace prefix: wsntopprefix:dW
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Number of namespace mappings
in the topic expression: 2
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O ---- Prefix 'b2' maps to
namespace 'http://docs.oasis-open.org/wsn/b-2'
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O ---- Prefix 'wsntopprefix' maps
to namespace 'http://wsn.jaxws.dw/topicns01'
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Topic expression, without
the prefix: dW
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Message payload: Sample
payload
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Payload namespace: http://
wsn.jaxws.dw.ibm.com
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Payload namespace prefix: dW
[19/10/08 21:03:52:878 BST] 00000035 SystemOut O -- Payload element name: dWMsg
[19/10/08 21:03:53:288 BST] 00000035 SystemOut O -- Subscription ID:
sub_1ae9adc025057716a14fa615_11d16b17c8b_3 |
-
为了清理订阅资源,请通过 WS-Notification Subscriber 浏览器选项卡发出取消订阅请求。
返回到 WS-Notification Subscriber 浏览器选项卡,然后单击在订阅信息旁边显示的 Unsubscribe 链接。这样将向订阅管理器 Web 服务发出针对之前创建的订阅的取消订阅请求。图 7 显示了所显示的确认消息。
图 7. 成功取消订阅请求后的 WS-Notification Subscriber 页面
-
检查系统输出日志,以验证进行了取消订阅(可选)。
[19/10/08 21:07:53:924 BST] 00000023 SystemOut O
Unsubscribed subscription ID: sub_1ae9adc025057716a14fa615_11d16b17c8b_3
-
验证 V7.0 WS-Notification 服务中不再存在订阅(可选)。
通过返回到管理控制台,并再次访问 Subscriptions 面板,可以确认 V7.0 WS-Notification 服务已经接收并处理了取消订阅请求(图 8)。
图 8. 管理控制台显示不存在订阅
-
再次发布消息并确定使用者组件不会收到消息(可选)。
为了最后确认之前创建的订阅已经销毁,请返回到 WS-Notification Publisher 选项卡,单击 Publish,然后检查所发出的通知消息是否未显示在 WS-Notification Consumer 选项卡中(刷新之后),如图 9 和图 10 中所示。
图 9. 发出第二个发布请求后的 WS-Notification Publisher 页面
图 10. WS-Notification Consumer 显示不再收到发布的消息
非常好!我们现在已经进行了整个方案的演练。您可以随意对示例组件进行试验;例如尝试不同的主题方言和表达式。通过分析相应 Servlet 的代码,您应该发现,如果希望在发布或订阅过程中操作更多的参数,向输入表单添加更多的字段的工作非常简单。
总结
本文说明了如何编写基于 JAX-WS 的客户端和服务组件,以与 IBM WebSphere Application Server V7 中提供的 WS-Notification 新 JAX-WS 实现结合使用。
首先,我们介绍了创建和配置 JAX-WS 或 V7.0 类型的 WS-Notification 服务的关键步骤。
然后,我们提供了帮助您编写基于 JAX-WS 的发布者、使用者和订阅者组件的说明。
最后,我们描述了一个示例方案,其中使用了随本文提供的示例脚本和企业应用程序存档文件。这个演练方案说明了如何使用示例应用程序来执行涉及订阅、发布、消息使用和取消订阅的关键 WS-Notification 操作。
编写了 JAX-WS Web 服务客户端和服务之后,现在就准备好了配置系统进行可靠的通知。这将在本系列的第 2 部分中进行讨论。
致谢
我们要感谢 Brian De Pradine、Peter Niblett 和 David Illsley 对本文进行审阅,Chris Whyley 提供了示例 Jython 脚本,Emily Jiang 提供了使用 IBM Helper 类的示例代码。David 和 Brian 还在 Lucas 编写代码时提供了宝贵的建议。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Sample application | 0811_partridge_JAXWSsampleapp.zip | 606 KB | HTTP |
|---|
参考资料
作者简介  | 
|  |
Lucas Partridge 是在英国的 IBM Hursley Park 工作的一名软件工程师。他在从事 WebSphere Application Server 方面的工作。 |
 | 
|  |
James Siddle 是在英国的 IBM Hursley Park 工作的一名软件工程师。他在从事 WebSphere 品牌产品方面的工作,如 WebSphere Application Server 和 WebSphere Business Events。 |
对本文的评价
|  |