EJB 2.0消息驱动Beans开发消息驱动Beans和JMS
这次我们要讨论的话题是EJB2.0消息驱动Beans。 我将结合JMS的知识来演示一个具体的实例。文章所有的代码都可以在支持消息Beans的EJB服务上运行(Ejb2.0容器);另外你还需要一个支持JMS的消息服务器来控制消息队列。本文假设你了解企业级JavaBeans的知识。在这篇文章中我们将创建一个邮件消息队列系统。这个系统包括以下几个部分:
JMS Servser:一个JMS服务器,它将保存我们的邮件系统的消息队列。
JMS Queue: JMS队列,它将保存来自客户端的JMS消息。在我们的例子中,这个消息是一个映射消息(MapMessage),他允许我们存储有关被发出的邮件的"键/值"对信息。
Email Message Client:一个Email消息客户端,它将创建一个JMS消息然后把消息发送到JMS队列中。这个消息中包含了将要发送的邮件信息。
Email Message Drive Bean:一个Email消息驱动Bean负责接收JMS映射消息并把它发送出去。
<IMG>http://www.matrix.org.cn/upload/article/a200311495538.gif<;/IMG>
JMS 的结构在我们讲述消息驱动Beans之前,让我们先来谈谈JMS(Java消息服务)。我们知道现在有很多消息系统,每一个都有他们自己的API,这些消息系统提供了事件交换和数据异步的服务。作为一个程序员,我可以给消息服务器发送一些信息然后继续工作,而不必等待来自系统的响应。JMS API描述了一个标准的方法来访问几乎所有的消息系统,就像JDBC允许我们使用相同的API访问Oralce,Sybase和SQL Server 一样。 如同可以调用异步服务那样,我们从中获得额外的好处是程序之间有一个宽松的耦合,发送请求的代码和响应请求的代码是分离的。不同的客户端把消息发送到同一个指定的目的地;然后,接收者(Receiver)从目的地分离出消息并显示出来。下面让我们快速的了解一些JMS API的基本概念:消息映射域
JMS消息消息映射域
消息系统有几种运行模式。JMS API 提供了不同的域,他对应不同的模式。一个JMS提供者(provider)一个或多个域。两个、多个公共域是点对点和发布/订约。这两个域有下面的概念:Destination:对象客户端指定消息发送和接受的目的地Producer:一个客户端发送一个消息到一个目的地Consumer:一个客户端从目的地接受消息点对点(PTP)一个点对点应用有下面几个特征:
一个PTP提供者是一个发送者一个PTP消费者是一个接收者一个PTP目标是一个队列一个消息只能被一个接收者消费
发布/订阅(pub/sub)一个pub/sub应用有以下特征:A pub/sub producer is publisherA pub/sub consumer is a subscriber
A pub/sub destination is a topic
A message may have multiple subscribers
一个pub/sub提供者是一个发布者一个pub/sub消费者是一个订阅者一个pub/sub目标是一个主题一个消息可以有多个订阅者。
比如,一个邮件通信系统可以使用发布/订阅模式。每一个对邮件通信感兴趣的人都可以成为一个订阅者,当一个新的消息被发布后,这个消息会发送给每一个订阅者。
例子程序介绍
我们的Email应用程序将使用PTP域模型。当一个eamil被放入队列中时我们只希望一个接收者能够收到,另外email可以被多次发送。
JMS 消息被放入队列的条目是JMS消息。这是一个普通的消息,他拥有消息头和消息体。JMS消息有如下几个类型:
类型描述
TextMessage文本消息. 你可以通过msg.setText("foo")和msg.getText方法来操作TextMessageObjectMessage
这种消息存储序列化对象。你可以通过msg.setObject(Object o)和msg.getObject()来操作ObjectMessage.MapMessage
这种消息保存键/值对信息。你可以通过msg.setString(key,value)和msg.getString(key)来操作MapMessage.他还有其他几种getter和setter方法对应基本java类型(比如,getBoolean,getInt,getObject,等等)BytesMessage这种消息是一个字节流。他可被用于封装现有的消息格式。StreamMessage这种消息允许你发送java的原语流。在我们的例子中将使用一个MapMessage,因为他提供了一种方法可以让我们把email的标题信息和内容信息放到消息中。EJB 2.0消息驱动Bean我们已经讲述了JMS的基本概念,现在我们来谈一谈EJB2.0规范中的新概念。
回顾一下前面讲的JMS部分。我们有一个发送者,他把消息放到队列中,然后一个接收者将读取这个消息并使用接收到的信息来发送email。这个接收者可以是一个运行的程序,他接收“Email队列”中的消息。为了实现这个动作我们需要一个接收者,这个接收者的结构允许一个消息流的并行处理,同时它还要处理事务,这将使我们能够处理商业逻辑。这就是消息Bean的由来。也就是说一个消息Bean是一个简单的JMS消费者。一个客户端不能直接访问消息Beans(像你访问状态Bean那样)你只能通过发送JMS消息到消息Bean所侦听的目的地。为了达到重用的目的,像其他的EJB一样,许多的支持信息都在EJB部署描述符中。这意味着我们不必关心我们从那里获得消息(不管是一个queue或者是topic),我们只需要写一个OnMessage(Message msg)方法来处理消息就可以了。我们已经讲述了JMS和MDB的知识;现在让我们开始我们的例子吧.Email 应用程序的开发步骤:我们将通过下面几步来完成email应用程序:在JMS服务器上安装一个“Email Queue”消息队列。创建一个email客户端,他负责向emial 队列发送java 消息。创建一个消息驱动Bean,他将处理这些消息,然后用这些信息用Email发送。为消息驱动Bean写部署描述符。打包代码。
将被创建的代码是:
代码
描述
com.customware.client.EmailClientemail客户端,他将把消息发送到队列中com.customware.ejb.EmailMDB消息驱动Bean将消耗来自客户端的JMS消息,并且使用EmailHelper来发送邮件。com.customware.util.EmailHelper一个助手类,他有一个静态方法sendmail(Map mail,这个方法将使用JavaMail发送邮件。
第一步:安装一个邮件消息队列
这一步将依赖于你的消息服务器(比如IBM MQSeries,SonicMQ,等等)。我们需要安装一个JMS 队列。我给他取名为EmailQueue,客户端和消息驱动Bean部署描述符要用到它。
第二步:创建一个Email客户端 (EmailClient.java)现在我们需要创建一个客户端(JMS sender)。这个客户端截获搜有关email的信息,然后把它发送出去。main()方法从命令行获得参数,创建一个Hashtable(用于存储Map),调用sendmail(Map m)方法。sendmail方法获得信息,根据获得的信息中创建一个MapMessage,再通过sender.send(message)把消息发送到EmailQueue队列中。主要的工作在构造函数之中,这些是JMS工作的全部内容。
下面是构造函数的内容:
1.通过getInitialContext()助手方法连接到JNDI服务。
2.为队列查找一个连接工厂[(QueueConnectionFactory) ctx.lookup(CONNECTION_FACTORY)]
3.为我们的JMS服务器创建一个队列连接[conFactory.createQueueConnection()]
4.创建一个JMS会话(session),这个会话用于生产和消费信息。[connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE)]5.查找队列,他将发送消息[(Queue) ctx.lookup(QUEUE_NAME)]6.最后创建一个发送者,这个发送者将使用会话(我们前面创建的)发送消息到我们查找到的队列中。
第三步:创建一个消息Bean (EmailMDB.java)在写会话 Bean或实体Bean时,你必须创建远程接口、主接口和Bean类(实体Bean还有一个可选的主键类)。而消息Bean只需要Bean类,因为有一个“客户端”他将作为Bean的接口。一个消息驱动Bean必须扩展两个接口:
接口描述
MessageListener
javax.jms.MessageListener这是JMS接口,他提供了一个onMessage(Message msg)方法。当一个消息放入队列中时,消息驱动Bean的onMessage方法将被调用,容器将传输实际的消息来被消费。MessageDrivenBean
javax.ejb.MessageDrivenBean这是EJB接口,他包含EJB生命周期的方法:ejbCreate(): 当EJB创建时容器会调用这个方法ejbRemove(): 当容器销毁EJB时调用setMessageDrivenContext(MessageDrivenContext ctx): 当对象被装载后ejbCreate()调用之前上下文环境被传输到EJB中. 上下文包含信息,容器保存这些信息并允许你查许、处理(getUserTransaction(), setRollbackOnly(), getRollbackOnly())
security (getCallerPrincipal(), isCallerInRole())
如果你看一下EmailMDB.java代码你会发现开头的几个方法实现了MessageDrivenBean接口。我们在这些方法中作的所有事情就是打印他们被调用的信息。setMessageDrivenContext()把上下文环境保存到实例变量中,以便我们今后能够找的到他。你要作的差不多就是这些了。最后要作的就是扩展MessageListener接口的onMessage(Message msg)方法。这就是我们消费消息并处理他们的过程。开始,我们通过抛出的消息创建一个MapMessage。然后我们从map消息中查找“键/”值对,把他们的值装入标准的Hashtable。注意,这些方法我们是通过MapMessage调用的:// 从Map Message获得键
Enumeration mapnames = mapmessage.getMapNames();
// 从MapMessage中获得值
String val = mapmessage.getString(key);
最后,调用 EmailHelper.sendmail(map)方法,把消息当作邮件发送出去。是不是很简单。这就是是消息驱动Bean重要的部分,我们没有写那些晦涩的JMS代码。实际上消息驱动Bean是如何知道从那里获得这些消息?这些我们是通过部署描述符来实现的。
第四部:为MDB(MessageDriveBeans)定义部署描述符(ejb-jar.xml)
在部署的时候,我们要告诉容器(这里使用的是weblogic)有关MDB的信息。使用标注的EJB部署描述符来安装MDBs.因此,我们需要创建一个名为META-INF的文件夹来放置这些部署文件。
META-INF/ejb-jar.xml
在ejb-jar文件中,我们描述MDB类的名称,bean类的地址类型,和安全信息等。下面是这个文件;注意类名称和JMS地址类型(
标记的内容根据
中描述的特定的创建者而改变。如果你使用的是BEA Weblogic6.0sp1你需要使用到
描述
)
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>EmailMDB</ejb-name>
<ejb-class>com.customware.ejb.EmailMDB</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<security-identity>
<run-as-specified-identity>
<role-name>system</role-name>
</run-as-specified-identity>
</security-identity>
</message-driven>
</enterprise-beans>
</ejb-jar>
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>EmailMDB</ejb-name>
<ejb-class>com.customware.ejb.EmailMDB</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<security-identity>
<run-as-specified-identity>
<role-name>system</role-name>
</run-as-specified-identity>
</security-identity>
</message-driven>
</enterprise-beans>
</ejb-jar>
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>EmailMDB</ejb-name>
<ejb-class>com.customware.ejb.EmailMDB</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<security-identity>
<run-as-specified-identity>
<role-name>system</role-name>
</run-as-specified-identity>
</security-identity>
</message-driven>
</enterprise-beans>
</ejb-jar>
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>EmailMDB</ejb-name>
<ejb-class>com.customware.ejb.EmailMDB</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<security-identity>
<run-as-specified-identity>
<role-name>system</role-name>
</run-as-specified-identity>
</security-identity>
</message-driven>
</enterprise-beans>
</ejb-jar>
我们在那里告诉容器队列的名称呢?这要放到厂商特定的文件中。比如,如果你部署在BEA WebLogic 6.0上你需要一个weblogic-ejb-jar.xml文件,这个文件应该是这样的:
META-INF/weblogic-ejb-jar.xml
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>EmailMDB</ejb-name>
<message-driven-descriptor>
<pool>
<max-beans-in-free-pool>200</max-beans-in-free-pool>
<initial-beans-in-free-pool>5</initial-beans-in-free-pool>
</pool>
<destination-jndi-name>EmailQueue</destination-jndi-name>
</message-driven-descriptor>
<jndi-name>jms/EmailMDB</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>EmailMDB</ejb-name>
<message-driven-descriptor>
<pool>
<max-beans-in-free-pool>200</max-beans-in-free-pool>
<initial-beans-in-free-pool>5</initial-beans-in-free-pool>
</pool>
<destination-jndi-name>EmailQueue</destination-jndi-name>
</message-driven-descriptor>
<jndi-name>jms/EmailMDB</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
<ejb-name>描述的名称对应你在ejb-jar.xml中定义的bean名称。然后我们可以定义pool(池)信息。在这个例子中我们将拥有最少5个最多200个MDBs实例。这将允许我们有200个并发的消息发送到队列中。
描述的内容告诉容器我们将使用EmailQueue查找目的地址。因为我们并没有把它写道代码中,所以如果JMS环境发生变化,我们只需要修改部署描述文件,然后重新部署就可以了。
<ejb-name>描述的名称对应你在ejb-jar.xml中定义的bean名称。然后我们可以定义pool(池)信息。在这个例子中我们将拥有最少5个最多200个MDBs实例。这将允许我们有200个并发的消息发送到队列中。
描述的内容告诉容器我们将使用EmailQueue查找目的地址。因为我们并没有把它写道代码中,所以如果JMS环境发生变化,我们只需要修改部署描述文件,然后重新部署就可以了。
第五步:打包代码:
现在我们完成了代码和部署描述文件,我们需要把它们打包然后部署到EJB服务器上。这些文件的目录结构大概的样子如下图所示:
例子文件 |
其中的client,ejb和util目录中应该使编译过的class文件;比如:/client/EmailClient.class
, ../ejb/EmailMDB.class
, 和 ../util/EmailHelper.class
.现在我们通过部署描述符来打包代码:../code% jar cvf emailmdb.jar com META-INF
现在我们有了一个Email MDB的jar文件,我们把它部署到EJB服务器上。为了测试,在部署完bean之后,运行client你应该看到EJB服务器发送了一个email.要是这一切运行正常你需要确保JavaMail API mail.jar包含在EJB服务器的CLASSPATH中。
结论
我们已经创建了一个消息驱动Bean,你可以看到在JMS中他作为一个消费者是多么的简单。消息驱动Bean对于EJB组件结构是一个很好的补充,他为开发人员提供了一个方法来创建消费者使它集合化,支持事务处理并且使用了容器的架构。
译者语:
如果你使用的是Weblogic 光有上面的代码是不够的,你好要设置一个destination.具体在weblogic 的console中的JMS中设置.如果有什么不明白的地方请与我联系:
mail:wafd2003@yahoo.com.cn
qq:282099538 (请注明你是来自Matrix :-))
原文:http://www.onjava.com/pub/a/onjava/2001/05/22/ejb_msg.html?page=1