设计企业应用程序以使用 JMS
设计企业应用程序以直接将 JMS API 用于异步消息传递时,需要考虑很多事项。
过程
- 对于消息传递操作,您写的应用程序应该仅引用 Sun 的 javax.jms 包中定义的接口。 JMS 定义了映射至底层传输的通用消息传递视图。使用 JMS 的企业应用程序使用 Sun 的 javax.jms 包中所定义的下列接口:
- 连接
- 提供对底层传输的访问,并用于创建会话。
- Session
- 提供用于生产和使用消息的上下文,包括用于创建 MessageProducer 和 MessageConsumer 的方法。
- MessageProducer
- 用于发送消息。
- MessageConsumer
- 用于接收消息。
通用 JMS 接口将划分为点到点以及发布/预订行为的下列更具体版本的子类。表 1. JMS 公共接口的点到点以及发布/预订版本. 此表的第一列列示 JMS 公共接口,第二列列示相应的点到点接口,第三列列示相应的发布/预订接口。 JMS 公共接口 点到点接口 发布/预订接口 ConnectionFactory QueueConnectionFactory TopicConnectionFactory 连接 QueueConnection TopicConnection 目标 队列 主题 Session QueueSession, TopicSession, MessageProducer QueueSender TopicPublisher MessageConsumer QueueReceiver, QueueBrowser
TopicSubscriber 有关如何使用这些 JMS 接口的更多信息,请参阅 IBM MQ 信息中心的 Java™ 消息服务文档和“使用 Java”一节。
“Java 消息服务 (JMS) 要求”部分(在 J2EE 规范中)给出了不能在 web 和 EJB 容器中调用的方法列表:javax.jms.Session method setMessageListener javax.jms.Session 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 setExceptionListener javax.jms.Connection stop javax.jms.Connection setClientID
通过抛出 javax.jms.IllegalStateException 异常,在 WebSphere® Application Server 中强制执行此方法限制。
- 应用程序会引用作为受管对象对 WebSphere Application Server 预定义的 JMS 资源。
WebSphere 管理支持机构对 WebSphere Application Server 定义企业应用程序所使用 JMS 资源的详细信息,并将该信息绑定至 JNDI 名称空间。企业应用程序可从 JNDI 名称空间检索这些对象,并使用它们,无需了解关于其实现的任何内容。这能够更改由 JMS 资源定义的底层消息传递体系结构,而无需更改企业应用程序。
表 2. 用于点到点以及发布/预订消息传递的 JMS 资源. 此表的第一列中列示用于点到点消息传递的 JMS 资源,第二列中列示用于发布/预订的 JMS 资源。 点到点 发布/预订 ConnectionFactory(或 QueueConnectionFactory)队列
ConnectionFactory(或 TopicConnectionFactory)主题
连接工厂用来创建从 JMS 提供程序到消息传递系统的连接,并且,它封装了创建连接时所需的配置参数。
- 要改善性能,应用程序服务器将连接和会话与 JMS 提供程序合用。 必须为应用程序正确地配置连接和会话池属性,否则也许不能获取需要的连接和会话行为。
- 应用程序不能高速缓存 JMS 连接、会话、生产者或使用者。 WebSphere Application Server 会在 Bean 或 Servlet 完成时关闭这些对象,因此任何尝试使用已高速缓存对象都将失败,且异常为 javax.jms.IllegalStateException。
要改善性能,应用程序可以高速缓存从 JNDI 找到的 JMS 对象。例如,EJB 或 Servlet 仅需要查询一次 JMS 连接工厂,但它必须在每次实例化时调用 createConnection 方法。因为将连接和会话与 JMS 提供程序合作的效果,所以应该不会影响性能。
- 非持久订户只能在创建该订户时存在的同一事务上下文(例如,全局事务或未指定的事务上下文)中使用。
- 将持久预订用于缺省消息传递提供程序。 JMS 主题上的持久预订使订户可以接收发布到该主题的所有消息的副本,即使订户在未连接到服务器的一段时间之后也如此。因此,与服务器断开连接很长一段时间时仍可以执行订户应用程序,然后可再重新连接到服务器并处理在缺少订户应用程序期间已发布的消息。如果应用程序创建了持久预订,那么它被添加到管理员可以显示并通过管理控制台操作的运行时列表。每个持久预订具有唯一标识 clientID##subName,其中:
- clientID
- 客户机标识,用于将连接及其对象与为应用程序(作为 JMS 提供程序的客户机)维护的消息相关联。应该使用命名约定来帮助您标识应用程序,以免必须将持久预订与运行时管理关联的应用程序相联系。
- subName
- 预订名称,用于唯一识别给定客户机标识中的持久预订。
对于由消息驱动的 bean 创建的持久预订,这些值是在 JMS 激活规范上设置的。对于其他持久预订,客户机标识是在 JMS 连接工厂上设置的,而预订名称是由应用程序在 createDurableSubscriber 操作上设置的。
要创建对主题的持久预订,应用程序会使用 JMS API 中定义的 createDurableSubscriber 操作:public TopicSubscriber createDurableSubscriber(Topic topic, java.lang.String subName, java.lang.String messageSelector, boolean noLocal) throws JMSException
- topic
- 要预订的 JMS 主题的名称。它是支持 javax.jms.Topic 接口的对象名,例如,通过查找合适的 JNDI 条目找到的对象名。
- subName
- 用于识别此预订的名称。
- messageSelector
- 仅具有匹配消息选择器表达式的属性的消息会交付给使用者。值 NULL 或空字符串表明应该交付所有消息。
- noLocal
- 如果将此参数设置为 true,那么它将阻止消息的交付与持久订户发布在同一连接上。
应用程序可以使用两自变量格式的 createDurableSubscriber 操作,此时,它只接收 topic 和 subName 参数。此备用调用会直接调用前面显示的四种自变量版本,但将 messageSelector 设置为 NULL(以便交付所有消息)并将 noLocal 设置为 false(以便交付在连接上发布的消息)。例如,要对名为 myTopic 的主题创建持久预订,并且预订名称为 mySubscription:session.createDurableSubscriber(myTopic,"mySubscription");
如果 createDurableSubscription 操作失败,它会抛出提供消息和链接异常的 JMS 异常,以提供有关问题原因的更多详细信息。
要删除持久预订,应用程序会使用 JMS API 中定义的取消预订操作。
在常规操作中,每次最多只有一个活动的(已连接)订户可以持久预订。然而,订户应用程序可以在克隆应用程序服务器中运行,以进行故障转移并实现负载均衡。这样的话,会取消“一个活动的订户”限制,以提供可以具有多个同时使用者的共享持久预订。
有关应用程序对持久预订的使用方式的更多信息,请参阅 JMS 规范中的“使用持久预订”部分。
- 确定需要哪些消息选择器。 您可使用 JMS 消息选择器机制选择队列上消息的子集,以便接收调用返回此子集。 选择器可引用 JMS 消息头中的字段和消息属性中的字段。
- 在接收消息上操作。 接收消息时,您可按应用程序业务逻辑的需要进行操作。一些一般 JMS 操作检查消息的类型是否正确,并抽取消息的内容。要从消息主体抽取内容,请从通用 Message 类(这是接收方法的已声明返回类型)强制转换为更具体的子类(如 TextMessage)。好的实践是总在数据类型转换前测试消息类,以便适度处理意外错误。
在此示例中,instanceof 运算符用于检查接收的消息类型为 TextMessage。然后,通过将消息内容数据类型转换为 TextMessage 子类,以进行抽取。
if ( inMessage instanceof TextMessage ) ... String replyString = ((TextMessage) inMessage).getText();
- 使用缺省消息传递提供程序的 JMS 应用程序可以无任何限制地访问已从 WebSphere Application Server V5 嵌入式消息传递或 IBM MQ 接收到的消息内容。
- JMS 应用程序可以访问 JMS_IBM* 属性的完整设置。 这些属性是特定 JMS 应用程序的值,这些 JMS 应用程序使用缺省消息传递提供程序、V5 缺省消息传递提供程序或 IBM MQ 提供程序提供的资源。
对于 IBM MQ 处理的消息,JMS_IBM* 属性会映射至等价的 IBM MQ 消息描述符 (MQMD) 字段。有关 JMS_IBM* 属性和 MQMD 字段的更多信息,请参阅 IBM MQ 信息中心的“使用 Java”部分。
- JMS 应用程序可以使用报告消息作为受管的请求/响应处理的格式,以向生产者提供有关发送操作的结果以及消息的结局的远程反馈。 JMS 应用程序可以请求使用 JMS_IBM_Report_Xxxx 消息属性的整个范围的报告选项。有关使用 JMS 报告消息的更多信息,请参阅JMS 报告消息。
- JMS 应用程序可以使用 JMS_IBM_Report_Discard_Msg 属性来控制当无法将请求消息交付到目标队列时处理它的方法。
- MQRO_Dead_Letter_Queue
- 这是缺省值。应该将请求消息编写到死信队列。
- MQRO_Discard
- 应该废弃请求消息。它通常与 MQRO_Exception_With_Full_Data 联合使用,从而将未交付的请求消息返回到它的发送方。
- 使用侦听器异步接收消息。 在客户机(而不是 Servlet 或企业 Bean)中,调用 QueueReceiver.receive() 的替代办法是注册一种方法,当有合适消息可用时自动调用此方法;例如:
... MyClass listener =new MyClass(); queueReceiver.setMessageListener(listener); //application continues with other application-specific behavior. ...
当消息可用时,由侦听器对象上的 onMessage() 方法进行检索。import javax.jms.*; public class MyClass implements MessageListener { public void onMessage(Message message) { System.out.println("message is "+message); //application specific processing here ... } }
对于异步消息传递,应用程序代码不能捕获由接收消息故障引发的异常。这是因为应用程序代码未对 receive() 方法进行明确调用。要处理此情况,您可注册 ExceptionListener,它是实现 onException() 方法的类的实例。当发生错误时,调用此方法,将 JMSException 作为其仅有的参数传递。
有关使用侦听器来异步接收消息的更多详细信息,请参阅 Java 消息服务文档。
注: 如使用消息驱动的 bean 编程中所述,您可以使用消息驱动的 bean 来替代开发您自己的 JMS 侦听器类。 当从服务器端应用程序组件执行 JMS receive() 时,小心该 receive() 调用是否正在等待由部署在同一服务器中的另一个应用程序组件所产生的消息。 这样的 JMS receive() 是同步的,因此在接收到响应消息前会阻塞。
这种应用程序设计可能会导致使用者或生产者问题,那些被阻塞且等待响应的接收组件可能会耗尽所有工作线程,使得没有工作程序线程可以分派将生成 JMS 响应消息的应用程序组件。例如,将 Servlet 与消息驱动的 bean 部署在相同服务器中。当此 Servlet 分派请求时,它将消息发送至由消息驱动的 bean 服务的队列(即,Servlet 生成的消息由消息驱动的 bean onMessage() 方法使用)。随后,该 Servlet 发出 receive(),等待临时 ReplyTo 队列上的应答。消息驱动的 bean onMessage() 方法执行数据库查询并将应答发送回临时队列上的 Servlet。如果一次产生大量的 Servlet 请求(相对于服务器工作程序线程的数量而言),那么所有可用的服务器工作程序线程都将可能用于分派 Servlet 请求、发送消息以及等待应答。然后,应用程序服务器进入一种情况,在这种情况下没有剩余的线程去处理任何暂挂着的消息驱动的 bean。 因此,由于 Servlet 正在等待获取的响应全部被阻塞,服务器会挂起,这可能会导致应用程序失败。
可能的解决方案是:
- 确保工作程序线程的数量(每个服务器区域线程的数量 * 每台服务器的服务器区域的数量)超出应用程序组件执行 receive() 时并发分派的数量,因此总是有可用的工作程序线程来分派消息产生组件。
- 使用应用程序拓扑以便将接收方应用程序组件放置在与生产者应用程序组件不同的服务器上。虽然在此类部署方案中仍需仔细考虑工作程序线程的使用,但是此分隔确保始终存在不会被消息接收组件阻塞的线程。还有其他交互作用要考虑,例如安装了多个应用程序的应用程序服务器。
- 重构您的应用程序,从客户机组件进行消息接收,这将不与生产方组件竞争工作程序线程。 此外,客户机组件可以进行异步(无阻塞)接收(J2EE 服务器禁止异步接收)。因此,例如,可重构前一示例应用程序,以让客户机将消息发送至队列,然后等待来自 MDB 的响应。
- 如果要将认证用于 IBM MQ 或 V5 嵌入式消息传递支持,那么用户标识的长度不能超过 12 个字符。 例如,缺省 Windows NT 用户标识 administrator 不能用于 WebSphere 内部消息传递,因为它包含 13 个字符。
- 按 EJB 规范中的定义,下列各点应用到 createxxxSession 调用上标志的使用:
- 全局事务中忽略 createxxxSession 上传递的事务标志,而且所有工作作为事务的一部分执行。 在事务外部使用事务标志,如果该标志设置为 true,那么应用程序应该使用 session.commit() 及 session.rollback() 来控制工作的完成。在 EJB2.0 模块中,如果事务标志设置为 true 并位于 XA 事务外部,那么会话包含在 WebSphere 局部事务中,并且该方法的未解析操作属性将应用于 JMS 工作(如果应用程序未落实或回滚该属性)。
- 客户机不能使用 Message.acknowledge() 确认消息。 如果值 CLIENT_ACKNOWLEDGE 是在 createxxxSession 调用上传递的,那么应用程序服务器会自动确认消息,并且不会使用 Message.acknowledge()。
- 如果希望应用程序将 IBM MQ 用作外部 JMS 提供程序,那么发送容器管理的事务中的消息。
将 IBM MQ 用作外部 JMS 提供程序时,在用户管理的事务中发送的消息可以在事务落实之前到达。仅当将 IBM MQ 用作外部 JMS 提供程序并将消息发送至用户管理的事务中的 IBM MQ 队列时,才会发生此情况。这些消息将在该事务落实之前到达目标队列。
发生此问题的原因是 IBM MQ 资源管理器尚未加入用户管理的事务中。
解决方案是使用容器管理的事务。


http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=tmj_desap
文件名:tmj_desap.html