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 Bean是JMS消息驱动的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 Bean与EJB容器、客户端、消息系统
下图是EJB容器、客户端、消息系统与EJB之间的关系:
EJB容器、客户端、消息系统与EJB之间的关系
客户端发送消息到JMS消息系统中的队列或主题。Message-driven Bean在部署到容器中时,指定的队列或主题,容器对其进行监听;当容器从队列或主题中接收到消息之后,将消息作为事件的一部分通知容器中相应的Message-driven Bean实例。
组件模型单元
Message-driven Bean由容器控制其生存周期,容器提供安全、并发、事务等等服务,对于客户端来说,Message-driven Bean是不可见的。因此,Message-driven Bean不同于Session Bean和Entity 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 Bean和Entity Bean不可实现javax.jms.MessageListener接口。
javax.ejb.MessageDrivenContext接口
容器将提供一个MessageDrivenContext对象,使实例可以访问由容器维护的实例的上下文环境。在此接口中,定义了如下方法:
· getEJBHome、getEJBLocalHome方法,从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_ACKNOWLEDGE或DUPS_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调用
在系统发生异常的情况下,不能保证容器总会调用Bean的PreDestroy方法,因此,如果Bean在PostConstruct方法中打开了一些资源,并在PreDestroy方法中释放这些资源,在这种情况下,则这些资源不能被释放。
鉴于以上原因,使用Message-driven Bean的应用需要提供一种机制,以便周期性的清除这些未释放的资源占用。
必须遵守的规则
在开发Message-driven Bean时,开发者必须遵守如下规则:
组件类
· 使用EJB2.1规范时,必须间接或直接实现javax.ejb.MessageDrivenBean接口;使用EJB3.0规范时,可改为使用@MessageDriven注解对组件类进行标记。
· 必须间接或直接实现javax.jms.MessageListener接口;
· 类必须声明为public,不可被声明为final或abstract类;
· 必须拥有一个无参数的public构造函数(constructor);
· 类不能定义finalize()方法;
· 在原EJB2.1规范中,类必须实现ejbCreate()方法用来创建组件实例,在EJB3.0中,这一要求已被移除了。EJB3.0的兼容规则规定,如果Message-driven Bean类实现了ejbCreate()方法,将看作被@PostConstruct注解标记的方法处理。此时若同时使用@PostConstruct注解,则只能标记ejbCreate()方法。
onMessage方法
· 方法必须被声明为public;
· 方法不能被声明为final或static;
· 返回值必须为void;
· 方法只能有一个javax.jms.Message类型的参数;
· 不能抛出java.rmi.RemoteException异常
· 运行期间一般来说不应抛出RuntimeException。请参考:
ejbRemove方法
· 方法名必须是ejbRemove;
· 方法必须被声明为public;
· 方法不能被声明为final或static;
· 返回值必须为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 阅读(2688)
评论(0) 编辑 收藏 所属分类:
EJB3.0