Enterprise JavaBeans Distilled
作者: Tnk Luo
第六次:
消息驱动Bean(续)
TopicConnectionFactory 和 Topic
为发送JMS消息,需要一个到JMS供应者的连接和用于消息的目的地地址。使用JMS连接工厂可以建立到JMS供应者的连接。同时,使用Topic对象来标识消息的目的地地址。这两者都可通过TravelAgent EJB的JNDI ENC服务获得:
TopicConnectionFactory factory = (TopicConnectionFactory)
jndiContext.lookup("java:comp/env/jms/TopicFactory");
Topic topic = (Topic)
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供应者的实际连接:
TopicConnection connect = factory.createTopicConnection();
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后释放它以节省资源,是一个良好的编程习惯。
TopicConnection connect = factory.createTopicConnection();
...
connect.close();
TopicPublisher
使用TopicSession创建TopicPublisher,从而可以从TravelAgent EJB中发送消息到Topic对象给定的目的地中。任何订阅该topic的JMS客户都将收到该消息的拷贝:
TopicPublisher publisher = session.createPublisher(topic);
TextMessage textMsg = session.createTextMessage();
textMsg.setText(ticketDescription);
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消息:
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
...
TopicPublisher publisher = session.createPublisher(topic);
MapMessage mapMsg = session.createTextMessage();
mapMsg.setInt("CustomerID", ticket.customerID.intValue());
mapMsg.setInt("CruiseID", ticket.cruiseID.intValue());
mapMsg.setInt("CabinID", ticket.cabinID.intValue());
mapMsg.setDouble("Price", ticket.price);
publisher.publish(mapMsg);
其中,接收该消息的JMS客户能够通过名字访问MapMessage的属性(CustomerID、CruiseID、CabinID、和Price)。
换过一种做法,在TravelAgent EJB中使用ObjectMessage类型的消息,从而可以发送完整的TicketDO对象。其中,该消息实现了Java序列化。
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
...
TopicPublisher publisher = session.createPublisher(topic);
ObjectMessage objectMsg = session.createObjectMessage();
ObjectMsg.setObject(ticket);
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。
待续。。。。。。。。
(作者其它文章:http://www.csdn.net/develop/author/netauthor/worldheart/ )
谢谢!!