【永恒的瞬间】
☜Give me hapy ☞
 

Message-driven Bean

Message-driven Bean是EJB2.0 规范中提出的的Enterprise Bean

Message-driven Bean 的产生原因

效率原因

在JavaEE™平台中,客户端对Session Bean 和Entity Bean 的方法调用通过RMI或RMI-IIOP协议进行,这是传统的通过网络进行远程调用的方法,当调用请求通过网络传播到容器,容器则将客户端请求变成一序列的方法调用依次进行。客户端只有在容器处理完请求并返回结果后方可继续进行。

可靠性的原因

当客户端对Session Bean或Entity Bean进行调用时,必须保证服务器容器处于运行状态,如容器或网络出现错误,客户端调用将无法进行。

事件的广播

传统的RMI 或RMI-IIOP机制中,客户端在某一时刻只能与某一具体的服务器通讯, 没有任何内置的机制来将事件广播到多个服务器。

Message-driven Bean作为远程方法调用的一种替代方法,在客户端和服务器的直接方法调用之间放置了一个中间层,接收一个或多个客户端的消息,并将消息转发给一个或多个消息的使用者(Message Consumer)。

通过消息机制而非直接的方法调用,客户端可以继续执行而不必等待服务器的运行结果,服务器可以选择在方法调用完成后通知客户,而消息机制本身保证了信息传输的可靠性,同时使用消息域(Message Domain)中的消息类型模型以达到事件广播的机制。

但是,对Message-Driven Bean 的使用也有一定的限制,如不适用于依赖于方法调用、要求具有明确的返回值才能继续的客户端程序。另外,如果在一个应用中过多的使用了Message-Driven Bean, 对应用的执行效率将会产生影响,所以不适用于对时间因素敏感的客户端程序(如在下午两点定购下午四点的机票,而在四点后才得到订购是否成功的结果,这时结果已毫无用处)。

Message-driven Bean 作为一般的JMS 使用者(consumer)

作为一种具有JMS使用者(consumer)功能的Enterprise Bean组件模型,Message-Driven Bean由EJB容器进行管理,具有一般的JMS使用者(consumer)所不具有的优点,如对于一个Message-driven Bean,容器可创建多个实例来处理大量的并发消息,而一般的JMS使用者 (consumer)开发时则必须对此进行处理才能获得类似的功能。同时Message-Driven Bean可取得EJB所能获得的标准服务,如容器管理事务等服务。

由于与Message-driven Bean相关的主题(Topic)或队列(Queue)可以在部署时配置,因此,Message-driven Bean具有更多的灵活性。

但注意,一个Message-driven Bean在部署时只可与一个具体的主题(Topic)或队列(Queue) 建立关联。如有多个主题(Topic)或队列(Queue)需要与一个Message-driven Bean关联,则 可以在部署时部署多个Message-driven Bean类,或使用一般的JMS使用者(consumer) 。

Message-driven Bean 与其他Enterprise Bean

作为Enterprise Bean组件模型之一,Message-driven Bean,具有一些与Session Bean 和Entity Bean相同的方法,但由于Message-driven Bean本身不处理客户端调用,也无会话状态,客户只能通过向与Message Driven Bean关联的队列或主题发送消息从而与Message-driven Bean 进行交互,因此,Message-driven Bean 与Session Bean 和Entity Bean之间最大的不同之处在于Message-Driven Bean不具有组件接口及Home接口。

另外,Message-driven Bean异步地处理队列(Queue)或主题(Topic)中的消息,而非方法调用。

Message-driven Bean

Message-driven BeanJMS消息驱动的JavaEE™平台服务器端组件,具备无状态、支持事务的特点。当从JMS队列(Queue)或主题(Topic)中接收到JMS消息后,由容器对组件进行调用。一般,可理解为消息的监听器(Listener)及接收者(Consumer)。

Message-driven Bean组件对于客户端是不可见的。客户端如希望调用封装在组件中的业务逻辑,只能通过向组件监听的JMS队列(Queue)或主题(Topic)发送消息,然后容器以事件的形式向组件实例发送消息,组件实例根据消息的内容调用相应的业务逻辑或其他组件。

因此,任何向组件监听的特定JMS队列(Queue)或主题(Topic)发送JMS消息的客户端,即可视为Message-driven Bean的客户端。

Message-driven Bean组件模型不具备会话状态,也就是说,当组件实例在没有对客户端的JMS消息提供处理的时候,所有的实例间没有差别。

Message-driven Bean的运行和客户端的运行是异步的。同时,对于客户端不可见。其实例由容器创建,其生存周期由容器控制。

Message-driven BeanEJB容器、客户端、消息系统

下图是EJB容器、客户端、消息系统与EJB之间的关系:

EJB容器、客户端、消息系统与EJB之间的关系

客户端发送消息到JMS消息系统中的队列或主题。Message-driven Bean在部署到容器中时,指定的队列或主题,容器对其进行监听;当容器从队列或主题中接收到消息之后,将消息作为事件的一部分通知容器中相应的Message-driven Bean实例。

组件模型单元

Message-driven Bean由容器控制其生存周期,容器提供安全、并发、事务等等服务,对于客户端来说,Message-driven Bean是不可见的。因此,Message-driven Bean不同于Session BeanEntity Bean,不具有组件接口和Home接口。

Message-driven Bean组件模型包含两个单元,即组件类和部署描述。下面分别对开发这些单元时,涉及的普遍过程、规则及注意事项进行描述。

组件类

 javax.ejb.MessageDrivenBean接口

EJB2.1规范中的Message-driven Bean组件中的组件类必须实现MessageDrivenBean接口。EJB3.0规范不强制Message-driven Bean实现该接口,而通过@MessageDriven注解进行标记并通过依赖注入与注解实现类似功能。

MessageDrivenBean接口中定义了两个容器管理回调的方法:

·             setMessageDrivenContext方法,容器创建Bean实例后,容器将调用该方法将由容器维护的Bean实例的上下文(context)与Bean实例进行关联。EJB3.0规范中,可使用@Resource注解通知容器注入MessageDrivenContext实例。

·             ejbRemove方法,在实例被容器清除时,容器将调用此方法。一般,实例会在此方法中对实例占用的资源进行释放。在EJB3.0规范中,可使用@PreDestroy注解标记此方法。

 javax.jms.MessageListener接口

Message-driven Bean组件中的组件类必须实现MessageListener接口。

在消息到达Message-driven Bean指定的监听队列或主题时,容器将调用javax.jms.MessageListener接口中定义的onMessage方法。开发者在此方法中提供对消息进行处理的业务逻辑。

Session BeanEntity Bean不可实现javax.jms.MessageListener接口。

 javax.ejb.MessageDrivenContext接口

容器将提供一个MessageDrivenContext对象,使实例可以访问由容器维护的实例的上下文环境。在此接口中,定义了如下方法:

·             getEJBHomegetEJBLocalHome方法,从EJBContext接口继承的方法,Message-driven Bean实例不可调用此方法;

·             getCallerPrincipal方法,从EJBContext接口继承的方法,Message-driven Bean实例不可调用此方法;

·             isCallerInRole方法,从EJBContext接口继承的方法,Message-driven Bean实例不可调用此方法;

·             setRollbackOnly方法,当前事务将被永久标记为回滚,不会被提交。只有容器管理事务的Message-driven Bean可被允许使用此方法;

·             getRollbackOnly方法,检查当前事务是否已被标记为回滚。例如,EJB实例可以通过此方法,决定是否继续在当前事务边界内继续进行计算。只有容器管理事务的Message-driven Bean可被允许使用此方法;

·             getUserTransaction方法,返回javax.transaction.UserTransaction接口。EJB实例可通过此接口对事务边界进行划分,并取得事务的状态。只有容器管理事务的Message-driven Bean可被允许使用此方法;

 串行化的调用

Apusic应用服务器中的EJB容器支持Message-driven Bean的多个实例的并发运行,但是每个实例只看到一个串行的方法调用过程,因此开发Message-driven Bean时,不需要将其以可重入(reentrant)的方式进行编写。

 消息处理的并发

Apusic应用服务器允许Message-driven Bean的多个实例并发执行,提供对流(Stream)消息并发处理。

 Message-driven Bean方法的事务上下文

onMessage方法在何种事务范围内被调用,取决于部署描述中指定的事物属性,如Bean被指定使用容器管理的事务的方式,则必须将事务属性设置为“Required”“NotSupported”

Bean采用Bean管理的事务的方式,即使用javax.transaction.UserTransaction接口进行事务划分时,导致Bean实例被调用的消息接收操作并非是事务中的一部分。如果希望消息接收操作是事务中的一部分,则Bean必须使用容器管理事务的方式,并且设置事务属性为“Required”

 消息接收确认(Message Acknowledgement

Message-driven Bean不能使用JMS API中提供的消息接收确认操作。消息接收确认操作由容器自动完成。如Bean采用了容器管理事务的方式,则消息接收确认操作作为事务提交的一部分自动进行。如使用了Bean管理事务的方式,消息接收确认操作不能作为事务提交的一部分,开发者可通过在部署描述中的acknowledge-mode元素指定消息接收确认操作的方式为AUTO_ACKNOWLEDGEDUPS_OK_ACKNOWLEDGE,如未指定acknowledge-mode元素,容器将使用AUTO_ACKNOWLEDGE方式进行消息接收确认操作。

指定队列(Queue)或主题(Topic

Message-driven Bean被部署到容器时,必须关联到某个消息队列(Queue)或主题(Topic),以便容器对此队列或主题进行监听。

开发者可通过@MessageDriven注解的mappedName属性或部署描述文件中的message-driven-destination元素指定关联的队列或主题。

Bean关联的是一个消息主题,则通过部署描述中的subscription-durability元素指定对队列进行的是持久还是非持久订阅,如此元素未指定,则使用非持久订阅的方式。

 异常处理

Message-driven Bean中的onMessage方法不能声明抛出java.rmi.RemoteException异常。

一般来说,Message-driven Bean在运行期间不应向容器抛出RuntimeException异常。RuntimeException异常是指会导致Message-driven Bean进入不存在状态的非应用级异常。如果Bean使用了Bean-managed事务并抛出了RuntimeException异常,容器不应确认收到了这条消息。

从发信端看来,收信端一直存在,若发信端继续对该目的地发送消息,容器会自动把消息转向到其他Message-driven Bean实例。

遗漏的PreDestroy调用

在系统发生异常的情况下,不能保证容器总会调用BeanPreDestroy方法,因此,如果BeanPostConstruct方法中打开了一些资源,并在PreDestroy方法中释放这些资源,在这种情况下,则这些资源不能被释放。

鉴于以上原因,使用Message-driven Bean的应用需要提供一种机制,以便周期性的清除这些未释放的资源占用。

 必须遵守的规则

在开发Message-driven Bean时,开发者必须遵守如下规则:

 组件类

·             使用EJB2.1规范时,必须间接或直接实现javax.ejb.MessageDrivenBean接口;使用EJB3.0规范时,可改为使用@MessageDriven注解对组件类进行标记。

·             必须间接或直接实现javax.jms.MessageListener接口;

·             类必须声明为public,不可被声明为finalabstract类;

·             必须拥有一个无参数的public构造函数(constructor);

·             类不能定义finalize()方法;

·             在原EJB2.1规范中,类必须实现ejbCreate()方法用来创建组件实例,在EJB3.0中,这一要求已被移除了。EJB3.0的兼容规则规定,如果Message-driven Bean类实现了ejbCreate()方法,将看作被@PostConstruct注解标记的方法处理。此时若同时使用@PostConstruct注解,则只能标记ejbCreate()方法。

 onMessage方法

·             方法必须被声明为public

·             方法不能被声明为finalstatic

·             返回值必须为void

·             方法只能有一个javax.jms.Message类型的参数;

·             不能抛出java.rmi.RemoteException异常

·             运行期间一般来说不应抛出RuntimeException。请参考:

 ejbRemove方法

·             方法名必须是ejbRemove;

·             方法必须被声明为public

·             方法不能被声明为finalstatic

·             返回值必须为void

·             方法不能有参数;

·             不能抛出java.rmi.RemoteException异常。

·             EJB3.0规范中,可使用@PreDestroy注解实现同样效果。若实现javax.ejb.MessageDrivenBean接口同时使用注解,则只能把ejbRemove()方法注解为@PreDestroy

生存周期

下图表示Message-driven Bean的生存周期。

Message-driven Bean的生存周期

posted on 2007-10-22 12:27 ☜♥☞MengChuChen 阅读(2683) 评论(0)  编辑  收藏 所属分类: EJB3.0

只有注册用户登录后才能发表评论。


网站导航: