Spring提供一个用于简化JMS API使用的抽象层框架,并且对用户屏蔽JMS API中从1.0.2到1.1版本之间的不同。
JMS大体上被分为两个功能块,消息生产和消息消费。在J2EE环境,由消息驱动的bean提供了异步消费消息的能力 。而在独立的应用中,则必须创建MessageListener或ConnectionConsumer来消费消息。 JmsTemplate的主要功能就是产生消息。Spring的未来版本将会提供,在一个独立的环境中处理异步消息。
org.springframework.jms.core包提供使用JMS的核心功能。 就象为JDBC提供的JdbcTemplate一样, 它提供了JMS模板类来处理资源的创建和释放以简化JMS的使用。 这个Spring的模板类的公共设计原则就是通过提供helper方法去执行公共的操作, 以及将实际的处理任务委派到用户实现的回调接口上,从而以完成更复杂的操作。 JMS模板遵循这样的设计原则。这些类提供众多便利的方法来发送消息、异步地接收消息、 将JMS会话和消息产生者暴露给用户。
org.springframework.jms.support包提供JMSException的转换功能。 它将checked JMSException级别转换到一个对应的unchecked异常级别, 任何checked的javax.jms.JMSException异常的子类都被包装到unchecked的UncategorizedJmsException。 org.springframework.jms.support.converter 包提供一个MessageConverter的抽象进行Java对象和JMS消息之间的转换。 org.springframework.jms.support.destination提供多种管理JMS目的地的策略, 例如为存储在JNDI中的目的地提供一个服务定位器。
最后,org.springframework.jms.connection包提供一个适合在独立应用中使用的 ConnectionFactory的实现。它还为JMS提供了一个Spring的PlatformTransactionManager的实现。 这让JMS作为一个事务资源和Spring的事务管理机制可以集成在一起使用。
JMS主要发布了两个规范版本,1.0.2和1.1。JMS1.0.2定义了两种消息域, 点对点(队列)和发布/订阅(主题)。 JMS1.0.2的API为每个消息领域提供了类似的类体系来处理这两种不同的消息域。 结果,客户端应用在使用JMS API时要了解是在使用哪种消息域。 JMS 1.1引进了统一域的概念来最小化这两种域之间功能和客户端API的差别。 举个例子,如果你使用的是一个JMS 1.1的消息供应者, 你可以使用同一个Session事务性地在一个域接收一个消息后并且从另一个域中产生一个消息。
JMS 1.1的规范发布于2002年4月,并且在2003年11月成为J2EE 1.4的一个组成部分, 结果,现在大多数使用的应用服务器只支持JMS 1.0.2的规范.
使用JmsTemplate的代码只需要实现规范中定义的回调接口。 在JmsTemplate中通过调用代码让MessageCreator回调接口用所提供的会话(Session)创建消息。 然而,为了顾及更复杂的JMS API应用,回调接口SessionCallback将JMS会话提供给用户, 并且暴露Session和MessageProducer。
JMS API暴露两种发送方法,一种接受交付模式、优先级和存活时间作为服务质量(QOS)参数, 而另一种使用缺省值作为QOS参数(无需参数)方式。由于在JmsTemplate中有多种发送方法, QOS参数用bean属性进行暴露设置,从而避免在一系列发送方法中重复。同样地, 使用setReceiveTimeout属性值来设置用于异步接收调用的超时值。
某些JMS供应者允许通过ConnectionFactory的配置来设置缺省的QOS值。 这样在调用MessageProducer的发送方法send(Destination destination, Message message) 时效率更高,因为调用时直接会使用QOS缺省值,而不再用JMS规范中定义的值。 所以,为了提供对QOS值域的统一管理,JmsTemplate必须通过设置布尔值属性isExplicitQosEnabled 为true,使它能够使用自己的QOS值。
JmsTemplate请求一个对ConnectionFactory的引用。 ConnectionFactory是JMS规范的一部分,并被作为使用JMS的入口。 客户端应用通常作为一个工厂配合JMS提供者去创建连接,并封装一系列的配置参数, 其中一些是和供应商相关的,例如SSL的配置选项。
当在EJB内使用JMS时,供应商提供JMS接口的实现,以至于可以参与声明式事务的管理, 提供连接池和会话池。为了使用这个实现,J2EE容器一般要求你在EJB或servlet部署描述符中将JMS连接工厂声明为 resource-ref。为确保可以在EJB内使用JmsTemplate的这些特性, 客户应用应当确保它能引用其中的ConnectionFactory实现。
Spring提供ConnectionFactory接口的一个实现,SingleConnectionFactory, 它将在所有的createConnection调用中返回一个相同的连接, 并忽略close的调用。这在测试和独立的环境中相当有用, 因为同一个连接可以被用于多个JmsTemplate调用以跨越多个事务。 SingleConnectionFactory接受一个通常来自JNDI的标准ConnectionFactory的引用。
Spring为单个JMS ConnectionFactory提供一个JmsTransactionManager来管理事务。 它允许JMS应用可以利用第7章中描述的Spring的事务管理特性。JmsTransactionManager 从指定的ConnectionFactory将一个Connection/Session对绑定到线程。然而,在一个J2EE环境, ConnectionFactory将缓存连接和会话,所以被绑定到线程的实例依赖于缓存的行为。 在一个独立的环境,使用Spring的SingleConnectionFactory将导致使用单独的JMS连接, 而且每个连接都有自己的会话。JmsTemplate也能和JtaTransactionManager 以及XA-capable的JMS ConnectionFactory一起使用以完成分布式事务。
当使用JMS API从连接创建一个Session时,跨越管理性和非管理性事务的复用代码可能会让人困惑。这是因为JMS API只提供一个工厂方法来创建会话,并且它要求事务和确认模式的值。在受管理的环境下,由事务结构环境负责设置这些值,这样在供应商包装的JMS连接中可以忽略这些值。当在一个非管理性的环境中使用JmsTemplate时,你可以通过使用属性SessionTransacted和SessionAcknowledgeMode来指定这些值。当在JmsTemplate中使用PlatformTransactionManager时,模板将一直被赋予一个事务性JMS会话。
Destination,象ConnectionFactories一样,是可以在JNDI中进行存储和提取的JMS管理对象。当配置一个Spring应用上下文,可以使用JNDI工厂类JndiObjectFactoryBean在你的对象引用上执行依赖注入到JMS Destination。然而,如果在应用中有大量的Destination,或者JMS供应商提供了特有的高级Destination管理特性,这个策略常常显得很笨重。高级Destination管理的例子如创建动态destination或支持destination的命名层次。JmsTemplate将destination名字到JMS destination对象的解析委派到一个DestinationResolver接口的实现。DynamicDestinationResolver是JmsTemplate 使用的默认实现,并且提供动态destination解析。同时JndiDestinationResolver作为JNDI包含的destination的服务定位器,并且可选择地退回来使用DynamicDestinationResolver提供的行为。
相当常见的是在一个JMS应用中所使用的destination只有在运行时才知道,因此,当一个应用被部署时,它不能被创建。这经常是因为交互系统组件之间的共享应用逻辑是在运行时按照已知的命名规范创建destination。虽然动态destination的创建不是JMS规范的一部分,但是许多供应商已经提供了这个功能。用户为所建的动态destination定义名字,这样区别于来临时destination,并且动态destination不会被注册到JNDI中。创建动态destination所使用的API在不同的供应商之间差别很大,因为destination所关联的属性是供应商特有的。然而,有时由供应商作出的一个简单的实现选择是忽略JMS规范中的警告,并使用TopicSession的方法createTopic(String topicName)或者QueueSession的方法createQueue(String queueName)来创建一个拥有默认属性的新destination。依赖于供应商的实现,DynamicDestinationResolver可能也能创建一个物理上的destination,而不是只是解析。
布尔属性PubSubDomain被用来配置JmsTemplate使用什么样的JMS域。这个属性的默认值是false,使用点到点的域,也就是队列。在1.0.2的实现中,这个属性值用来决定JmsTemplate将消息发送到一个队列还是主题。这个标志在1.1的实现中对发送操作没有影响。然而,在这两个实现中,这个属性决定了通过DestinationResolver的实现来解析动态destination的行为。
你还可以通过属性DefaultDestination配置一个带有默认destination的JmsTemplate。默认的destination被使用时,它的发送和接收操作不需要指定一个特定的destination。
要开始使用JmsTemplate前,你需要选择JMS 1.0.2的实现,JmsTemplate102,还是JMS 1.1的实现,JmsTemplate。检查一下你的JMS供应者支持那个版本。
JmsTemplate包含许多有用的方法来发送消息。这些发送方法可以使用javax.jms.Destination对象指定destination,也可以使用字符串在JNDI中查找destination。没有destination参数的发送方法使用默认的destination。这里有个例子使用1.0.2的实现发送消息到一个队列。
import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Queue; import javax.jms.Session; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.JmsTemplate102; import org.springframework.jms.core.MessageCreator; public class JmsQueueSender { private JmsTemplate jt; private ConnectionFactory connFactory; private Queue queue; public void simpleSend() { jt = new JmsTemplate102(connFactory, false); jt.send(queue, new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createTextMessage("hello queue world"); } }); } public void setConnectionFactory(ConnectionFactory cf) { connFactory = cf; } public void setQueue(Queue q) { queue = q; } }
这个例子使用MessageCreator回调接口从所提供的会话对象中创建一个文本消息,并且通过一个ConnectionFactory的引用和指定消息域的布尔值来创建JmsTemplate。BeanFactory使用一个没有参数的构造方法和setConnectionFactory/Queue方法来用构造实例。simpleSend方法在下面修改为发送消息到一个主题而不是队列。
public void simpleSend() { jt = new JmsTemplate102(connFactory, true); jt.send(topic, new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createTextMessage("hello topic world"); } }); }
当在应用上下文中配置JMS 1.0.2时,重要的是记得设定布尔属性 PubSubDomain的值以确定你是要发送到队列还是主题。
方法send(String destinationName, MessageCreator c)让你利用destination的名字发送消息。如果这个名字在JNDI中注册,你应当将模板中的DesinationResolver属性设置为JndiDestinationResolver的一个实例。
如果你创建JmsTemplate并指定一个默认的destination,send(MessageCreator c)发送消息到这个destination。
虽然JMS一般都是应用在异步操作,但它也可能同步接收消息。重载的receive方法就提供这个功能。在同步接收时,调用线程被阻塞直到收到一个消息。这是一个危险的操作,因为调用线程可能会被无限期的阻塞。receiveTimeout属性指定接收者在放弃等待一个消息前要等多久。
为了更容易的发送域模式对象,JmsTemplate有多种将一个Java对象作为消息数据内容的发送方法。在JmsTemplate中重载方法convertAndSend和receiveAndConvert,可以将转换过程委派到MessageConverter接口的一个实例。这个接口定义了一个简单的Java对象和JMS消息之间进行转换的约定。它的默认实现SimpleMessageConverter支持在String和TextMessage,byte[]和BytesMesssage,java.util.Map和MapMessage之间进行转换。通过使用转换器,你的应用代码可以专注于通过JMS发送或接收的业务对象,并不用为了怎样将它描述为一个JMS消息而费心。
沙箱目前包含MapMessageConverter,它使用反射在JavaBean和MapMessage之间进行转换。你还可以选择使用XML组包的转换器,如JAXB、Castor、XMLBeans或Xstream,来创建一个TextMessage来描述该对象。
消息属性、消息头和消息体的设置,一般不能被封装在一个转换器类中,为了调整它们,接口MessagePostProcessor可以使你在消息转换后,发送前,访问消息。下面的例子展示了如何在一个java.util.Map被转换为消息之后修改一个消息的头和属性。
public void sendWithConversion() { Map m = new HashMap(); m.put("Name", "Mark"); m.put("Age", new Integer(35)); jt.convertAndSend("testQueue", m, new MessagePostProcessor() { public Message postProcessMessage(Message message) throws JMSException { message.setIntProperty("AccountID", 1234); message.setJMSCorrelationID("123-00001"); return message; } }); }
这是一个由上面得到的消息
MapMessage={ Header={ ... standard headers ... CorrelationID={123-00001} } Properties={ AccountID={Integer:1234} } Fields={ Name={String:Mark} Age={Integer:35} } }