技术开发 频道

J2EE 1.4 中的消息

  【IT168 技术文章】

        JMS 1.1

  兼容 J2EE 的应用服务器现在需要支持 1.1 版的 Java 消息服务(Java Message Server,JMS)规范,这也许是新版本 J2EE 规范中最明显的改变。JMS 1.1 完全向后兼容 J2EE 1.3 规范所要求的 JMS 1.1,所以应当不需要改变现有的应用程序。值得强调的是,除非需要向后兼容性,否则没有理由用老的队列和主题接口编写新的 JMS 应用程序。新的应用程序应当只使用新的统一接口,如清单 1 中的例子所示。

  清单 1. 展示统一 JMS 接口的例子

1 InitialContext context = new InitialContext();
2 ConnectionFactory factory =
3     (ConnectionFactory) context.lookup("java:comp/env/jms/cf");
4 Destination source =
5     (Destination) context.lookup("java:comp/env/jms/source");
6 Connection connection = factory.createConnection();
7 connection.start();
8 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
9 MessageConsumer consumer = session.createConsumer(source);
10 Message message = consumer.receive();
11 connection.close();
12

  正如 Bobby 在他的文章中所说的,统一接口不仅简化了消息编程模型,它们还使应用程序可以用一个事务 Session 接收来自队列的消息,并向主题发送消息(或者相反)。这意味着可以在同一个事务工作单元中进行发送和接收,而无需借助于 bean 或者容器管理的事务。

  J2EE 对 JMS 使用的限制

  像以前版本的 J2EE 规范一样,1.4 版对 JMS 的使用有一些限制。我将在本节分析这些限制。

  限制的接口

  尽管许多开发人员可能不知道,但是 J2EE 规范对于 J2EE 应用程序如何使用 JMS API 总是有一些限制。例如,以下接口用于 JMS 提供者与应用服务器之间的集成(它们是在 JMS 规范中描述的 Application Server Facilities 的一部分),因而不能被应用程序使用:

  javax.jms.ServerSession

  javax.jms.ServerSessionPool

  javax.jms.ConnectionConsumer

  All javax.jms.XA interfaces

  限制的方法

  对于那些“应用程序组件在阻止创建线程的容器中不能执行”的方法,J2EE 1.3 规范给出了让人困惑的声明。EJB 容器不允许应用程序创建线程,但是一个 Web 容器 可以 允许应用程序这样做。结果,在 J2EE 1.3 中,根据所使用的 Web 容器,可以调用或者不可以调用这些方法。幸运的是,J2EE 1.4 规范删除了这些令人困惑的说法,只是说明下列方法只能被运行在客户容器中的应用程序所使用——换句话说,它们不会在 Web 或者 EJB 容器中调用:

  javax.jms.ServerSession method setMessageListener()

  javax.jms.ServerSession method getMessageListener()

  javax.jms.Session method run()

  javax.jms.QueueConnection method createConnectionConsumer()

  javax.jms.TopicConnection method createConnectionConsumer()

  javax.jms.TopicConnection method createDurableConnectionConsumer()

  javax.jms.MessageConsumer method getMessageListener()

  javax.jms.MessageConsumer method setMessageListener()

  javax.jms.Connection method setExceptionListener()

  javax.jms.Connection method stop()

  javax.jms.Connection method setClientID()

  这些方法中的前六个也属于 Application Server Facilities,因此将它们排除在外是合理的,但是其他的方法呢?禁止 setMessageListener() 和 getMessageListener() 的决定会给大多数应用程序开发人员带来最大的问题。这些方法用于注册 MessageListener ,这样当消息到达目标时就会调用其 onMessage() 方法。在 Web 和 EJB 容器中不允许它的原因是因为 上下文。调用 EJB 和 servlet 方法时,容器会保留与线程相关联的上下文,如当前事务和安全 principal。JMS 提供者调用 MessageListener 时,应用服务器没有办法拦截这个调用并添加适当的上下文。应当用同步方法 receive() 轮询消息或者考虑使用消息驱 bean 替代这些方法。

  尽管上面列出的最后三个方法在以前版本的 J2EE 应用程序也是禁止的,但是您仍然想知道为什么不允许它们。这要追溯到应用服务器管理连接的方式。它可能希望在不同的应用程序之间共享同一个连接,如果应用程序可以调用改变连接的状态的方法,那么它就不能这样做了。只有在使用 MessageListener 时 setExceptionListener() 和 stop() 方法才是有用的,因此,不能调用它们不是个问题,而应当能够在管理式地定义连接工厂时设置一个客户标识符。

  每个 Connection 有一个 Session

  还有一个新限制可能使现有的应用程序无法使用。J2EE 规范现在规定一个应用程序中,对每一个 Connection 只能有一个活动的(未关闭) Session 。换句话说,调用了 createSession() 后,如果在关闭原来的 Session 之前再次调用它,就会抛出一个异常。

  规范没有解释为什么会增加这个限制,不过我将给出我的理论。最新版本的 Java Connector Architecture (JCA) 规范建议 JMS 提供者可以实现为 JCA 资源适配器。在 JCA 编程模型中,就像 JDBC 一样,只有两个对象( ConnectionFactory 和 Connection ),而在 JMS 中有三个( ConnectionFactory 、 Connection 和 Session )。更重要的是,在 JCA 中,与事务相关联的是 Connection ,而在 JMS 中是 Session 。因而,如果在 JMS 中允许每个 Connection 有多于一个 Session ,那么 Connection 实际上就会与多个事务相关联。总之,这种限制使得 JMS 提供者更容易实现为 JCA 资源适配器。

  那么所有这些对应用程序开发人员有什么意义呢?在过去,您也许曾经试图利用 JMS Connection 对象的多线程本性,并将它们缓冲到 EJB 组件或者 servlet 的静态变量中。与之相反,现在应当只是将 Connection 做为 EJB 组件的一个实例变量缓冲,并保证在任何时刻只有一个会话,或者只在每次需要 Session 时创建一个 Connection 。大多数应用服务器会对对象实现某种缓冲池,所以它没有您所担心的那么昂贵(只是要记住在完成 Connection 时关闭它们)。

  

        消息目标

  使用 JMS 的 J2EE 应用程序通常分为两个阵营:

  使用 JMS 与后端系统通信的应用程序。

  使用 JMS 提供与应用程序的不同部分异步通信的应用程序。

  在这一节中,我将描述在 J2EE 规范中的一个改变,它使第二种类型的应用程序更容易部署。作为例子,我将展示两个 EJB 组件,它们通过彼此发送消息进行通信。在 J2EE 1.3 中,每个 bean 都要在其部署描述符中定义一个 resource-env-ref ,它在以后要在其本地命名空间中查询它。在清单 2 所示的例子中, SenderEJB 将从 java:/comp/env/jms/target 中查询其目标,而 ReceiverEJB 将从 java:/comp/env/jms/source 中查询其目标。因为引用在不同的本地命名空间中,所以没有一种机制让应用程序汇编器(application assembler )向部署人员表明这些 resource-env-ref s 实际上应当绑定到同一个目标。

  清单 2. 显示 resource-env-ref 的部署描述符代码片段

1 <ejb-jar>
2   <enterprise-beans>
3     <session>
4       <ejb-name>SenderEJB</display-name>
5       ...
6       <resource-env-ref>
7         <resource-env-ref-name>jms/target</resource-env-ref-name>
8         <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
9       </resource-env-ref>
10       ...
11     </session>
12     <session>
13       <ejb-name>ReceiverEJB</display-name>
14       ...
15       <resource-env-ref>
16         <resource-env-ref-name>jms/source</resource-env-ref-name>
17         <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
18       </resource-env-ref>
19       ...
20     </session>
21   </enterprise-beans>
22 </ejb-jar>
23

  在 J2EE 1.4 中,仍然可以使用 resource-env-ref 定义目标,不过,还有一个与 resource-env-ref 非常类似的新元素 message-destination-ref ,它还有两个子元素。其中第一个是 message-destination-usage ,正如它的名字所表明的,它用于向部署人员表明应用程序准备如何使用目标。它可以取以下的值中的一个:

  Produces

  Consumes

  ProducesConsumes

  因而部署人员通常会将 Produce 的引用和另一个 Consume 的引用绑定到同一个目标上。

  增加的第二个元素是 message-destination-link 。它可用于将两个或者更多 message-destination-ref 联系到一起。在 link 中包含的名字必须与在另一个新元素—— message-destination ——中给定的名字相匹配。部署应用程序后,部署人员将一个 message-destination 绑定到一个 JMS 目标,所有 message-destination-ref s 都使用这同一个绑定。

  因此在 J2EE 1.4 中,清单 2 中的例子现在可以改写为如清单 3 所示的形式。

  清单 3. 带有 message-destination-ref 和 message-destination 元素的部署描述符代码片段

1 <ejb-jar>
2   <enterprise-beans>
3     <session>
4       <ejb-name>SenderEJB</display-name>
5       ...
6       <message-destination-ref>
7         <message-destination-ref-name>jms/target</message-destination-ref-name>
8         <message-destination-type>javax.jms.Queue</message-destination-type>
9         <message-destination-usage>Produces</message-destination-usage>
10         <message-destination-link>destination</message-destination-link>
11       </message-destination-ref>
12       ...
13     </session>
14     <session>
15       <ejb-name>ReceiverEJB</display-name>
16       ...
17       <message-destination-ref>
18         <message-destination-ref-name>jms/source</message-destination-ref-name>
19         <message-destination-type>javax.jms.Queue</message-destination-type>
20         <message-destination-usage>Consumes</message-destination-usage>
21         <message-destination-link>destination</message-destination-link>
22       </message-destination-ref>
23       ...
24     </session>
25   </enterprise-beans>
26   <assembly-descriptor>
27     ...
28     <message-destination>
29       <message-destination-name>destination</message-destination-name>
30     </message-destination>
31     ...
32   </assembly-descriptor>
33 </ejb-jar>
34

  为了强调这两种新元素所造成的不同,请看以下两图,在每个图中,EAR 文件用蓝颜色遮蔽,表示应用程序汇编器的影响范围。应用程序部署人员添加红色箭头以将在应用程序中定义的资源绑定到实际的 JMS 队列。在图 1 中,可以看到应用程序汇编器有两个 resource-env-ref ,每个 EJB 组件有一个,并必须保证部署人员知道它们应当绑定到同一个 JMS 目标。

  图 1. 使用 resource-env-ref 的示例应用程序

  与此相反,在图 2 中,应用程序汇编器可以通过将两个 message-destination-ref 链接到同一个 message-destination ,而清楚地表明他或者她的意图。之后部署人员只需要添加一个绑定。

  图 2. 使用 message-destination 的示例应用程序

  与 ejb-link 使用的语法相同,还可以链接到在同一应用程序中不同 JAR 文件中定义的 message-destination 。例如, ../other/other.jar#destination 会用相对路径 ../other/other.jar 上的名字 destination 链接到 message-destination 。还可以设置 message-destination-link 表明消息驱动 bean 应当在其中使用消息的目标,我将在下一节中更详细地讨论这些内容。

  消息驱动 bean

  本文介绍的 J2EE 消息系统的最后一个改变是消息驱动 bean (MDB) 的改变。在 EJB 2.0 规范中,MDB 必须实现接口 javax.jms.MessageListener 。在 EJB 2.1 规范中,已经不再严格要求了,因此 MDB 可以实现 任何接口,只要有人准备好向它传送消息!对于 JMS 提供者,一般仍然实现 javax.jms.MessageListener ,的确不需要改变 MDB 代码就可以接纳新的规范。不过为了支持其他接口,部署描述符有了很大改变。

  清单 4 展示了 EJB 2.0 应用程序的 MDB 部署描述符的一个典型例子。清单 5 展示了同一 MDB 用在 EJB 2.1 应用程序中时,部署描述符会是什么样子。

  清单 4. EJB 2.0 部署描述符

1 <ejb-jar>
2   <enterprise-beans>
3     <message-driven>
4       <ejb-name>DurablePubSubMDB</ejb-name>
5       <ejb-class>example.ExampleMDB</ejb-class>
6       <transaction-type>Bean</transaction-type>
7       <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
8       <message-driven-destination>
9         <destination-type>javax.jms.Topic</destination-type>
10         <subscription-durability>Durable</subscription-durability>
11       </message-driven-destination>
12       <message-selector>
13         JMSType = 'person' AND gender = 'male'
14       </message-selector>
15     </message-driven>
16   </enterprise-beans>
17 </ejb-jar>
18

  清单 5. EJB 2.1 部署描述符

1 <ejb-jar>
2   <enterprise-beans>
3     <message-driven>
4       <ejb-name>DurablePubSubMDB</ejb-name>
5       <ejb-class>example.ExampleMDB</ejb-class>
6       <messaging-type>javax.jms.MessageListener</messaging-type>
7       <transaction-type>Bean</transaction-type>
8       <activation-config>
9         <activation-config-property>
10           <activation-config-property-name>
11             acknowledgeMode
12           </activation-config-property-name>
13           <activation-config-property-value>
14             Auto-acknowledge
15           </activation-config-property-value>
16         </activation-config-property>
17         <activation-config-property>
18           <activation-config-property-name>
19             destinationType
20           </activation-config-property-name>
21           <activation-config-property-value>
22             javax.jms.Topic
23           </activation-config-property-value>
24         </activation-config-property>
25         <activation-config-property>
26           <activation-config-property-name>
27             subscriptionDurability
28           </activation-config-property-name>
29           <activation-config-property-value>
30             Durable
31           </activation-config-property-value>
32         </activation-config-property>
33         <activation-config-property>
34           <activation-config-property-name>
35             messageSelector
36           </activation-config-property-name>
37           <activation-config-property-value>
38             JMSType = 'person' AND gender = 'male'
39           </activation-config-property-value>
40         </activation-config-property>
41       </activation-config>
42     </message-driven>
43   </enterprise-beans>
44 </ejb-jar>
45

  首先要注意的是新的 messaging-type 元素,它给出了 MDB 实现的接口类的名字。其次是删除了 acknowledge-mode 、 destination-type 、 subscription-durability 和 message-selector 。取而代之的是一组通用的名-值对。对于 JMS 提供者,JCA 规范建议支持表 1 中给出的名字和值。

  表 1. 建议的 JMS activation-config-property 值

activation-config-property-name activation-config-property-value
destinationTypejavax.jms.Queue or javax.jms.Topic
acknowledgeModeAuto-acknowledge (default) or Dups-ok-acknowledge
subscriptionDurabilityNonDurable (default) or Durable
messageSelectorString selector

  不幸的是,这些属性只是建议性的,不是每一个 JMS 提供者都一定支持它们。如何知道所使用的提供者支持哪些属性呢?在 JCA 规范的“Message Inflow”一章中,规定了 JMS 提供者与向 MDB 发送消息的应用服务器之间的协议。作为这个协议的一部分,JMS 提供者实现一个称为 ActivationSpec 的 JavaBean 组件,它的属性包含那些提供者发送消息所需要的信息。管理员可以为这些属性定义默认值,但是部署包含 MDB 的应用程序时,在 MDB 的部署描述符中定义的任何 activation-config-property 元素都会覆盖它们。因而遵守该建议的 JMS 提供者在 ActivationSpec 上有名为 destinationType 、 acknowledgeMode 、 subscriptionDurability 和 messageSelector 的属性。

  注意在这个新模型中,可以不让属性出现在部署描述符中,而管理式地定义它。另一方面,可能有另外一些特定于提供者的属性,以前必须管理式地定义,现在则可加入到 MDB 的部署描述符中(尽管这显然降低了应用程序的可移植性)。

  建议 JMS ActivationSpec 具备的另一个属性是 destination 。不幸的是,JCA 规范没有定义这个属性的类型或者它的意义。例如,它可以是要求部署人员为目标指定某个特定于提供者的名字的字符串。一种特殊的情况是属性的类型是 javax.jms.Destination 。在这种情况下,应用程序汇编器可以在 MDB 的部署描述符中指定一个 message-destination-link ,以表明 MDB 应当从中收取消息的 message-destination 。这个链接不是 message-destination-ref 的一部分,它与 message-destination-type 都定义为 message-driven 元素的直接子元素,如清单 6 的示例部署部署描述符所示。在运行时,应用服务器可以将链接解析为一个 JMS 目标对象、并将它设置到 ActivationSpec 的 destination 属性中。

  清单 6. 显示一个 MDB message-destination-link 的 EJB 2.1 部署描述符

1 <ejb-jar>
2   <enterprise-beans>
3     <message-driven>
4       <ejb-name>DurablePubSubMDB</ejb-name>
5       <ejb-class>example.ExampleMDB</ejb-class>
6       <messaging-type>javax.jms.MessageListener</messaging-type>
7       ...
8       <message-destination-type>
9         javax.jms.Queue
10       </message-destination-type>
11       <message-destination-link>
12         destination
13       </message-destination-link>
14       ...
15     </message-driven>
16     ...
17   </enterprise-beans>
18   ...
19 </ejb-jar>
20

  结束语

  在 J2EE 1.4 规范中,JMS 应用程序有许多改变和新功能。有一些改变,比如在 Web 容器上使用 setMessageListener() 的更严格限制,限制每个 Connection 有一个 Session ,以及有很大变化的 MDB 部署描述符,它们将要求修改现有的应用程序。其他改变,如统一的 JMS 1.1 API,则会简化新应用程序的编程并扩展现有的功能。不论是否正在移植 J2EE 1.3 应用程序或者有机会编写新的应用程序,我希望随着越来越多的 J2EE 1.4 应用服务器出现,您会发现本文会很有用。

0
相关文章