J2EE 组件开发:消息驱动的EJB |
作者:佚名 发布时间:2005-04-02 来源:不详
|
消息服务是一种在分布 合、语言中立、平台中立的 之间传递的消息进行封装, 务为消息的客户程序提供了 户程序能够通过一个友好的
|
式应用之间提供消息传递服务的 特点,而且通常是可配置的。它 并在分布式消息客户程序结合的 一个接口,这个接口隔离了底层 编程接口方便地通信。
|
软件,具有可靠、异步、宽松结 的实现原理是:对发送者和接收者 位置加上一个软件处理层。消息服 的消息服务, 使得各种不同的客
|
Java消息服务(Java M 序如何以一种标准化的形式 务提供者通过该接口向客户 Point-to-Point)和发布-
|
essage Service,JMS)是一个J 与底层的消息服务提供者交互。 程序提供JMS消息服务。JMS提供 订阅消息模式(Publish-Subscr
|
ava API,它定义了消息的客户程 JMS提供了一种接口,底层消息服 了点对点消息模式( ibe)。点对点消息模式通过一
|
个消息队列实现,消息的生产者向队列写入消息,消息的消费者从队列提取消息。 |
发布-订阅消息模式通 这个层次结构发布消息,消
|
过一个话题(Topic)节点构成 息的消费者向这个结构订阅消息
|
的层次结构实现,消息的生产者向 。
|
消息的接收者和发送者之间不存在时 否在运行,接收者都可以提取信息。
|
间上的依赖关系。不论发送者发送消息时接收者是
|
向某个话题订阅的客户程序只能收到 订阅者必须保持活动状态。因此,发布者 定程度上放宽了对这种依赖关系的要求, 有了持久性订阅,当订阅者不活动时发送
|
那些在它订阅之后发布的消息。为了接收到消息, 和订阅者之间存在时间上的依赖关系。JMS API在一 允许创建持久性订阅(Durable S ubscription)。 的消息也能接收到。
|
EJB 2.0规范定义了一 MDB),它能够以EJB的形式 接口使得EJB能够异步地接 的构造方式与普通JMS消息 消息的消费者是一个EJB。 户程序不通过接口访 问Bea 。
|
种新的EJB类型,即消息驱动的E 实现JMS消息的接收者。消息驱 收和处理JMS消息生产者发送到 生产者的构造方式完全一样,也 相对于会话Bean和实体Bean而言 n。与会话Bean和实体Bean不同
|
JB(Message-Driven EJB,简称 动的EJB实现一组新的接口,这组 队列或话题的消息。EJB客户程序 就是说,JMS消息生产者不必知道 ,消息驱动的Bean最大的特点是客 ,消息驱动的Bean只有一个Bean类
|
从某些方面看,消息驱动的Bean类似于无状态会话Bean: |
消息驱动的Bean不为特定的客户保留数据或对话状态。 |
一个消息驱动Bean的所有的实例都是 驱动Bean的实例。容器能够建立消息驱动 的Bean能够处理来自多个客户程序的消息
|
等价的,这使得容器能够把消息指派给任意一个消息 Bean的缓冲池,实现消息的并发处理。一个消息驱动 。
|
消息驱动Bean的实例变 开的数据库连接,或者是对 onMessage()方法处理消息 型之一,然后按照应用的业
|
量可以在处理客户消息期间包含 EJB对象的引用。当一个消息到 。onMessage()方法通常把消息 务逻辑的要求处理消息。
|
一些状态信息,例如JMS连接、打 达,容器调用消息驱动Bean的 定型(cast)成为五种JMS消息类
|
传递给消息驱动Bean的 作都属于该事务的一部分。 该使用消息驱动的Bean呢? 能异步接收。一些时候,为 阻塞,这时,我们可以用消
|
消息可能处于一个事务之内,这 如果消息处理结果被回退,则系 会话Bean和实体Bean能够发送JM 防止过多地占用服务器资源,在 息驱动的Bean异步接收消息。
|
时,onMessage()方法内的所有操 统将再次投递该消息。哪些时候应 S消息,能够同步接收消息,但不 服务器端的组件中,我们想要避免
|
在图一中,位于顶端的 EnterpriseBean接口派生出
|
是javax.ejb.EnterpriseBean接 了javax.ejb.MessageDrivenBea
|
口,它是所有EJB的基础接口。 n接口,所有消息驱动的EJB类
|
必须实现javax.ejb.MessageDrivenB javax.jms.MessageListener接口。公用 显示的MyMessageDrivenEJBean,必须同 口。消息驱动的EJB与其他类型的EJB不同 是遵从EJB容器的接口要求。由于这个原
|
ean接口。此外,消息驱动的Bean必须实现 的、非最终的、非抽象的消息驱动的EJB,比如图一 时实现MessageListener接口和MessageDrivenBean接 ,它们不把业务方法导出给客户程序,它们关心的只 因,消息驱动的Bean必须有一个不需要参
|
数的公用构造方法(ejbCreate()方
|
法),而且不应该实现finalize()方法。
|
在消息驱动的Bean中, ageDrivenContext的对象实 个方法。
|
setMessageDrivenContext()方 例传递给EJB,它是MessageDriv
|
法用来把一个Mess enBean接口定义中容器调用的第一
|
MessageDrivenContext EJB实例访问容器提供的运
|
对象封装了一个EJB消息驱动容 行时消息驱动上下文
|
器上下文的接口,支持消息驱动的
|
对于消息驱动的EJB来说,关键之一 器准备创建消息驱动EJB的实例时,它将 ,可能是因为它要构造一个Bean实例的缓 ejbCreate()方法和其他Bean上的EJB构造 始化方法。
|
是要实现一个没有参数的ejbCreate()方法。当EJB容 调用这个方法。容器之所以决定创建某个EJB的实例 冲池,也可能是因为它接收到了客户的请求。这个 方法类似,属于EJB实现的一种特殊的构造函数或初
|
当EJB容器准备不让Bea ejbRemove()方法。何时在 EJB客户程序的任何约束。 操作时,容器会调用ejbRem
|
n实例继续处理客户程序的请求 消息驱动的Bean上调用ejbRemov 应当注意的是,容器并不保证一 ove()方法;但是,当消息驱动
|
时,它就会调用消息驱动Bean的 e()方法由EJB容器单独决定,不受 定调用ejbRemove()方法 .在正常 的Bean向容器抛出了
|
系统异常时,不能保证 按时检查和清除Bean分配的
|
ejbRemove()方法一定会被调用 所有资源。
|
。由于这个原因,Bean开发者必须
|
对于Bean开发者来说,最重要的任务 由Bean实例处理时,容器将调用onMessag javax.jms.Message的实例,消息驱动的E 消息处理。
|
也许是实现onMessage()方法。当一个异步消息必须 e()方法。onMessage()方法的参数是一个普通的JMS JB实例从这个Message的实例提取待处理的数据完成
|
那么,在onMessage() 提取哪些信息呢?图二描述 息系统中,Message接口是 Root Interface)。Destin 传递模式,所以图二还显示
|
方法调用传入的 JMS消息中,消 了基本JMS消息类型的核心接口 在系统中传递的所有消息的最基 ation接口描述了消息传递的一 了Message接口与DeliveryMode
|
息驱动的Bean如何提取信息,可以 和概念。在一个以JMS为基础的消 本的接口(或称之为根接口, 个终端;类似地,由于消息有一个 接口的概念上的关系。
|
JMS消息的头信息可以通过一组标准 getJMSXXX()或setJMSXXX()形式(下面我 头信息中的属性名字,例如getJMSDelive set方法操作的标准头信息属性包括:唯 ,消息传递模式,消息类型,以及消息的
|
的方法设置或提取,这组标准方法的名字为 们分别称之为get方法和set方法),其中XXX是消息 ryMode()方法。在Message接口中,通过get方法和 一的消息ID,时标(Timestamp),答复和目标地址 优先级。
|
在JMS消息中,JMS容器 setXXXProperty()方法设置 operty(java.lang.String 。名字以JMSX前缀开头的属 消息头相对而言)的五种类 据由BytesMessage封装,Se 封装,键-值对由MapMessag
|
提供者特有的属性可以通过getX ,其中XXX表示属性的类型,例 name)。每一个属性有一个通过S 性作为标准JMS属性保留。与消 型对应,五种消息类型扩展了Me rializable对象由ObjectMessag e封装,I
|
XXProperty()方法提取,或通过 如byte getBytePr tring对象指定的名字和相应的值 息正文数据(或称之为消息体,与 ssage接口,如图三所示。Byte数 e封装,String消息由TextMessage
|
这些派生消息类型上的 础接口内,有一个通用的cl 。clearBody()方法只清除 消息后,消息正文的状态将
|
方法为特定类型的消息正文定义 earBody()方法,这个方法清除 消息正文,不清除消息头或属性 和新消息的空白正文状态一样。
|
了get和set操作,而在Message基 消息的正文,并把它置入只写模式 。如果消息正文是只读的,调用该
|
消息驱动Bean的客户程序并不知道接 动Bean的客户程序的构造方法与普通JMS 四所示。从图中我们可以看出,JMS Conn 称和目录接口(Java Naming and Direct
|
收端实际处理消息的将是一个EJB。事实上,消息驱 客户程序的构造方法完全一样。JMS的核心体系如图 ectionFactory(连接工厂)初始上下文通过Java名 ory Interface,JND
|
I)创建。随后,连接 可以获得创建消息生产者和 序就是消息的生产者,它发
|
工厂将用来创建与JMS服务提供 消息消费者的会话(Session) 送的消息将由消息驱动的Bean(
|
者的连接。有了JMS连接,我们就 。实际上,消息驱动Bean的客户程 即消息消费者)接收。
|
图五显示了在一个支持点对点消息队 上是核心JMS体系的一个扩展,特别地, 会话、消息生产者、消息消费者等都用点 利用JNDI获得一个QueueConnectionFacto
|
列的系统中JMS的基本体系结构。消息队列体系实际 它加入了对消息队列功能的支持。连接工厂、连接、 对点消息队列形式的接口进行了扩展。JMS客户程序 ry对象的引用。随后,我们用Que
|
ueConnectionFactory.createQueueC 的实例。调用createQueueConnection() 可以使用该方法不带参数的版本,此时假 Connection接口的一种子类型,它代表着 调用createQueueSession()方法创建Queu 一个boolean类型的参数指定了QueueSess 在createQueueSession()调用中通过参数 AUTO_ACKNOWLEDGE,CLIENT_ACKNOWLED
|
onnection()方法之一创建一个QueueConnection对象 方法时可以指定一个用户名字和密码,或者,我们也 定使用默认用户身份。QueueConnection接口是 一个与JMS点对点消息队列服务的连接。JMS客户程序 eSession的实例,createQueueSession()方法调用中 ion对象是否要提供事务支持。另外,回执的模式也 指定,这个参数的值可以是三个静态的标识符之一:
|
GE,DUPS_OK_ACKNOWLE ,调用Queue.getQueueName
|
DGE。QueueSession.createQueu ()方法可以返回队列的名字。
|
e()方法返回一个Queue对象的实例
|
QueueSession.createS 可以把消息发送到Queue。 这些不同的send()方法能够 send()方法调用中指定的Qu
|
ender()方法创建一个QueueSend 消息可以通过各种不同的QueueS 把消息发送给QueueSender对象 eue对象。消息递送模式、优先
|
er消息生产者,利用QueueSender ender.send()方法发送到Queue, 关联的Queue对象,或者发送给 级、消息的有效时间都
|
可以在调用QueueSender.send()方法 义的各种消息构造方法创建。
|
时指定。发送给Queue的消息可以用Session接口中定
|
图六显示了在一个支持发布-订阅消 机制也是核心JMS机制的一种扩展,增加 连接、会话、消息生产者、消息消费者等 通过JNDI获得一个TopicConnectionFacto
|
息模式的系统中JMS的基本体系结构。发布-订阅消息 了一些适合发布-订阅消息模式的功能。连接工厂、 都用发布-订阅形式的接口进行了扩展。JMS客户程序 ry对象的引用。TopicConnection
|
Factory.createTopicC createTopicConnection() 法不带参数的版本,此时假
|
onnection()方法用来创建Topic 方法时可以指定一个用户名字和 定使用默认用户身份。
|
Connection对象的实例。调用 密码,或者,我们也可以使用该方
|
TopicConnection接口 息服务的连接。JMS客户程
|
是Connection接口的一种子类型 序调TopicConnection.createTo
|
,它代表着一个与JMS发布-订阅消 picSession()方法创建Top
|
icSession的实例。会
|
话的事务支持和回执模式也在创
|
建TopicSession时指定。
|
TopicSession.createT 题目的地,发布者向该目的 不同的方式实现话题名称的 形式的描述。
|
opic()方法返回一个Topic对象 地发送消息,订阅者从该目的地 层次结构,调用Topic.getTopic
|
的实例。Topic接口封装了一个话 接收消息。不同的服务提供者按照 Name()方法可以获得话题的String
|
TopicSession.createP 息发布到Topic。消息可以 不同的publish()方法能够 publish()方法调用中指定 调用TopicPublisher.publi 的各种消息构造方法创建。
|
ublisher()方法创建一个TopicP 通过各种不同TopicPublisher.p 把消息发送给TopicPublisher对 的Topic对象。消息递送模式、 sh()方法时指定。发送给Topic
|
ublisher消息生产者,它用来把消 ublish()方法发布到Topic,这些 象关联的Topic对象,或者发送给 优先级、消息的有效时间都可以在 的消息可以用Session接口中定义
|
本示例应用是一个消息驱动Bean应用的简单例子,由以下两部分构成: |
SimpleMessageClient:J2EE应用客
|
户程序,向队列发送消息。
|
SimpleMessageEJB:一 息。
|
个消息驱动的Bean,异步地接收
|
和处理由客户程序发送到队列的消
|
图七描述了这个应用的 j2eeadmin命令创建。JMS提 Bean的实例处理消息。
|
结构。客户端应用把消息发送到 供者(这里是J2EE服务器)把消
|
队列,队列由管理员通过 息传递给消息驱动Bean的实例,由
|
SimpleMessageClient把消息发送到S
|
impleMessageBean监听的队列。客户程序首先确定
|
queueConnectionFacto
|
ry = (QueueConnectionFactory
|
) jndiContext.lookup
|
("java:comp/env/jms/MyQueueConne
|
ctionFactory");
|
queue = (Queue) jndi
|
Context.lookup("java:comp/en
|
v/jms/QueueName");
|
接下来,客户程序创建队列连接、会话和一个消息发送器: |
queueConnection = qu
|
eueConnectionFactory.createQ
|
ueueConnection();
|
queueSession = queueConnection.c
|
reateQueueSession(false,
|
Session.AUTO_ACKNOWLEDGE); |
queueSender = queueS
|
ession.createSender(queue);
|
message = queueSession.createTex
|
tMessage();
|
for (int i = 0; i < NUM_MSGS;
|
i++) {
|
message.setText("我是" + msgArray[i] ); |
System.out.println("
|
Sending message: " +
|
queueSender.send(message); |
SimpleMessageEJB类阐明了编写消息驱动Bean类的要求: |
实现MessageDrivenBean接口和Messa
|
geListener接口。
|
实现一个ejbCreate()方法和一个ejbRemove()方法。 |
与会话Bean和实体Bean不同,消息驱 先定位消息驱动的Bean,再调用这些Bean 们可以包含由onMessasge()方法内部调用 用消息驱动Bean的onMessage()方法。在S 的消息定型(cast)成为TextMessage类
|
动的Bean不定义客户程序访问的接口。客户程序不是 上的方法。虽然消息驱动的Bean没有业务方法,但它 的辅助方法。当队列接收到一个消息,EJB容器将调 impleMessageBean类中,onMessage()方法把接收到 型,然后显示出文本信息:
|
public void onMessage(Message in
|
Message) {
|
if (inMessage instanceof TextMes
|
sage) {
|
msg = (TextMessage) inMessage; |
System.out.println("MESSAGE BEAN
|
:收到消息: "
|
System.out.println("消息类型错误: " |
+ inMessage.getClass().getName()); |
} catch (JMSException e) { |
消息驱动Bean的ejbCre
|
ate()方法和ejbRemove()方法必
|
须符合以下要求:
|
访问控制修饰符必须是 throws子句不能定义任何应 ejbCreate()方法和ejbRemo
|
public。返回值类型必须是void 用自定义的异常。不能带有参数 ve()方法都是空的,不执行任何
|
。不能有static和final修饰符。 。在SimpleMessageBean类中, 有实际意义的操作。
|
接下来我们要把上面的应用打包成一 Jar文件。通常,打包过程可以通过工具 用模块部署描述器中,顶级元素下面包含 ,每一个元素描述一个消息驱动Bean的配 下所示。元素内定义了消息驱动Bean的配 、配置参数、安全信息、事务信息、消息
|
个J2EE EAR文件。首先要把SimpleMessageEJB打包成 完成,但理解模块部署描述器仍是必要的。在EJB应 元素。下面可以包含一组元素(按照EJB2.0新规范) 置和部署。SimpleMessageEJB的ejb-jar.xml文件如 置和部署信息,例如唯一的Bean名字、Bean类的名字
|
除了ejb-jar.xml部署描述器之外,
|
通常还要有面向特定平台和环境的部署描述器。大多
|
数时候,这种描述器可以用GUI工具编写。请参见下载代码中提供的例子。 |
打包好各个模块之后,接着还要把J2
|
EE应用打包成EAR文件。有关这一步骤的详细说明,
|
请参见开发平台的相关文档。本文以
|
后有关部署和运行的说明,就以打包后的EAR文件为
|
假设我们在Sun的J2EE 输出,我们必须以-verbose
|
参考实现上部署和测试这个示例 模式启动服务器:
|
应用。为便于查看消息驱动Bean的
|
j2ee -verbose用下面的j2eeadmin命令创建队列: |
j2eeadmin -addJmsDestination jms
|
/MyQueue queue
|
验证队列已经创建成功:j2eeadmin
|
-listJmsDestination
|
启动deploytool,选择菜单“File-->Open”,打开SimpleMessageApp.ear文件。接着选择菜单“Tools --> Deploy”,部署应用。出现部署提示时,选中“Return ClientJAR”检查框。在一个命令窗口中,进入EAR文件(SimpleMessageAppClient.jar文件)所在目录,把环 |
境变量APPCPATH设置为
|
SimpleMessageAppClient.jar。
|
然后,执行下面的命令:
|
runclient -client SimpleMessageA
|
pp.ear -name SimpleMessageClient -textauth
|
在登录提示中,输入用
|
户名字j2ee,输入密码j2ee。此
|
时,客户程序将输出以下内容:
|
在启动j2ee服务器的命令窗口,我们可以看到如下输出: | |
|
|
|
|