Sunday, January 21, 2007

使用Spring进行JMS消息传递

我碰巧看到了一篇文章,是有关使用Spring框架来简化与IBM WebSphere MQ的交互的。这篇文章是对Spring中的JMS支持的相当不错的介绍,但是有一些重要的东西它却没有提到。

  Spring作为J2EE框架的地位,与最近BEA宣布在WebLogic中对Spring提供正式支持这则消息结合起来,就会使一些开发人员认为,文章中的代码可以不加修改地用于在WebLogic上运行的J2EE应用程序(很有可能,大部分人会选择使用WebLogic startup类而不是基于文件的JDNI来映射MQ ConnectionFactory和队列到WebLogic JNDI命名空间中,或通过支持Foreign JMS提供者来映射)。

  那么,把文章中的代码用于在WebLogic上运行的J2EE应用程序又会产生什么结果呢?如预料中的一样,它确实运行了——消息被发送和交付,没有出现异常,所以乍一看一切都很正常。直到您将其用于CMT或BMT事务,比如说下面来自一个会话bean的代码:
代码:

/**
* @ejb.bean
* type="Stateless"
* name="SpringTest"
* view-type="local"
* transaction-type="Container"
*
* @ejb.transaction
* type="RequiresNew"
*/
public class SpringTestBean implements SessionBean {
...
/** @ejb.interface-method */
public void sendMessage() throws Exception {
JmsSender jmsSender =
(JmsSender)springContext.getBean("jmsSender");
jmsSender.sendMesage("test");
// rollback CMT transaction
sessionContext.setRollbackOnly();
}
...
}


本来事务回滚之后,消息应该不在MQ中,但是实际情况是消息在MQ中。其原因非常简单——WebLogic需要使用特定的包装器来包装MQ ConnectionFactory对象,以确保在XA事务上下文中获取正确的资源。仅仅将对象放入WebLogic JNDI是不够的。开发人员应该通过EJB部署描述符中的resource-ref元素声明ConnectionFactory:
代码:


myQcf
javax.jms.QueueConnectionFactory
Container
Shareable

...

myQcf
mq.qcf

然后,在Spring上下文定义中,QueueConnectionFactory应该引用通过resource-ref映射的名字:






然后,在Spring上下文定义中,QueueConnectionFactory应该引用通过resource-ref映射的名字:
代码:



myQcf
javax.jms.QueueConnectionFactory
Container
Shareable

...

myQcf
mq.qcf

然后,在Spring上下文定义中,QueueConnectionFactory应该引用通过resource-ref映射的名字:






注意,该工厂是在java:comp/env命名空间中,而不是在JNDI全局作用域中进行查找。这将确保WebLogic所使用的将要参与全局事务的ConnectionFactory对象经过正确包装,然后上面的例子就会如预料那样运行了。

  虽然上面的例子正常运行了,但是还是有一些问题。因为现在所有使用Spring的JMS对象的操作都应该由某个正确定义了resource- ref的EJB 发起。这意味着,例如,开发人员要非常小心,不要把JMSSender作为依赖注入到某个可以不作为EJB调用序列(例如,调度程序之类)的一部分而执行的类或需要访问多个ConnectionFactory的类中。另一种方法是扩展Spring的 JndiObjectFactoryBean类,支持创建所要求的包装器。这种方法的问题是,(据我所知)该包装器API还没有说明文档。

  所以,最后结论是,最好不要假设Spring会“魔法”,然后就期待一切发生,还是要经常测试,确保它真的 管用。

评论

* 确实,没有魔法:即使在一个基于Spring的应用程序中,资源引用也需要得到正确定义,就像普通的J2EE应用程序与这些资源交互时一样。
resource-ref元素也可以在web.xml中声明,所以同样的功能也完全适用于普通的web应用程序(WAR部署单元),而不是只适用于 EJB。这是Spring应用程序直接与应用服务器的服务连接的最典型的场景:使用Spring驱动的事务,用 JtaTransactionManager作为后端,JndiObjectFactoryBean定义指向resource-ref JNDI位置(一个JDBC DataSource或一个JMS ConnectionFactory)。
发表人:juergen.hoeller,2005年10月20日,01:59 PM

* 是的,也可以在WAR中使用resource-ref,而且还要更好一些,因为可以以全局级别指定resource-ref,而对于EJB,如果我没有弄错的话,是针对每个bean。
如果定义了多个ConnectionFactory,一定要小心。因为常见的实践是在不同EJB的同一个“逻辑”名下定义它们,而且它们可以调用同一个共享的组件。
发表人:maximdim,2005年10月20日,02:33 PM


http://dev2dev.bea.com/blog/maximdim/archive/2005/10/jms_messaging_w.html

作者简介
Dmitri Maximovich是一位独立顾问,专长是软件设计、开发和技术培训。他具有超过12年的从业经验,而且很早就开始涉及J2EE。他的工作包括为金融业和医药业设计和开发任务关键的应用程序。

No comments:

Blog Archive