Sun Java System Studio IDE 中的EJB 构建器使你能开发消息驱动bean(Message-Driven Beans) ,这类bean 能支持一个应用客户端的异步处理过程。本章将讨论创建和处理消息驱动bean 的过程。这些bean 的事务通常由EJB 容器来管理,但如果你愿意,你可以自己提供事务管理代码。
采用消息驱动bean 有以下几个原因:
性能和对多任务的支持。 客户端程序 可以发出一个消息后继续其他的任务而不必等待该消息的响应。就是说,该客户端可以异步调用你的消息驱动 bean 。
可靠性。 如果应用系统 使用了 Java 消息服务( Java Message Services , JMS ),除非应用层突然瘫痪否则不会丢失任何客户端请求。
但消息驱动bean 并不总是正确的选择。例如在下列情况中其他方式可能会工作的更好:
当客户端需要确认请求已收到或需要返回一个结果时
当该操作是一个时间敏感性事务( time-sensitive transaction )的一部分并且不能在非高峰期完成时
当应用很小且并不复杂,同时添加其他层会减缓创建、调试和执行速度时
该IDE 提供了一个向导来让你创建一个消息驱动bean 所需要的一个单独的bean 类。因为一个消息驱动bean 只是从一个客户端提出消息并用它们开始其他的bean 处理,所以不需要其他的接口类。向导会自动执行创建一个消息驱动bean 所需的许多任务,而你需要用IDE 的源码编辑器和属性页来完成创建任务。
编写消息驱动bean 时你还有本章没有说明的一些选项。虽然Sun ONE Studio IDE 被设计用来帮你做许多编码工作,但该IDE 还灵活地支持那些选项并把决定权留给你。要获得更多信息,参见列在开始之前 的相关资源,或者可以参考编写企业bean 方面的众多优秀教材中的一本。
对消息驱动 Bean 使用 EJB 构建器
创建一个 bean 需要的类。 执行过 EJB 构建器向导之后你就有了一个消息驱动 bean 的框架,它由其 bean 类和一个该 bean 的其他部分的逻辑组构成。注意该类和逻辑组都会显示在对象管理器的“ Filesystems ” tab 页中,还有它们的子结点。该向导会为 bean 类生成两个必须的方法的声明: ejbCreate 和 onMessage 。然后你可以提供方法的实现。
逻辑结点是处理消息驱动 bean 的最好地方。一个逻辑结点在对象管理器中显示这一图标:
如果需要,完成 bean 类结点。 使用本章随后描述的 IDE 支持。
在一个 bean 的布署描述符中设定值。 用来自逻辑结点的消息驱动 bean 属性页编辑属性。
从一个消息驱动bean 的逻辑结点你可以检查该bean 代码的有效性。
决定事务管理
在创建消息驱动bean 之前,你首先要考虑是让容器来管理你的bean 要执行的事务还是自己编写这些代码。你可以用IDE 中EJB 构建器的不同执行过程来创建这两种bean 。表7-1说明了设计时的考虑。
表 7-1 在容器管理和 bean 管理的事务间作出决定
问题
容器管理的事务(BMP)
Bean管理的事务(BMP)
事务的管理者
容器自己是事务管理者。
你利用JTA自己写代码管理事务。这可以包含用于其他资源如JDBC的事务。
事务边界的设定
EJB容器根据Java2平台企业版规范 来决定何时开始和提交一个事务。
程序 员显示地编写事务边界来获得对事务更细化的控制。
事务时效
消息驱动bean在同一个事务中接收一个消息并执行它的业务方法。
事务直到该消息驱动bean收到消息之后才开始。
问题处理
容器会回滚该事务并通知该bean这一消息。
该消息驱动bean根据你生成该bean后指明的通知模式来做出响应。
关于这些选项的更多信息,参见书构建J2EE应用 中关于事务的那一章。
本章剩下的部分将介绍如何创建每种消息驱动bean和开发中要考虑的问题。
定义一个消息驱动 Bean
EJB 构建器向导会自动执行创建你的消息驱动bean 所需的一个bean 类的许多任务。要定义一个消息驱动bean ,按以下步骤执行:
1. 选择或创建一个包含该bean 的包。
2. 利用EJB 构建器向导生成你的消息驱动bean 的基本结构。
3. 完成onMessage 方法体,并且如果需要,完成setMessageDrivenContext 和ejbCreate 方法。
这些基本步骤随后会详细解释。
完成本章讲述的步骤之后你必须给你完成的bean 的属性页中添加信息以便它可以同其他bean 交互、查找它的资源、以及监听相应的消息。这些关于准备把你完成的bean 用于一个应用中的步骤会在第8章讨论。
创建一个包
如果你需要创建一个包来保存你的消息驱动bean ,选择一个文件 系统 ,单击鼠标右键并选择“New Java Package ”
启动 EJB 构建器向导
当你准备要创建一个消息驱动bean 时,按以下步骤执行:
bean
在EJB 构建器的“Message-Driven Bean Name ”和“Properties ”面板中命名你的消息驱动bean 并决定如何管理该bean 可能执行的事务。缺省值是容器管理的事务,但如果你愿意,你可以在该bean 类中提供所有事务管理代码。
做出你的选择后你可以单击“Finish ”。(或者你可以单击“Next ”以打开你可以为你的消息驱动bean 指明一个既存的bean 类的面板。这之后你再单击“Finish ”。)
你新建的消息驱动bean 会显示在IDE 对象管理器中的“Filesystems ”面板中。该bean 的基本结构(它的基本bean 类和两个组件方法)已经被该EJB 构建器自动生成了。
在对象管理器中查看你的消息驱动 bean
图 7-1 展示了一个典型的消息驱动bean 是如何在对象管理器的“Filesystems ”面板中显示的。
图 7-1 一个典型的消息驱动bean 的缺省类和方法
在显示的两个主要结点中,一个是逻辑结点(用一个bean 图标表示)一个表示其实际的类(用一个类图标表示)。在逻辑结点上进行你的所有编辑工作。该bean 的两个主要结点接下来会说明。
在对象管理器中创建的逻辑结点是用来组织你的消息驱动 bean 的所有元素的,而且使你对它们的处理更方便。
该 bean 类实现了 java x.ejb.MessageDrivenBean 和 java x.jms.MessageListener 接口,并且展现了消息驱动 bean 的方法。
“Classes ”结点包含bean 类的代码,它包括了这些方法。 “Create Method ”结点指向了初始化你的消息驱动bean 的代码。“OnMessage Method ”结点指向收到一个消息时要调用的那个方法。
展开结点
当你展开你的消息驱动bean包下面的两个结点时,你能看到图7-2那样的视图。
图 7-2 一个典型消息驱动bean的对象管理器详细视图
回顾生成的类
向导会自动在每个消息驱动bean中放入某些缺省的方法:一个创建方法、一个onMessage方法、以及两个生命期方法。如表7-2所示,该创建方法ejbCreate与其他类型企业bean工作的很类似,但onMessage 方法是一个新且不同的方法。
表 7-2 一个消息驱动 bean 类中 ejbCreate 和 onMessage 方法的目的
方法
目的
ejbCreate
该方法在需要的时候初始化该bean
onMessage
这一方法打开该消息驱动bean收到的消息、决定用它做什么、以及处理它。
向导还添加了表7-3 描述的缺省的生命期方法.
表 7-3 一个消息驱动 bean 类中缺省生命期方法的目的
方法
目的
setMessageDrivenContext
该方法在ejbCreate方法之前调用,它把该消息驱动bean同一个context对象关联。
ejbRemove
该方法在该消息驱动bean实例刚被删除时调用,它释放不再用到的资源。在一个简单的消息驱动bean中这一方法可能根本用不到。
完成你的消息驱动 bean
要完成你的消息驱动bean ,执行以下步骤:
添加代码完成你的 bean 的 onMessage 方法体。
添加你的 bean 的 setMessageDrivenContext 方法需要的任何代码。
简单的消息驱动 bean 不需要 ejbCreate 和 ejbRemove 方法。但如果确实需要,那么 ejbCreate 方法可以用来分配资源而 ejbRemove 可以释放那些资源。
用属性页(发布你的 bean 的应用服务器 的 tab 接口)指明资源类型、资源工厂、以及该消息驱动 bean 用到的服务器 。 为客户消息驱动bean指明资源和第8章提供了这方面的详细情况。
单击对象管理器中逻辑bean 结点下的bean 组件来打开源码编辑器完成其他工作。
在处理企业 Bean 时使用推荐的方法
附录A 探讨了修改你的企业bean 的最佳方式,以及当你采用其他方式时可能会遇到的错误和异常。作为一条总的原则,你应该通过逻辑结点而不是单独的类结点来工作,利用bean 的属性页(property sheets )或定制对话框(Customizer dialog box )来编辑方法,以及使用IDE 的源码编辑器(Source Editor )来完成或编辑所有通过这些对话框访问不到的bean 代码。
完成 onMessage 方法
你的消息驱动bean 的一个实例在某一时刻只能处理一个消息,并且该bean 只能有一个onMessage 方法。下面是一个完整方法的例子。
public void onMessage(Message inMessage)
{ TextMessage msg = null;
try
{ if (inMessage instanceof TextMessage)
{ msg = (TextMessage) inMessage;
System.out.println("MESSAGE BEAN: Message " +
"received: " + msg.getText());
}
else { System.out.println("Message of wrong type: " +
inMessage.getClass().getName());
}
} catch (JMSException e)
{ System.err.println("MessageBean.onMessage: " +
"JMSException: " + e.toString());
context.setRollbackOnly();
} catch (Throwable te)
{ System.err.println("MessageBean.onMessage: " +
"Exception: " + te.toString());
}
}
完成 setMessageDrivenContext 方法
setMessageDrivenContext 方法在一个域中保存消息驱动上下文并且构造非持久性域。如果需要,你可以用这一方法来分配独立于该 bean 对象且在整个 bean 生命期中都会保持的资源。这些资源可能包括一个队列连接( queue-connection )或者主题连接工厂( topic-connection factory )。
缺省地,EJB 构建器向导会生成把消息驱动上下文赋给一个叫做coontext 的非持久性域的代码。通常,你不需要再给生成的方法加什么东西。但是,如果你确实需要完成它,拷贝生成的context 代码到实例变量中。如:
表 7-4 一个 setMessageDrivenContext 方法的例子
public void setMessageDrivenContext(java x.ejb.MessageDrivenContext aContext ) {
this.context=context;
}
创建你的消息驱动 bean 之后
除需要其他几步工作为该bean 用于其最终环境作准备外,你的消息驱动bean 现在已经完成了。你必须在属性页中指明下述信息:
该 bean 的消息驱动目标,即,该 bean 是从一个队列( queue )还是一个主题( topic )中取它的消息
如果该 bean 监听一个主题,它的订阅是持久的还是非持久的
该 bean 是否采用了一个选择器(过滤器)来缩小其获得消息的范围。
如果你的消息驱动bean 将从一个客户端那里接收消息并且你打算把你的bean 发布到J2EE 引用实现应用服务器 (reference implementation application server ,RI )上,你必须在该bean 的属性页中的“J2EE RI ”tab 页中指明其目标。
如果你的消息驱动bean 自己就是一个发送消息到一个目标的客户端,你必须在该bean 属性页中的“References ”tab 页中指明下述信息:
该 bean 的资源引用(它用到的用于访问其消息驱动目标的连接工厂)
该 bean 的资源环境引用(实际的目标:队列或者主题)
接着会讨论这些属性的设定。
指明一个消息驱动目标
要指明该消息驱动bean 是一个队列监听器还是一个主题监听器,执行以下步骤:
1. 在IDE 对象管理器中,单击该消息驱动bean 的逻辑结点并选择“Properties ” 。
该bean 的属性页显示出来。
2. 在“Properties ”tab 页中, 单击“ Message-Driven Destination ”域后单击省略号按钮(...) 。
属性编辑器显示出来
3. 选择 “Queue ”, “Topic ”, 或者“(Not Set) ”。
如果客户端只给这一特定 bean 发送消息并且你需要使用点对点模式,选择“ Queue ”。
如果你需要通过发布—订阅模式允许多个客户端给这个 bean 发送消息,选择“ Topic ”。如果你选择了“ Topic ”,你还必须指明该 bean 的订阅是持久( Durable )的还是非持久( non-durable )的。
如果消息必须一直保存直到被该 bean 处理,那么选择“ Durable ”。通过这种方式,即便该 bean 的应用服务器 崩溃,在下一次该 bean 可用时依然可以获得该消息。
如果该 bean 只接收在其可用时发布的消息,那么选择“ Non-durable ”。所有其他消息都会被删除。
如果你想以后再设置这一属性就保持该域为空(选择“ Not Set ”值)。
4. 单击“OK ”关闭属性编辑器。
指明一个消息选择器
如果你想过滤传给你的bean 的消息,执行以下步骤:
1. 单击“Message Selector ” 域后再单击省略号(... )按钮。
一个属性编辑器显示出来。
2. 如果你想减少你的bean 必须监听的消息数目,指明一个过滤器。
3. 单击“OK ”关闭属性编辑器。
为客户消息驱动bean 指明资源
一个消息驱动bean 属性页的“References ”tab 页包含“Resource Reference ”和“Resource Environment Reference ”域。这些域是代发送消息的客户端来设置的。例如,你的消息驱动bean 可能是一个应用的一部分,该应用中的一个web 模块发送消息到一个队列供你的bean 用。在该例子中,“Resource Reference ”和“Resource Environment Reference ”就应该由该web 模块的提供者来指明。
或者,如果你的消息驱动bean 本来在它自己的模块中就被当作一个发送消息到一个队列或者主题的客户端,那么你可以在这里指明资源工厂和资源。
明资源工厂 (reference factories)
要把该消息驱动bean 同一个创建目的对象的工厂对象关联,执行下述步骤:
1. 在“References ”tab 页中,单击“Resource References ”域后再单击省略号(... )按钮。
在属性编辑器中的域就是用来指明客户端(或作为客户端的消息驱动bean )用于访问消息资源的连接工厂的。
2. 单击“Add ”按钮。
带有两个tab 页的“Add Resource Reference ”对话框显示出来。
在“ Standard ” tabb 页中:
键入创建你的 bean 到它的队列或主题的连接的对象的名字。
在“ Type ”组合框中选择你的 bean 将要用的资源工厂的类型。这一类型应该和你在“ Properties ” tab 页中的“ Message-driven Destination ”域中选择的相对应。参见 指明资源环境引用 查看对各种资源工厂类型的解释。
在“ Authorization ”域中,指明是 EJB 容器还是应用客户端来给该 bean 授权使用该资源。
在“ Sharing Scope ”域中,指明到这一资源的连接是否可以被相同应用中的其他企业 bean 来共享。如果两个或者多个 bean 可以在同一个事务上下文中使用相同的资源,容器就可以在本地执行事务并节省时间。
如果你打算把你的消息驱动bean 发布到RI 上,完成以下步骤:
在“ J2EE RI ” tab 页中:
在“ JNDI Name ”域中,键入定位该资源工厂所用到的名字。
在“ User Information ”域中提供任何访问该资源时用到的信息。
如果适合你的情况,在“ Mail Configuration ”域中提供使用一个 JavaMail 会话工厂将用到的信息。
3. 完成后单击“OK ”关闭对话框。
指明资源
要把该消息驱动bean 同一特定目标对象关联,执行下述步骤:
1. 在“References ”tab 页中,单击“Resource Environment References ”域后再单击省略号(... )按钮。
在属性编辑器中的域就是用来指明客户端(或作为客户端的消息驱动bean )将要发送消息的实际资源。
2. 单击“Add ”按钮
带有两个tab 页的“Add Resource Environment Reference ”对话框显示出来。
在“ Standard ” tab 页中:
键入你的客户端或 bean 将要发送消息的队列或者主题的名字。
在组合框中选择一个资源类型
如果你打算把你的消息驱动bean 发布到J2EE 资源实现应用服务器 (RI )上,完成以下步骤:
在“ J2EE RI ” tab 页中键入该消息资源(队列或者主题)用到的 JNDI 名称。
3. 完成后单击“OK ”关闭对话框。
为发布到 RI 上的消息驱动 bean 监听器指明资源
如果你的bean 要发布到RI 上,你不仅要说明它是监听一个队列还是监听一个主题。你还要指明该资源的JNDI 名称以便RI 可以找到它。执行下述步骤:
1. 在“J2EE RI ”tab 页里单击“Destination JNDI Name ”域再单击省略号(... )按钮.
2. 在属性编辑器中为你的bean 指明消息资源的JNDI 名称。
用类型 / 资源 的形式,例如,一个消息队列可以指明为jms/myQueue 。
请阅读第2章中关于消息驱动目标的更多内容以及第8章中关于设置属性的更多内容 。
避免消息驱动 bean 的缺陷
如果你能理解下面可能的复杂情况,你的应用系统 的消息层就很少会出问题:
消息顺序。 你的消息驱动 bean 应该对没按顺序到达的消息有所准备。一个 JMS 服务器 可能会以任一顺序给一个消息驱动bean 池发送消息。
未调用的 ejbRemove 方法。 一个简单的消息驱动 bean 不需要使用 ejbCreate 或者 ejbRemove 方法,但是,如果你的 bean 很复杂并确实需要这些方法,记住在某些情况下(比如系统 或者容器崩溃) ejbRemove 可能不会被调用。这种情况下,你应该准备让该 bean 自己完成清理工作。这也依赖于你的应用服务器 的行为。更多信息请参考你的服务器 文档。
有害消息。 当你用 EJB 构建器向导来生成一个自己管理事务的消息驱动 bean 的基本结构时,你可以设定 bean 的“ Acknowledge Mode ”为“ Auto ”。这一设定会使该 bean 在每次收到消息后自动发出通知。按这种方式,你可以避免事务失败而消息目标却不知道消息已收到并继续不断发消息的情况。
要获得更详细的设计上的考虑,请参见企业级JavaBean 规范版本2.0 以及关于编写企业bean 的教材。