JSPCN主页 | JSP空间 | 网站制作 | JSP下载 | JSP论坛 | JSP教程 | 关于JSPCN | 联系我们
JSP虚拟主机,jsp空间,java空间,java虚拟空间,详细请点击进入
做最专业的JSP中文网站 当前位置首页--JSP技术--JAVABEAN  
文章搜索:
关键字 标题
  
JSP中文网内容管理系统(JCMS)
JSP虚拟主机
网络笔记本
网摘,图片,笔记收藏
虚拟服务器

JSPCN文章目录分类
JSP配置[219]JSP基础[136]
中文问题[69]上传问题[27]
JAVABEAN[46]数据库[212]
文件操作[126]图片声音[17]
JSP其他[57]时间相关[16]
JAVAMAIL[72]STRUTS[144]
开发工具[28]教程系列[157]
JSP实例[89]
JAVA基础[421]APPLET[78]
JAVA网络[179]Applica[115]
Servlet[98]XML[163]
J2ME[257]J2EE[374]
考试相关[63]JAVA线程[90]
EJB[261]Swing[26]
Java API[141]声音图片[28]
异常处理[33]JAVA实例[290]
JAVA类[139]SUN[89]
Hibernate[6]JMX[8]
Spring[34]
本版推荐文章 
本版热点文章 
相关文章链接 
 
Enterprise JavaBeans Distilled(2)
作者:     文章来源:
访问次数:105次     加入时间:2006年11月01日
-------------------------------------------------------------

消息驱动Bean

-------------------------------------------------------------

这个系列好久才回来了,不好意思。让我们继续往下看。这一期,让我们看看消息驱动Bean相关的内容。

这接下来的几部分内容,我主要以翻译,《Enterprise JavaBeans》,3rd中的第13章为主。

其网址如下:
http://www.oreilly.com/catalog/entjbeans3/chapter/ch13.html
《Enterprise JavaBeans》,第三版

第13章 消息驱动Bean
译者: Tnk Luo

本章内容包括两部分:充当资源的JMS 和 消息驱动Bean。第一部分给出Java消息服务(JMS)介绍和充当资源的角色,即JMS对任何EJB可用(包括会话Bean、实体Bean和消息驱动Bean)。对JMS不熟悉的读者在深入第二部分内容之前,应该阅读第一份部分的内容。

    如果您已经对JMS很熟悉,则可以直接跳到第二部分内容,其中对新的EJB类型(消息驱动Bean)作了大概的介绍。消息驱动Bean是由消息分发者(message delivery)激活的异步Bean。在EJB 2.0规范中给出要求,即产品厂商需要支持基于JMS的消息驱动Bean,一种从某topic或queue中接收JMS消息,并处理分发给它们的这些消息的企业Bean。

默认情况下,所有的EJB 2.0产品厂商必须支持一个JMS供应者。大部分EJB 2.0产品厂商都内置了一个JMS供应者,但也可支持其他的JMS供应者。不管EJB 2.0产品厂商是如何提供JMS服务的,如果想支持消息驱动Bean,则它必须至少支持一个JMS供应者。这种对JMS的强制做法所带来的好处在于,EJB开发者能有一个可操作的JMS供应者,从而使得它可以发送和接收消息。

充当资源的JMS

JMS,一种独立于厂商的标准API,也是J2EE平台的部分内容,能用于访问企业消息系统。企业消息系统(又名,面向消息的中间件,(译者注:简称MOM))提供了在网络环境中软件应用之间交换消息的便利。JMS类似于JDBC:JDBC是用于访问多种不同RDBMS的API,而JMS则提供了独立于产品厂商、对企业消息系统的访问。现如今有很多企业消息产品支持JMS,其中包括IBM的MQSeries、BEA的WebLogic JMS服务、Sun公司的iPlanet Message Queue、Progress的SonicMQ等等。使用JMS API发送或接收消息的软件在不同JMS产品厂商间是可移植的。

使用JMS的Java应用称之为,JMS客户。同时,处理消息的路由、分发的消息系统称之为,JMS供应者。JMS应用是由多个JMS客户和一个JMS供应者(一般情况下)构成的业务系统。

发送和接收消息的JMS客户分别称之为生产者(producer)和消费者(consumer)。当然,单个的JMS客户可以同时是生产者和消费者。当使用术语消费者和生产者时,本书分别指接收和发送消息的JMS客户。

在EJB中,所有类型的企业Bean都能使用JMS发送消息到不同的目的地(destination)。其他的Java应用或消息驱动Bean可以消费这些消息。JMS使得在使用消息服务的EJB中发送消息更容易,其中这里的消息服务有时候称之为消息中介(message broker)或消息路由器(message router)。其中,消息中介已经存在很多年,最早、最为广泛使用的当然是IBM的MQSeries,但JMS还是比较新的东西,因为它被设计成在Java应用间分发不同消息的目的。

使用JMS重新实现TravelAgent EJB

在这里我们来修改第12章开发的EJB,TravelAgent,使得它可以用JMS来通知其他的Java应用,预定已经完成。下面的代码给出了如何修改bookPassage()方法,从而使得EJB,TravelAgent能够发送基于TicketDO对象中给出的描述信息的简单文本消息:

  1. public TicketDO bookPassage(CreditCardDO card, double price)
  2.     throws IncompleteConversationalState {
  3.     if (customer == null || cruise == null || cabin == null) {
  4.         throw new IncompleteConversationalState();
  5.     }
  6.     try {
  7.         ReservationHomeLocal resHome = (ReservationHomeLocal)
  8.             jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
  9.         ReservationLocal reservation =
  10.             resHome.create(customer, cruise, cabin, price, new Date());
  11.         Object ref = jndiContext.lookup
  12.             ("java:comp/env/ejb/ProcessPaymentHomeRemote");
  13.         ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
  14.             PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
  15.         ProcessPaymentRemote process = ppHome.create();
  16.         process.byCredit(customer, card, price);
  17.         TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
  18.         //新添加的内容
  19.         String ticketDescription = ticket.toString();
  20.         TopicConnectionFactory factory = (TopicConnectionFactory)
  21.             jndiContext.lookup("java:comp/env/jms/TopicFactory");
  22.         Topic topic = (Topic)
  23.             jndiContext.lookup("java:comp/env/jms/TicketTopic");
  24.         TopicConnection connect = factory.createTopicConnection();
  25.         TopicSession session = connect.createTopicSession(true,0);
  26.         TopicPublisher publisher = session.createPublisher(topic);
  27.         TextMessage textMsg = session.createTextMessage();
  28.         textMsg.setText(ticketDescription);
  29.         publisher.publish(textMsg);
  30.         connect.close();
  31.         return ticket;
  32.     } catch(Exception e) {
  33.         throw new EJBException(e);
  34.     }
  35. }


为了能够发送消息,添加了很多新代码。然而,尽管从表面上看很复杂,但JMS概念并不是这样的。

TopicConnectionFactory 和 Topic

为发送JMS消息,需要一个到JMS供应者的连接和用于消息的目的地地址。使用JMS连接工厂可以建立到JMS供应者的连接。同时,使用Topic对象来标识消息的目的地地址。这两者都可通过TravelAgent EJB的JNDI ENC服务获得:

  1. TopicConnectionFactory factory = (TopicConnectionFactory)
  2. jndiContext.lookup("java:comp/env/jms/TopicFactory");
  3. Topic topic = (Topic)
  4. jndiContext.lookup("java:comp/env/jms/TicketTopic");


其中,JMS中的TopicConnectionFactory在功能上类似于JDBC中的DataSource。正如DataSource提供对数据库的连接一样,TopicConnectionFactory提供对消息路由器的JMS连接。(这种类推并不完美。也可以说TopicSession类似于DataSource,因为这两者都是事务资源连接。)

Topic对象本身表示了独立于网络的目的地,即消息的地址。在JMS中,消息被发送到目的地,或者是topic,或者queue,而不是直接给其他的应用程序。Topic类似于邮件列表或新闻组。任何订阅了该topic消息服务的应用程序可以发送和接收来自该topic的消息。当JMS客户收到来自topic的消息时,可以说,这个客户订阅了该topic。JMS使得,应用借助于目的地来互发消息,从而降低了应用的耦合度。其中的,目的地起着虚拟通道(virtual channel)的作用。

当然,JMS也支持另外一种目的地类型,Queue。后面的内容将解释这两者的区别。

TopicConnection 和 TopicSession

在这里,使用TopicConnectionFactory来创建TopicConnection,一种对JMS供应者的实际连接:

  1. TopicConnection connect = factory.createTopicConnection();
  2. TopicSession session = connect.createTopicSession(true,0);


一旦获得TopicConnection,就可以使用它创建TopicSession。TopicSession对象使得Java开发者能够组合发送和接收消息动作。在这种情形下,只需要单个TopicSession。然而,提供多个TopicSession对象经常会带来很多好处:如果使用多线程生产和消费消息,需要创建不同的Session以满足每个线程对它的访问。这是因为JMS Session对象使用单线程模型,从而阻止了多个线程对单个Session的并发访问。创建TopicSession的线程通常是使用该Session的生产者和消费者(即,TopicPublisher和TopicSubsriber对象)。如果想使用多线程技术来生产和消费消息,则需要创建每个线程使用的Session对象。

其中,createTopicSession()有两个参数:
createTopicSession(boolean transacted, int acknowledgeMode)

在EJB 2.0规范中给出,运行时会对这些参数视而不见,因为EJB容器管理事务,并从JNDI ENC中得到任何JMS资源的确认模式。规范建议开发者分别使用true和0作为transacted和acknowledgeMode参数的值。但既然规范是这样规定的,如何给出这些参数值不会有任何关系。但不幸的是,并不是所有的产品厂商会遵循这部分内容。有些厂商会遵循规范,但有些不会。参考产品文档以寻求在容器管理和Bean管理事务中这些参数的正确取值。

在使用完TopicConnection后释放它以节省资源,是一个良好的编程习惯。

  1. TopicConnection connect = factory.createTopicConnection();
  2. ...
  3. connect.close();


TopicPublisher

使用TopicSession创建TopicPublisher,从而可以从TravelAgent EJB中发送消息到Topic对象给定的目的地中。任何订阅该topic的JMS客户都将收到该消息的拷贝:

  1. TopicPublisher publisher = session.createPublisher(topic);
  2. TextMessage textMsg = session.createTextMessage();
  3. textMsg.setText(ticketDescription);
  4. publisher.publish(textMsg);


消息类型

In JMS, a message is a Java object with two parts: a header and a message body. The header is composed of delivery information and metadata, while the message body carries the application data, which can take several forms: text, serializable objects, byte streams, etc. The JMS API defines several message types (TextMessage, MapMessage, ObjectMessage, and others) and provides methods for delivering messages to and receiving messages from other applications.

在JMS中,消息是由两部分构成的Java对象:消息头(header)和消息体。消息头由分发信息和原数据组成,而消息体是应用程序数据,现支持的消息体格式有:文本、序列化对象、字节流等等。JMS API定义了几种消息类型(TextMessage、MapMessage、ObjectMessage等)并提供了用于在应用间分发和接收消息的方法。

比如,将TravelAgent EJB作如下改变,使得它发送MapMessage而不是TextMessage消息:

  1. TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
  2. ...
  3. TopicPublisher publisher = session.createPublisher(topic);
  4. MapMessage mapMsg = session.createTextMessage();
  5. mapMsg.setInt("CustomerID", ticket.customerID.intValue());
  6. mapMsg.setInt("CruiseID", ticket.cruiseID.intValue());
  7. mapMsg.setInt("CabinID", ticket.cabinID.intValue());
  8. mapMsg.setDouble("Price", ticket.price);
  9. publisher.publish(mapMsg);


其中,接收该消息的JMS客户能够通过名字访问MapMessage的属性(CustomerID、CruiseID、CabinID、和Price)。

换过一种做法,在TravelAgent EJB中使用ObjectMessage类型的消息,从而可以发送完整的TicketDO对象。其中,该消息实现了Java序列化。

  1. TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
  2. ...
  3. TopicPublisher publisher = session.createPublisher(topic);
  4. ObjectMessage objectMsg = session.createObjectMessage();
  5. ObjectMsg.setObject(ticket);
  6. publisher.publish(objectMsg);


除了TextMessage、MapMessage和ObjectMessage,JMS还提供了两种其他的消息类型:StreamMessage和BytesMessage。StreamMessage把I/O流的内容作为消息有效载荷。同时,BytesMessage操作字节数组,即opaque data。

XML配置描述符

When a JMS resource is used, it must be declared in the bean's XML deployment descriptor, in a manner similar to the JDBC resource used by the Ship EJB in Chapter 10: 

使用JMS资源时,必须在EJB的XML配置描述符中声明。其操作方式类似于第10章Ship EJB中的JDBC资源的声明。

<enterprise-beans>
<session>
<ejb-name>TravelAgentBean</ejb-name>
...
<resource-ref>
<res-ref-name>jms/TopicFactory</res-ref-name>
<res-type>javax.jms.TopicConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-ref>
<res-ref-name>jdbc/titanDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-env-ref>
<resource-env-ref-name>jms/TicketTopic</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
</resource-env-ref>
...
</session>

其中,用于JMS TopicConnectionFactory的<resource-ref>,类似于JDBC DataSource中的<resource-ref>声明:声明JNDI ENC名字、接口类型和授权协议。除了<resource-ref>之外,TravelAgent EJB还必须声明<resource-env-ref>,这项元素列举出和< resource-ref >相关的任何“被管理对象”。在这里,声明Topic以用于发送ticket消息。在部署阶段,部署者将把<resource-ref>和<resource-ref>中声明的JMS TopicConnectionFactory和Topic映射到JMS工厂和topic。

JMS 应用客户程序

To get a better idea of how JMS is used, we can create a Java application whose sole purpose is receiving and processing reservation messages. We will develop a very simple JMS client that simply prints a description of each ticket as it receives the messages. We'll assume that the TravelAgent EJB is using the TextMessage to send a description of the ticket to the JMS clients. The following code shows how the JMS application client might look:

为更好的理解如何使用JMS,可以开发一个Java应用,其唯一的用途在于接收和处理预定消息。在这里,将开发一个非常简单的JMS客户。当该客户接收到消息时,会打印出所每张票的描述信息。假定TravelAgent EJB使用TextMessage以发送票的描述给JMS客户。下列代码显示了JMS应用客户可能的样子:

  1. import javax.jms.Message;
  2. import javax.jms.TextMessage;
  3. import javax.jms.TopicConnectionFactory;
  4. import javax.jms.TopicConnection;
  5. import javax.jms.TopicSession;
  6. import javax.jms.Topic;
  7. import javax.jms.Session;
  8. import javax.jms.TopicSubscriber;
  9. import javax.jms.JMSException;
  10. import javax.naming.InitialContext;
  11. public class JmsClient_1 implements javax.jms.MessageListener {
  12. public static void main(String [] args) throws Exception {
  13. if(args.length != 2)
  14. throw new Exception("Wrong number of arguments");
  15. new JmsClient_1(args[0], args[1]);
  16. while(true){Thread.sleep(10000);}
  17. }
  18. public JmsClient_1(String factoryName, String topicName) throws Exception {
  19. InitialContext jndiContext = getInitialContext();
  20. TopicConnectionFactory factory = (TopicConnectionFactory)
  21. jndiContext.lookup("TopicFactoryNameGoesHere");
  22. Topic topic = (Topic)jndiContext.lookup("TopicNameGoesHere");
  23. TopicConnection connect = factory.createTopicConnection();
  24. TopicSession session = 
  25. connect.createTopicSession(false,Session.AUTO_ACKNOWLEDGE); 
  26. TopicSubscriber subscriber = session.createSubscriber(topic);
  27. subscriber.setMessageListener(this);
  28. connect.start();
  29. }
  30. public void onMessage(Message message) {
  31. try {
  32. TextMessage textMsg = (TextMessage)message;
  33. String text = textMsg.getText();
  34. System.out.println("  RESERVATION RECIEVED: "+text);
  35. catch(JMSException jmsE) {
  36. jmsE.printStackTrace();
  37. }
  38. }
  39. public static InitialContext getInitialContext() {
  40. // 创建具体产品厂商的JNDI上下文
  41. }
  42. }


JmsClient_1的构建器含有来自JNDI InitialContext的TopicConnectionFactory和Topic。这些对象是使用具体厂商产品的properties创建的,从而使得客户能够连接到TravelAgent EJB使用的同一JMS供应者。比如,WebLogic应用服务器中getInitialContext()方法的代码如下:(JNDI也允许将properties放置在jndi.properties文件中,该文件包含有用于InitialContext的property值,并能够在运行时被动态找到。本书中,显式的给出了properties值。)

  1. public static InitialContext getInitialContext() {
  2. Properties env = new Properties();
  3. env.put(Context.SECURITY_PRINCIPAL, "guest");
  4. env.put(Context.SECURITY_CREDENTIALS, "guest");
  5. env.put(Context.INITIAL_CONTEXT_FACTORY,
  6. "weblogic.jndi.WLInitialContextFactory");
  7. env.put(Context.PROVIDER_URL, "t3://localhost:7001");
  8. return new InitialContext(env);
  9. }


一旦客户获得了TopicConnectionFactory和Topic,就可以采取和TravelAgent EJB相同的方式创建TopicConnection和TopicSession。它们的主要区别在于这里的TopicSession对象创建了TopicSubscriber对象,而不是TopicPublisher。其中,TopicSubscriber被明确设计成处理来自特定Topic的消息:

  1. TopicSession session = 
  2. connect.createTopicSession(false,Session.AUTO_ACKNOWLEDGE); 
  3. TopicSubscriber subscriber = session.createSubscriber(topic);
  4. subscriber.setMessageListener(this);
  5. connect.start();


TopicSubscriber能直接接收消息,或者能够将消息的处理委派给接口javax.jms.MessageLister。在例子中,JmsClient_1实现了MessageListener接口,使得它可以处理消息。MessageListener对象实现了单一的方法,onMessage(),每当新消息发送到订阅者的topic中时,该方法都会被调用。在这里,每次TravelAgent EJB发送预定消息到topic中时,JMS客户中的onMessage()方法都会被调用,使得它能够接收到消息的拷贝并处理它:

  1. public void onMessage(Message message) {
  2. try {
  3. TextMessage textMsg = (TextMessage)message;
  4. String text = textMsg.getText();
  5. System.out.println("  RESERVATION RECIEVED: "+text);
  6. catch(JMSException jmsE) {
  7. jmsE.printStackTrace();
  8. }
  9. }


JMS是异步的

JMS消息的一个主要优势在于它是异步的。换句话说,JMS客户能发送消息,而无需等待回复。我们来比较一下它和Java RMI同步发送消息的灵活性。RMI是一种用于组装事务组件的极好选择,但在使用上很受限。每次客户调用EJB的方法时,都会因等待方法执行结果而诸塞当前线程。这种“lock-step”处理使得客户依赖于EJB服务器的可用性,从而导致了客户和EJB的紧耦合。

在JMS中,客户异步发送消息到目的地(topic或queue),同时其他的JMS客户也能从该目的地地址接收消息。当JMS客户发送消息时,它不用等待回复。因为消息发送到路由器,路由器负责将消息分发给其他的JMS客户。发送消息的客户和接收消息的客户形成了松耦合,因为发送者不再依赖于接受者的可用性。

RMI的这种限制使得JMS成为了,用于和其他应用通讯,极为诱人的替代品。使用标准的JNDI ENC,EJB能获得到JMS供应者的JMS连接,并用连接分发异步消息给其他的应用。比如,TravelAgent会话Bean可以使用JMS来通知其他的应用程序,预定已经处理过了,如图13-1所示。




图 13-1. 使用JMS的TravelAgent EJB 

在这种情形下,接收来自TravelAgent EJB的JMS消息的应用可能是消息驱动Bean,或者是企业中的其他应用,或者是其他组织中的应用,这些组织因为被告知预定已经完成而受益。还可能是,一起分享信息的商业合作者,或者是添加客户到目录邮件列表的内部市场应用程序。

JMS使得EJB发送消息而没有出现诸塞。EJB不知道消息接收者,因为它将消息发送到虚拟通道(目的地),而不是直接给另外的应用。应用能够选择从哪个虚拟通道接收消息,并接受新预定的通知。

企业消息有趣的一方面是,这种技术本身的松耦合、异步特定意味着:发送者的事务和安全context并没有对消息的接收者形成影响。比如,当TravelAgent EJB发送票务消息时,JMS供应者可能要鉴定它,但它的安全context并没有给接收该消息的JMS客户形成影响。当JMS客户接收到来自TravelAgentEJB的消息时,它对消息发送的安全context并没有概念。其实事实就是这样,因为发送者和接收者在使用不同安全管理的操作环境中。

类似地,事务也从不在发送者和接收者之间有关系。首先,发送者对接收者没有概念。如果消息发送到存在一个,或成千上万个接收者的topic中,在这种模糊的环境中管理这种分布式事务不太现实。另外,接收消息的客户可能在消息发送后很长时间内都没有收到它,因为它们临时down机了,或者不能够接收到消息。JMS强有力的关键点在于它允许发送者和接收者是临时的松耦合。事务一般都是快速执行的,因为事物锁定资源,具有不可预期的长时间事务的可能性也太不现实。

A JMS client can, however, have a distributed transaction with the JMS provider so that it manages the send or receive operation in the context of a transaction. For example, if the TravelAgent EJB's transaction fails for any reason, the JMS provider will discard the ticket message sent by the TravelAgent EJB. Transactions and JMS are covered in more detail in Chapter 14.

然而,JMS客户能够和JMS供应者有分布式事务,从而能够在同一事物上下文中管理发送和接收操作。比如,如果TravelAgent EJB事务由于某种原因失败,JMS供应者将丢弃TravelAgent EJB发送的票务消息。本书第14章有事务和JMS的详细阐述。

JMS消息模型:发布-订阅 和 点对点

JMS提供了两种消息模型:发布-订阅和 点对点。JMS规范称之为,messaging domains。在JMS术语中,发布-订阅 和 点对点经常分别缩写为pub/sub和p2p(或PTP).本章这两种叫法都有。

简而言之,发布-订阅用于一对多广播式消息,然而点对点用于一对一分发消息(如图13-2所示)。




图 13-2. JMS messaging domains 

发布-订阅 

在发布-订阅消息中,生产者能通过称之为topic的虚拟通道将消息发送给多个消费者。消费者可以选择订阅的topic。任何发送到topic的消息会将分发到所有订阅了它的消费者。每个消费者会接收到每条消息的一份拷贝。发布-订阅模型大体上是一个基于推技术的模型,因为消息会自动广播到消费者,而不需消费者向它们发送请求或为新消息pull topic。

在发布-订阅模型中,发送消息的生产者不依赖于接收消息的消费者。比如,使用发布-订阅的JMS客户能够建立持久的订阅,其间允许消费者断开、以后连接,并收集消费者断开期间发布的消息。

本章的TravelAgent EJB使用发布-订阅编程模型,并使用Topic对象作为目的地。

点对点

点对点消息模型允许JMS客户借助于,称之为queue的虚拟通道来同步和异步,发送和接收消息。其中点对点消息模型,一般都是基于pull(或者poll)的模型,消息需要客户向queue发送请求,而不是自动推到客户端。(JMS规范没有明确给出必须如何实现发布-订阅和点对点模型。这两者都可以使用推技术或拉技术,但从概念上讲发布-订阅是推和点对点是拉。)

queue可能有多个接收者,但只有一个接收者会接收到每条消息。正如图13-2所示,JMS供应者会关注消费消息的JMS客户,确保每条消息仅仅被一个JMS客户消费掉。JMS规范没有给出在多个接收者之间分发消息的规则。

用于点对点的消息API类似于发布-订阅中的。下面给出的代码演示了,如何修改TravelAgent EJB使得它使用基于queue的点对点API,而不是前述例子中使用的基于topic的发布-订阅模型。

  1. public TicketDO bookPassage(CreditCardDO card, double price)
  2. throws IncompleteConversationalState {
  3. ...
  4. TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
  5. String ticketDescription = ticket.toString();
  6. QueueConnectionFactory factory = (QueueConnectionFactory)
  7. jndiContext.lookup("java:comp/env/jms/QueueFactory");
  8. Queue queue = (Queue)
  9. jndiContext.lookup("java:comp/env/jms/TicketQueue");
  10. QueueConnection connect = factory.createQueueConneciton();
  11. QueueSession session = connect.createQueueSession(true,0);
  12. QueueSender sender = session.createSender(queue);
  13. TextMessage textMsg = session.createTextMessage();
  14. textMsg.setText(ticketDescription);
  15. sender.send(textMsg);
  16. connect.close();
  17. return ticket;
  18. catch(Exception e) {
  19. throw new EJBException(e);
  20. }
  21. }


使用哪个消息模型?

这两种模型背后的基本原理在JMS规范中。JMS是用于访问现存消息系统,而提供公共API的方式出现的。在现如今的概念阶段,一些消息厂商提供了点对点模型支持,而另一些提供对发布-订阅模型支持。所以,JMS需要提供支持两种模型的API才能赢得业界的广泛支持。JMS 1.0.2规范部要求JMS供应者支持两种模型。然而,EJB 2.0厂商需要提供对两种消息模型的支持。

几乎任何事情都可用pub/sub模型完成,或用点对点完成。反之亦然。以此类推,类似于开发者的编程语言选择。理论上,能够用Pascal写的应用也可以用C完成。用C++能完成的任何事情,也能用Java做。在某些情形下,都有一定的选择,或者取决于对那种模型的熟悉程度罢了。

在大部分情况下,模型的选择取决于模型各自不同的优点。对于发布-订阅而言,任何数目的订阅者都能监听某个topic,并都能够收到相同消息的拷贝。比如,考虑发布者广播了股票报价这样一种情况。如果任何特定的订阅者当前没有连接,并错过了很好的报价,发布者不会去关注。相比之下,点对点Session很可能倾向于另一端特定应用的一对一对话。在这种情形下,每条消息都值要紧。

另外,消息所代表数据信息的范围和多样性也是一个因素。使用pub/sub时,消息是基于特定topic提供的过滤功能来分发到消费者的。即使当消息是用于和某个已知的应用建立一对一对话,使用多个topic的pub/sub来隔离不同类型的消息也是pub/sub的优势所在。每种消息类型能单独通过各自的消费者和onMessage()监听器来处理。

当仅仅想让特定接收者处理一次给定消息时,点对点更加方便。这也许是这两种模型最关键的区别:p2p保证仅仅有一个消费者处理每条消息。当消息需要依次单独处理时,这一点尤为重要。

实体和会话Bean不该接收消息

JmsClient_1用于消费TravelAgent EJB生产的消息。那么,其他的实体,或会话Bean也能接收哪些消息吗?答案是肯定的,但那可不是好办法。

实体和会话Bean会对来自EJB客户的Java RMI调用作出响应,但不能编写为,类似消息驱动Bean一样,对JMS消息的响应。这意味着,不可能写消息驱动的会话,或实体Bean。EJB的这种对JMS消息响应的局限性,使得消息驱动Bean为什么会在EJB 2.0中引入的原因。消息驱动Bean被设计成消费来自topic或queue的消息。消息驱动Bean补充了以前EJB中的不足,我们将在下一节给出如何编写它们的例子。

开发实体或会话Bean来消费来自业务方法的JMS消息时可能的,但是这个方法必须首先被EJB客户调用。比如,调用Hypothetical EJB中的业务方法,其中设置了JMS session并试着查看来自queue的消息:

  1. public class HypotheticalBean implements javax.ejb.SessionBean {
  2. InitialContext jndiContext;
  3. public String businessMethod() {
  4. try{
  5. QueueConnectionFactory factory = (QueueConnectionFactory)
  6. jndiContext.lookup("java:comp/env/jms/QueueFactory");
  7. Queue topic = (Queue)
  8. jndiContext.lookup("java:comp/env/jms/Queue");
  9. QueueConnection connect = factory.createQueueConneciton();
  10. QueueSession session = connect.createQueueSession(true,0);
  11. QueueReceiver receiver = session.createReceiver(queue);
  12. TextMessage textMsg = (TextMessage)receiver.receive();
  13. connect.close();
  14. return textMsg.getText();
  15. catch(Exception e) {
  16. throws new EJBException(e);
  17. }
  18. }
  19. ...
  20. }


消息消费者,QueueReceiver,用于取得queue中的消息。尽管程序设计正确,但这是很危险的操作,因为对QueueReceiver.receive()的调用将诸塞当前线程,直到消息可用为止。如果消息不会发送到接收者的queue中,该线程将一直被诸塞。换句话说,如果没有消息发送到这个queue中,QueueReceiver将一直等待下去。

当然也有一些择中的办法,使得receive()方法变得更安全。比如,receive(long timeout)给出了等待时间,一旦超过了这个时间,如果消息还没到达,QueueReceiver将停止诸塞当前线程,并放弃该消息。同时,也有receiveNoWait()方法,它会检查消息,如果没有等待,则返回null,因此避免了线程诸塞。

尽管这些receive()方法更安全,但仍然是危险的操作。不能够保证receive()方法预期运行、或者程序员的失误(比如,使用了错误的receive()方法),等等这些所带来的风险都是很大的。另外,消息驱动Bean提供了强有力、简单的企业Bean ,并专门为消费JMS消息而设计的。本书建议不要试着在实体,或会话Bean 中消费消息。

JMS更多内容

JMS(包括一般意义上的企业消息),代表了分布式计算中一种强有力的典范。用本人的观点理解,Java消息服务和EJB自身同等重要,在使用JMS开发之前一定要理解它。

尽管本章给出了JMS的总体概述,但只是给出足够的材料为下一节讨论消息驱动Bean打下基础。JMS具有很多的特性和细节,以至于本书不可能包含内容广泛的JMS。为理解JMS以及如何使用它,需要读者独立研究它。JMS的详细材料,《Java消息服务》,Richard Monson-Haefel和David Chappell(O’Reilly)。花时间学习JMS是很值得的。

--------全文完-----------

Copyright © 2002-2005 JSPCN.net. All rights reserved.
JSP中文网    备案序号:蜀ICP备05001583号
成都恒海科技发展有限公司    成都市一环路南二段6号新瑞楼三楼8号