提要:我们可以使用MessageDrivenBean(消息驱动组件),在企业级的应用程序中进行异步的消息传送。
引言:Enterprise JavaBeans(EJB)1.1版本中定义了两种组件类型?session组件和entity组件。客户端对象可以同步调用EJB1.1的这两种组件的方法,然而,为了继续Message Oriented Middleware(MOM,面向对象的中间件)和Java Message Service(JMS,Java消息服务)的优点的需要,EJB框架中也相应的应当加入异步的消息通讯机制,所以,在EJB2.0中就定义了第三种组件类型----MessageDrivenBean(消息驱动组件)
MessageDrivenBean兼备EJB和JMS的功能,当然,假如您想要消息操作技术,那么您大可只使用JMS就行了,但是新的消息驱动组件MessageDrivenBean提供了消息通讯的新的可能性。那么,这些组件如何整合到一个应用程序服务器框架中?他们的功能又如何扩大了过去JMS服务器的使用范围?让我们看下文吧!
EJB和JMS
前面我们已经说过了,EJB1.1种为开发者定义了两个企业级组件类型-----session和entity组件。session组件通常实现一些商业逻辑并且不能在多客户端共用。Entity组件则描述一个实体的面向对象的概念,而这个实体往往存在于像数据库那样固定的存储容器中。在这两种组件模型中,使用本地的或远程的接口来简化客户端的交互作用。按照定义,这种交互作用是严格的同步的。举例来说,通过一个方法调用把一个请求发送给组件,然后服务器对象返回一个响应。(如图1),
然而,在企业版应用程序的范围中,也经常需要异步的消息传递,比方说,一个客户可能想发给服务器一条信息,但是并不需要或者不想要服务器做出应答,这时,客户端就没有必要等待服务器对象处理请求。对于客户端应用程序来说,在确保消息最终能够到达服务器并被正常处理的前提下,提交一条消息然后继续处理本身的事务,将会在很大的程度上提高效率。
能够处理异步消息的能力的Java技术可以在Java Message Service(JMS)中找到,JMS原本就是被开发来提供传统的Message Oriented Middleware(MOM)产品的一个标准Java接口。
现在,一些公司开发出了一整套新一代轻量级高效的纯Java的JMS产品,这些产品是开发者能够建立JMS连接来发布或从其它应用程序组件中接受消息。下面的例程给出了与一个JMS提供者接口的必要步骤:
代码段一:预备客户端
客户端应用程序使用了带有JMS 主题的MessageListener来接受和处理消息。
import javax.jms.*;
/**
*一个例程,演示如何取得一个JMS
*连接并取得一个消息监听者。在本例中
*我们将获取一个与一个JMS主题的连接
*/
public class JMSSample {
public static void main (String args[])
{
InitialContext context = new InitialContext();
// 查找主题
Topic topic (Topic)context.lookup("MyTopic");
file://取得我们创建JMS连接时所要用到的连接创建器
TopicConnectionFactory tcf =
(TopicConnectionFactory)context.lookup(
"TopicConnectionFactory");
// 创建JMS连接
TopicConnection conn = tcf.createTopicConnection();
// 从连接中创建JMS session。
// 这样我们就可以创建一个非事务处理、AUTO_ACKNOWLEDGE的连接
TopicSession session = conn.createTopicSession(
false, Session.AUTO_ACKNOWLEDGE);
// 创建主题订阅者
TopicSubscriber subscriber =
session.createSubscriber(topic);
// 监听者
subscriber.setMessageListener(myListener);
// 指出我们将要接受信息的连接
conn.start();
}
}
除了采用上面的步骤取得JMS MessageConsumer(消息消费者)的连接之外,开发者还可创建并注册一个或多个使用Message Consumer的JMS Message Listener(消息监听者)接口。Message Listener总是在一个单独的控制线程中执行,这就意味着在编写消息监听者时,开发者不需要担心并发性问题的出现,见图2。
下面我给出了一个典型的JMS 消息监听者实现的代码。
代码段2:
/**
* 这个类是JMS MessageListener的一个实现
* 用来处理包含股票报价的消息
*/
class MyListener implements MessageListener {
/**
* 从收到的信息中取出股票报价
* 并且把它放入标准输出流中并显示。
*/
public void onMessage(Message message) {
// 从消息对象中取出报价
// 我们知道消息产生者发送TextMessages
try
{
String quote = ((TextMessage)message).getText();
System.out.println("股票报价: " + quote);
}
catch(JMSException e)
{
System.out.println(
"错误处理消息: "+message);
}
}
}
在这个实现中,MessageListener接收到的消息中包含了股票报价,消息监听者只是简单的从消息体中取得股票报价并把它输出到标准输出流中。
开发一个健壮的JMS客户端程序可能是非常困难的,程序员必须要考虑可能会同时接受多个消息,此外还有交易安全性、并发性消息处理、对象生命周期、容错性和可扩展性,这些都是开发者急切地想从EJB服务器中找到的功能。不过直到现在,程序员们还不得不自己动手把这些技术结合在一起应用。
为了整合EJB1.1和JMS,JMS监听者必须要使用我们在代码段2中描述的方法来建立。JMS客户端程序必须参考一个stateless(无状态)的用于响应处理JMS消息的session组件,然后,JMS消息要传递给EJB。然而,JMS消息并不要求被序列化,这就意味着这条消息在传递到远程的EJB实例之前必须被转换成为有序的消息类或在消息监听者中部分地解构。而且,应用程序开发者还有责任治理JMS服务器之间的事务联系,以及处理EJB、消息和并发性,这些都是非常复杂的事情。
即使一个应用程序开发者能把上面的这些都完成,并且也有能力访问JMS提供者并取得消息,但显然,他需要编写一大堆的代码,这对于我们这些常人往往是不大可能实现的。EJB2.0解决了这个问题,它通过扩展EJB组件类型,为需要异步消息支持的组件开发者提供简化的解决方案??新的MessageDriven组件类型。
MessageDrivenBean组件
MessageDrivenBean被部署成为总是扮演信息消费者角色的客户端。MessageDrivenBean没有客户端视图,这就意味着其本地和远程调用接口都是不可用的。一个消息产生者发信息给一个主题或队列并且没有熟悉到一个事实--MessageDrivenBean正扮演着消息消费者的身份。这就导致了在基于系统的 JMS之间的宽松联结,并更多的考虑到了在集合一个分布式计算环境时应有的灵活性。
MessageDriven 组件没有对话状态,其实所有的组件实例当它们在没有处理消息时都是等价的。这有点和无状态的session组件的状态特征有些类似。把组件实例集中起来是治理MessageDriven组件实例的普遍而又有效的办法。
MessageDriven 组件必须以直接或间接的方法从接口javax.ejb.MessageDrivenBean中取得,而这个接口类则是从javax.jms.MessageListener接口得来并添加了两个方法。onMessage()方法是从javax.jms.MessageListener接口中继续来的,这个方法有唯一的参数,就是javax.jms.Message,可以是任何有效的JMS消息类型。这个方法显然不包括throw(抛出)子句,所以在处理消息时不会抛出任何应用程序异常。
当这个容器接收到消息,它首先从一个可用实例池中取得一个MessageDriven组件(见图4)然后把部署描述器中制定的任务与执行线程联系起来,使其能够传播安全上下文。此外,假如部署描述器需要事务上下文的话,容器也会设置与之的关联。
一旦完成了治理任务,接收到的消息酒杯传送到MessageDrivenBean实例的onMessage()方法中,而一旦这个方法完成后,消息所载的事务就会被执行或返回,然后组件重新返回可用实例池中。
当MessageDrivenBean实例被从容器中(通常从实例池中)的任何强的参考中逐出,都会调用ejbRemove()方法。ejbRemove()方法将释放任何被组件实例占用的资源。setMessageDrivenContext()方法有一个参数--javax.ejb.MessageDrivenContext类的一个实例。MessageDrivenContext类与定义在EJB1.1中的entity和session类有点类似,当一个组件实例被创建,容器就把它传递进一个实例占用的上下文中,这个类有取得环境信息的方法也有相应的方法取得JTA UserTransaction类(用于治理事务定界的组件)。
此外,组件提供者还应当在EJB2.0服务器中可摄制的组件提供一个没有参数的ejb.Create()方法。这个组件实例可以获得任何在ejb.Create()用于进行处理的所需要的资源,比如说,在这一点上,MessageDrivenBean实例可以取得一个数据库连接,假如ejb.Remove()方法被调用的话,它将关闭或释放。
值得注重的是,MessageDrivenBean现在已经大大的简化了创建JMS消息消费者的过程,下面的代码段3就创建并配置了一个EJB容器所委托创建的JMS消息消费者。开发者现在可以很轻易的实现MessageDrivenBean接口,并可以把它配置在EJB服务器中且可以用来创建一个可收集消息的商业组件。
代码段3:
/**
*MessageDrivenBean接口由每一个消息驱动企业级组件类实现。
*这个容器使用MessageDrivenBean 方法来通知
*企业级Bean实例的实例生命周期事件
*/
public interface javax.ejb.MessageDrivenBean extends
javax.jms.MessageListener
{
/**
* 传送一个消息给监听者
*
* 参数 message :Message对象。
*/
public void onMessage(javax.jms.Message message);
/**
*容器在结束消息驱动对象的生命周期之前,调用这个方法。
*/
public void ejbRemove();
/**
*设置相关联的消息驱动上下文。
*容器在创建了实例后调用这个方法。
* 企业版 Bean 实例将保存context对象的参考到一个实例对象中
*/
public void
setMessageDrivenContext(
javax.ejb.MessageDrivenContext context);
}
在代码段4中给出了一个MessageDrivenBean实例的实现,在个组件从一个JMS TextMessage中取得一条字符串,并输出,它是根据代码段2种的JMS消息监听者程序改编的。
代码段: 4
/**
* 这个类是 MessageDrivenBean的一个实现。
*/
public class MyListenerMDB implements MessageDrivenBean
{
/**
* 这是一个无参数构造器,这样 EJB容器可以使用Class.newInstance()方法来创建组件实例
*/
public MyListenerMDB()
{
}
/**
*这个方法接受消息实例并执行消息处理过程。
*
* 参数:message 。Message对象
*/
public void onMessage(Message message)
{
// onMessage 实现仍然未变:
// 从message对象中取出股票报价。
// StockQuoteProdUCer 发送 TextMessages
// 并在适合的时候放出该对象。
try
{
String quote = ((TextMessage)message).getText();
System.out.println("股票报价: " + quote);
}
catch(JMSException e)
{
System.out.println(
"不能处理消息: " + message);
}
}
/**
* 当MessageDrivenBean实例被从容器中抛出,该方法就被调用。
*/
public void ejbRemove() throws javax.ejb.EJBException
{
System.out.println(
"StockListenerMDB: ejbRemove被调用。");
}
/**
* 设置MessageDrivenContext实例。本方法将在组件实例化时被调用
*消息驱动上下文答应组件开发者访问EJB容器的工具
*
* 参数ctx : 消息驱动上下文
*/
public void setMessageDrivenContext(
MessageDrivenContext ctx) throws javax.ejb.EJBException
{
System.out.println(
"StockListenerMDB: setMessageDrivenContext 被调用。");
}
/**
* ejbCreate with no args required by spec, though not
* enforced by interface
*/
public void ejbCreate()
{
System.out.println(
"StockListenerMDB: ejbCreate called.");
}
}
部署描述器
MessageDrivenBean可以使用XML部署描述器来指出受EJB服务器信息控制的运行时间动作,下面是部署描述器中定义MessageDrivenBean的有效的DTD元素。
<!ELEMENT message-driven (description?,
display-name?, small-icon?,
large-icon?, ejb-name?, ejb-class,
transaction-type, transaction-scope?,
jms-message-selector?, jms-acknowledge-
mode?, message-driven-destination?,
env-entry*, ejb-ref*, security-
identity?, resource-ref*, resource-
env-ref*)>
需要注重的是,部署描述器包含所有除用来部署MessageDrivenBean组件的目标名以外的所有信息,目标名被设置在一个应用程序服务器提供商指定的配置文件中或作为一个系统属性。
在部署描述器中,配置器可以指定组件是倾向于用于主题还是用于队列,并且,假如倾向于用于主题那么组件是否应该担当持久的签署者( durable subscriber)的身份。像队列一样,持久的主题保证监听者将接收到所有发布到这个主题的消息,即使监听者可能一段时间都不可用。 持久的主题对应用程序的可靠性很重要。
我们的给出的MessageDrivenBean的部署描述器(见代码段5)告诉容器这个组件是特意侦听一个不持久主题。这个组件有使用 NotSupported方法事务属性的容器治理事务限定。
代码段5:
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>MessageListenerMDB</ejb-name>
<ejb-class>messageListenerMDB</ejb-class>
<transaction-type>Container</transaction-type>
<transaction-scope>Local</transaction-scope>
<jms-acknowledge-mode>auto-acknowledge</jms-
acknowledge-mode>
<message-driven-destination>
<jms-destination-type>javax.jms.Topic</jms-
destination-type>
<jms-subscription-durability>nondurable</jms-
subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>MessageListenerMDB</ejb-name>
<method-name>onMessage</method-name>
<method-params>
<method-param>javax.jms.Message</method-param>
</method-params>
</method>
<trans-attribute>NotSupported</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
使用MessageDrivenBean组件的另一个好处就是它的部署的简洁性。 典型情况下,一个应用服务器供给商将提供定义组件部署描述器的工具,产生 EJB jar文件,并且部署组件。 一旦组件部署好了, EJB服务器将处理 EJB容器类的注册并且开始 JMS连接。 因为 JMS定义了一个标准,一个基于 JNDI的机制,用于获得 JMS主题,队列,连接以及一个能够使用任何JMS供给商实现的高端的应用程序服务器的引用。 这答应设计者在开发商业程序时最充分的利用应用程序服务器和JMS服务器。