服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

j2ee基础之组件开发:看消息驱动的ejb

一、概述

消息服务是一种在分布式应用之间提供消息传递服务的软件,具有可靠、异步、宽松结合、语言中立、平台中立的特点,而且通常是可配置的。它的实现原理是:对发送者和接收者之间传递的消息进行封装,并在分布式消息客户程序结合的位置加上一个软件处理层。消息服务为消息的客户程序提供了一个接口,这个接口隔离了底层的消息服务,使得各种不同的客户程序能够通过一个友好的编程接口方便地通信。

java消息服务(java message service,jms)是一个java api,它定义了消息的客户程序如何以一种标准化的形式与底层的消息服务提供者交互。jms提供了一种接口,底层消息服务提供者通过该接口向客户程序提供jms消息服务。jms提供了点对点消息模式(point-to-point)和发布-订阅消息模式(publish-subscribe)。点对点消息模式通过一个消息队列实现,消息的生产者向队列写入消息,消息的消费者从队列提取消息。发布-订阅消息模式通过一个话题(topic)节点构成的层次结构实现,消息的生产者向这个层次结构发布消息,消息的消费者向这个结构订阅消息。

点对点消息模式具有如下特点:

每一个消息只有一个消费者。

消息的接收者和发送者之间不存在时间上的依赖关系。不论发送者发送消息时接收者是否在运行,接收者都可以提取信息。接收者对于成功处理的消息给出回执。

发布-订阅消息模式具有如下特点:

每一个消息可以有多个消费者。

向某个话题订阅的客户程序只能收到那些在它订阅之后发布的消息。为了接收到消息,订阅者必须保持活动状态。因此,发布者和订阅者之间存在时间上的依赖关系。

jms api在一定程度上放宽了对这种依赖关系的要求,允许创建持久性订阅(durable subscription)。有了持久性订阅,当订阅者不活动时发送的消息也能接收到。

ejb 2.0规范定义了一种新的ejb类型,即消息驱动的ejb(message-driven ejb,简称mdb),它能够以ejb的形式实现jms消息的接收者。消息驱动的ejb实现一组新的接口,这组接口使得ejb能够异步地接收和处理jms消息生产者发送到队列或话题的消息。ejb客户程序的构造方式与普通jms消息生产者的构造方式完全一样,也就是说,jms消息生产者不必知道消息的消费者是一个ejb。

相对于会话bean和实体bean而言,消息驱动的bean最大的特点是客户程序不通过接口访问bean。与会话bean和实体bean不同,消息驱动的bean只有一个bean类。从某些方面看,消息驱动的bean类似于无状态会话bean:消息驱动的bean不为特定的客户保留数据或对话状态。

一个消息驱动bean的所有的实例都是等价的,这使得容器能够把消息指派给任意一个消息驱动bean的实例。容器能够建立消息驱动bean的缓冲池,实现消息的并发处理。一个消息驱动的bean能够处理来自多个客户程序的消息。

消息驱动bean的实例变量可以在处理客户消息期间包含一些状态信息,例如jms连接、打开的数据库连接,或者是对ejb对象的引用。当一个消息到达,容器调用消息驱动bean的onmessage()方法处理消息。onmessage()方法通常把消息定型(cast)成为五种jms消息类型之一,然后按照应用的业务逻辑的要求处理消息。

传递给消息驱动bean的消息可能处于一个事务之内,这时,onmessage()方法内的所有操作都属于该事务的一部分。如果消息处理结果被回退,则系统将再次投递该消息。

哪些时候应该使用消息驱动的bean呢?会话bean和实体bean能够发送jms消息,能够同步接收消息,但不能异步接收。一些时候,为防止过多地占用服务器资源,在服务器端的组件中,我们想要避免阻塞,这时,我们可以用消息驱动的bean异步接收消息。

二、mdb体系结构

消息驱动的bean组件的基本体系结构。位于顶端的是javax.ejb.enterprisebean接口,它是所有ejb的基础接口。e

nterprisebean接口派生出了javax.ejb.messagedrivenbean接口,所有消息驱动的ejb类必须实现javax.ejb.messagedrivenbean接口。此外,消息驱动的bean必须实现javax.jms.messagelistener接口。公用的、非最终的、非抽象的消息驱动的ejb,消息驱动的ejb与其他类型的ejb不同,它们不把业务方法导出给客户程序,它们关心的只是遵从ejb容器的接口要求。由于这个原因,消息驱动的bean必须有一个不需要参数的公用构造方法(ejbcreate()方法),而且不应该实现finalize()方法。

2.1 mdb接口

在消息驱动的bean中,setmessagedrivencontext()方法用来把一个messagedrivencontext的对象实例传递给ejb,它是messagedrivenbean接口定义中容器调用的第一个方法。

messagedrivencontext对象封装了一个ejb消息驱动容器上下文的接口,支持消息驱动的ejb实例访问容器提供的运行时消息驱动上下文对于消息驱动的ejb来说,关键之一是要实现一个没有参数的ejbcreate()方法。当ejb容器准备创建消息驱动ejb的实例时,它将调用这个方法。容器之所以决定创建某个ejb的实例,可能是因为它要构造一个bean实例的缓冲池,也可能是因为它接收到了客户的请求。这个ejbcreate()方法和其他bean上的ejb构造方法类似,属于ejb实现的一种特殊的构造函数或初始化方法。

当ejb容器准备不让bean实例继续处理客户程序的请求时,它就会调用消息驱动bean的ejbremove()方法。何时在消息驱动的bean上调用ejbremove()方法由ejb容器单独决定,不受ejb客户程序的任何约束。应当注意的是,容器并不保证一定调用ejbremove()方法。在正常操作时,容器会调用ejbremove()方法;但是,当消息驱动的bean向容器抛出了系统异常时,不能保证ejbremove()方法一定会被调用。由于这个原因,bean开发者必须按时检查和清除bean分配的所有资源。

对于bean开发者来说,最重要的任务也许是实现onmessage()方法。当一个异步消息必须由bean实例处理时,容器将调用onmessage()方法。onmessage()方法的参数是一个普通的jms javax.jms.message的实例,消息驱动的ejb实例从这个message的实例提取待处理的数据完成消息处理。

2.2 jms消息接口

那么,在onmessage()方法调用传入的 jms消息中,消息驱动的bean如何提取信息,可以提取哪些信息呢?图二描述了基本jms消息类型的核心接口和概念。在一个以jms为基础的消息系统中,message接口是在系统中传递的所有消息的最基本的接口(或称之为根接口,root interface)。destination接口描述了消息传递的一个终端;类似地,由于消息有一个传递模式,所以图二还显示了message接口与deliverymode接口的概念上的关系。

jms消息的头信息可以通过一组标准的方法设置或提取,这组标准方法的名字为getjmsxxx()或setjmsxxx()形式(下面我们分别称之为get方法和set方法),其中xxx是消息头信息中的属性名字,例如getjmsdeliverymode()方法。在message接口中,通过get方法和set方法操作的标准头信息属性包括:唯一的消息id,时标(timestamp),答复和目标地址,消息传递模式,消息类型,以及消息的优先级。

在jms消息中,jms容器提供者特有的属性可以通过getxxxproperty()方法提取,或通过setxxxproperty()方法设置,其中xxx表示属性的类型,例如byte getbyteproperty(java.lang.string name)。每一个属性有一个通过string对象指定的名字和相应的值。名字以jmsx前缀开头的属性作为标准jms属性保留。

与消息正文数据(或称之为消息体,与消息头相对而言)的五种类型对应,五种消息类型扩展了message接口,如图三所示。byte数据由bytesmessage封装,serializable对象由objectmessage封装,string消息由textmessage封装,键-值对由mapmessage封装,i/o流由streammessage封装。

这些派生消息类型上的方法为特定类型的消息正文定义了get和set操作,而在message基础接口内,有一个通用的clearbody()方法,这个方法清除消息的正文,并把它置入只写模式。clearbody()方法只清除消息正文,不清除消息头或属性。如果消息正文是只读的,调用该消息后,消息正文的状态将和新消息的空白正文状态一样。

2.3 mdb客户程序接口

消息驱动bean的客户程序并不知道接收端实际处理消息的将是一个ejb。事实上,消息驱动bean的客户程序的构造方法与普通jms客户程序的构造方法完全一样。

jms的核心体系如图四所示。从图中我们可以看出,jms connectionfactory(连接工厂)初始上下文通过java名称和目录接口(java naming and directory interface,jndi)创建。随后,连接工厂将用来创建与jms服务提供者的连接。有了jms连接,我们就可以获得创建消息生产者和消息消费者的会话(session)。实际上,消息驱动bean的客户程序就是消息的生产者,它发送的消息将由消息驱动的bean(即消息消费者)接收。

三、点对点消息队列模式

在一个支持点对点消息队列的系统中jms的基本体系结构。消息队列体系实际上是核心jms体系的一个扩展,特别地,它加入了对消息队列功能的支持。连接工厂、连接、会话、消息生产者、消息消费者等都用点对点消息队列形式的接口进行了扩展。

jms客户程序利用jndi获得一个queueconnectionfactory对象的引用。随后,我们用queueconnectionfactory.createqueueconnection()方法之一创建一个queueconnection对象的实例。调用createqueueconnection()方法时可以指定一个用户名字和密码,或者,我们也可以使用该方法不带参数的版本,此时假定使用默认用户身份。

queueconnection接口是connection接口的一种子类型,它代表着一个与jms点对点消息队列服务的连接。jms客户程序调用createqueuesession()方法创建queuesession的实例,createqueuesession()方法调用中一个boolean类型的参数指定了queuesession对象是否要提供事务支持。另外,回执的模式也在createqueuesession()调用中通过参数指定,这个参数的值可以是三个静态的标识符之一:auto_acknowledge,client_acknowled ge,dups_ok_acknowledge。

queuesession.createqueue()方法返回一个queue对象的实例,调用queue.getqueuename()方法可以返回队列的名字。

queuesession.createsender()方法创建一个queuesender消息生产者,利用queuesender可以把消息发送到queue。消息可以通过各种不同的queuesender.send()方法发送到queue,这些不同的send()方法能够把消息发送给queuesender对象关联的queue对象,或者发送给send()方法调用中指定的queue对象。消息递送模式、优先级、消息的有效时间都可以在调用queuesender.send()方法时指定。

发送给queue的消息可以用session接口中定义的各种消息构造方法创建。

四、发布-订阅消息模式

在一个支持发布-订阅消息模式的系统中jms的基本体系结构。发布-订阅消息机制也是核心jms机制的一种扩展,增加了一些适合发布-订阅消息模式的功能。连接工厂、连接、会话、消息生产者、消息消费者等都用发布-订阅形式的接口进行了扩展。

jms客户程序通过jndi获得一个topicconnectionfactory对象的引用。topicconnectionfactory.createtopicconnection()方法用来创建topicconnection对象的实例。调用createtopicconnection()方法时可以指定一个用户名字和密码,或者,我们也可以使用该方法不带参数的版本,此时假定使用默认用户身份。

topicconnection接口是connection接口的一种子类型,它代表着一个与jms发布-订阅消息服务的连接。jms客户程序调用topicconnection.createtopicsession()方法创建top icsession的实例。会话的事务支持和回执模式也在创建topicsession时指定。

topicsession.createtopic()方法返回一个topic对象的实例。topic接口封装了一个话题目的地,发布者向该目的地发送消息,订阅者从该目的地接收消息。不同的服务提供者按照不同的方式实现话题名称的层次结构,调用topic.gettopicname()方法可以获得话题的string形式的描述。

topicsession.createpublisher()方法创建一个topicpublisher消息生产者,它用来把消息发布到topic。消息可以通过各种不同的topicpublisher.publish()方法发布到topic,这些不同的publish()方法能够把消息发送给topicpublisher对象关联的topic对象,或者发送给publish()方法调用中指定的topic对象。消息递送模式、优先级、消息的有效时间都可以在调用topicpublisher.publish()方法时指定。发送给topic的消息可以用session接口中定义的各种消息构造方法创建。

五、实例

本示例应用是一个消息驱动bean应用的简单例子,由以下两部分构成:

simplemessageclient:j2ee应用客户程序,向队列发送消息。

simplemessageejb:一个消息驱动的bean,异步地接收和处理由客户程序发送到队列的

消息。

客户端应用把消息发送到队列,队列由管理员通过j2eeadmin命令创建。jms提供者(这里是j2ee服务器)把消息传递给消息驱动bean的实例,由bean的实例处理消息。

5.1 客户端

simplemessageclient把消息发送到simplemessagebean监听的队列。客户程序首先确定连接工厂和队列:

queueconnectionfactory = (queueconnectionfactory) jndicontext.lookup

("java:comp/env/jms/myqueueconnectionfactory");

queue = (queue) jndicontext.lookup("java:comp/env/jms/queuename");

接下来,客户程序创建队列连接、会话和一个消息发送器:

queueconnection = queueconnectionfactory.createqueueconnection();

queuesession = queueconnection.createqueuesession(false,

session.auto_acknowledge);

queuesender = queuesession.createsender(queue);

最后,客户程序把几个消息发送到队列:

message = queuesession.createtextmessage();

for (int i = 0; i < num_msgs; i++) {

message.settext("我是" + msgarray[i] );

system.out.println("sending message: " +

message.gettext());

queuesender.send(message);

}

5.2 mdb组件

simplemessageejb类阐明了编写消息驱动bean类的要求:

实现messagedrivenbean接口和messagelistener接口。

类定义为public类型。

类不能定义成abstract或final。

实现一个onmessage()方法。

实现一个ejbcreate()方法和一个ejbremove()方法。

包含一个public类型的不需要参数的构造方法。

不能定义finalize()方法。

与会话bean和实体bean不同,消息驱动的bean不定义客户程序访问的接口。客户程序不是先定位消息驱动的bean,再调用这些bean上的方法。虽然消息驱动的bean没有业务方法,但它们可以包含由onmessasge()方法内部调用的辅助方法。

当队列接收到一个消息,ejb容器将调用消息驱动bean的onmessage()方法。在simplemessagebean类中,onmessage()方法把接收到的消息定型(cast)成为textmessage类型,然后显示出文本信息:

public void onmessage(message inmessage) {

textmessage msg = null;

try {

if (inmessage instanceof textmessage) {

msg = (textmessage) inmessage;

system.out.println("message bean:收到消息: "

+ msg.gettext());

} else {

system.out.println("消息类型错误: "

+ inmessage.getclass().getname());

}

} catch (jmsexception e) {

e.printstacktrace();

mdc.setrollbackonly();

} catch (throwable te) {

te.printstacktrace();

} }

消息驱动bean的ejbcreate()方法和ejbremove()方法必须符合以下要求:

访问控制修饰符必须是public。

返回值类型必须是void。

不能有static和final修饰符。

throws子句不能定义任何应用自定义的异常。

不能带有参数。

在simplemessagebean类中,ejbcreate()方法和ejbremove()方法都是空的,不执行任何

有实际意义的操作。

5.3 打包

接下来我们要把上面的应用打包成一个j2ee ear文件。首先要把simplemessageejb打包成jar文件。通常,打包过程可以通过工具完成,但理解模块部署描述器仍是必要的。在ejb应用模块部署描述器中,顶级元素下面包含元素。下面可以包含一组元素(按照ejb 2.0新规范),每一个元素描述一个消息驱动bean的配置和部署。simplemessageejb的ejb-jar.xml文件如下所示。元素内定义了消息驱动bean的配置和部署信息,例如唯一的bean名字、bean类的名字、配置参数、安全信息、事务信息、消息目的地类型等。

......



simplemessagejar





simplemessageejb

simplemessageejb

simplemessagebean

container



javax.jms.queue



simplemessageejb

bean

onmessage



javax.jms.message



required

除了ejb-jar.xml部署描述器之外,通常还要有面向特定平台和环境的部署描述器。大多数时候,这种描述器可以用gui工具编写。请参见下载代码中提供的例子。

打包好各个模块之后,接着还要把j2ee应用打包成ear文件。有关这一步骤的详细说明,请参见开发平台的相关文档。本文以后有关部署和运行的说明,就以打包后的ear文件为基础。

5.4 部署和运行

假设我们在sun的j2ee参考实现上部署和测试这个示例应用。为便于查看消息驱动bean的输出,我们必须以-verbose模式启动服务器:

j2ee -verbose

用下面的j2eeadmin命令创建队列:

j2eeadmin -addjmsdestination jms/myqueue queue

验证队列已经创建成功:

j2eeadmin -listjmsdestination

启动deploytool,选择菜单“file-->open”,打开simplemessageapp.ear文件。接着,

选择菜单“tools --> deploy”,部署应用。出现部署提示时,选中“return client

jar”检查框。

在一个命令窗口中,进入ear文件(simplemessageappclient.jar文件)所在目录,把环

境变量appcpath设置为simplemessageappclient.jar。然后,执行下面的命令:

runclient -client simplemessageapp.ear -name simplemessageclient -textauth

在登录提示中,输入用户名字j2ee,输入密码j2ee。此时,客户程序将输出以下内容:

sending message: 我是老大孙悟空

sending message: 我是老二猪八戒

sending message: 我是老三沙和尚