spring-jms,spring-boot-starter-activemq JmsTemplate 发送方式

背景:

springboot jms_java

原来我准备是setDefaultDestinationName 设置队列的名称 发现 系统运行后 创建 的并不是队列 ,而是Topic ,
自己看下源码,发现在创建时候,需要观察session的类型

JmsTemplate提供了3组*3,共计9个发送用的方法。

发送的方法有3组:
基本的发送
转换并发送
转换、后处理再发送

必需的资源

必需的资源有:

javax.jms.ConnectionFactory

ConnectionFactory是客户端编程的开始,由它依次获取Connection、Session、Message、MessageProducer或MessageConsumer,从而做好了发送或接收的准备。

javax.jms.Destination

Destination是发送的目的地,或者接收的源
实际上,我们提供了上述的资源之后,我们就是做了这样的指令:让JmsTemplate连接到Destination,从我们提供的ConnectionFactory中获取连接资源。

P.S.如果你不了客户端编程模型,建议参考02. JMS客户端编程模型。
1.ConnectionFactory资源
我们如何提供ConnectionFactory给JmsTemplate?凭借JmsTemplete提供的构造器或setter方法:
public JmsTemplate(ConnectionFactory connectionFactory)
public void setConnectionFactory(ConnectionFactory connectionFactory)

继承自org.springframework.jms.support.JmsAccessor

2.Destination资源
我们如何提供Destination给JmsTemplate?
我们有两个机会做这件事,第一次是在初始化的时候提供一个默认的Destination,第二次是在发送的时候提供一个明确的Destination。相关的方法:
public void setDefaultDestination(Destination destination)
设置默认的Destination
public void send(Destination destination, MessageCreator messageCreator)
将消息发送到指定的Destination
MessageCreator接口用来提供创建消息的回调方法,后面再讲。

JmsTemplate还提供了另一种获取Destination的方式:基于Destination解析器、Destination类型、Destination名字的获取。你可以使用下面的代码来指定解析器、Destination类型:

1 // jt is instance of JmsTemplate
 2 jt.setDestinationResolver(new DynamicDestinationResolver()); // set Destination解析器
 3 jt.setPubSubDomain(false); // set Destinantion类型
 Destination解析器要实现org.springframework.jms.support.destination.DestinationResolver接口。解析器有默认的值,就是DynamicDestinationResolver,除非你要使用其他的解析器,否则不必调用setDestinationResolver。所以,第2行代码是多余的。
 Destination类型有2个:false-Queue类型,true-Topic类型;默认为false。所以,第3行代码是多余的。
 设置了解析器、类型,或者直接使用默认的值,之后,就可以设置destination的name。JmsTemplate提供了两个方法:
 public void setDefaultDestinationName(String destinationName)
 设置defaultDestination。这个方法和setDefaultDestination(Destination destination)做同样的事情,只是这个方法依赖于解析器和类型。
 public void send(String destinationName, MessageCreator messageCreator)


将消息发送到指定的Destination。这个方法和send(Destination destination, MessageCreator messageCreator)做同样的事情,只是这个方法依赖于解析器和类型。

P.S.如何根据destinationName创建Destination实例?
 javax.jms.Session提供了两个方法,分别创建两种类型的Destination:
 Queue createQueue(java.lang.String queueName)
 根据name创建Queue类型的Destination
 Topic createTopic(java.lang.String topicName)
 根据name创建Topic类型的Destination
 复制代码
 public class DynamicDestinationResolver implements DestinationResolver {
 @Override
 public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain)
 throws JMSException {
 Assert.notNull(session, "Session must not be null");
 Assert.notNull(destinationName, "Destination name must not be null");
 if (pubSubDomain) {
 return resolveTopic(session, destinationName);
 }
 else {
 return resolveQueue(session, destinationName);
 }
 }
 protected Topic resolveTopic(Session session, String topicName) throws JMSException {
 return session.createTopic(topicName);
 }
 protected Queue resolveQueue(Session session, String queueName) throws JMSException {
 return session.createQueue(queueName);
 }
 }


复制代码
根据这份源码,可以帮助理解pubSubDomain的机制,以及将JMS的api和Spring的Destination解析器这两个知识点连接起来。

1.基本的发送方法
在前文我们已经接触了两个,它们都是在发送的同时指定Destination。现把它们和第3种一起介绍:
public void send(Destination destination, MessageCreator messageCreator)
将消息发送到指定的Destination
public void send(String destinationName, MessageCreator messageCreator)
将消息发送到指定的Destination。这个方法和send(Destination destination, MessageCreator messageCreator)做同样的事情,只是这个方法依赖于解析器和类型。
public void send(MessageCreator messageCreator)
将消息发送到defaultDestination。
这个方法要求提前设置defaultDestination,你可以调用setDefaultDestination(Destination destination)或者setDefaultDestinationName(String destinationName)来满足这个前提。
在3个基本的发送方法中,都使用MessageCreator来创建消息。
使用MessageCreator创建消息
jms中的Message,是以接口javax.jms.Message为首的接口家族,这个家族的图谱是这样的:
javax.jms.Message
|---- BytesMessage
|---- MapMessage
|---- ObjectMessage
|---- StreamMessage
|---- TextMessage
JMS将Message细分为5种类型,并在javax.jms.Session接口中分别定义了创建上述Message的多个方法,通常以create*Message为名,返回对应的Message类型。

在JMS的api中,只有javax.jms.Session能创建消息。

所以在Spring中,如果我们要创建Message,就要有Session。但是我们只有现成的ConnectionFactory,我们不应该走一遍从ConnectionFactory到Session的路,否则我们也不需要JmsTemplate帮我们发送了,因为剩下的工作也没多少了——关键是我们并没有从JMS的API中解脱出来。
所以有了MessageCreator的接口,它定义了一个回调的方法:
Message createMessage(Session session)
只要我们把MessageCreator的实例传给JmsTemplate,它就会在合适的时候调用这个方法,并发送返回的消息。
下面给一个例子:
复制代码
复制代码

1 jt.send(DESTINATION_NAME, new MessageCreator() {
 2
 3 public Message createMessage(Session session) throws JMSException {
 4 String text = "A test for MessageCreator.";
 5 Message message = session.createTextMessage(text);
 6 return message;
 7 }


8 });
复制代码
复制代码
2.转换并发送的方法
我们需要将数据装进JMS的Message,然后再发送。JMS的Message有5种具体的类型,不同的类型适合装载不同的数据。如果你不想做这段工作,而是希望能直接把数据丢给谁,然后由它来封装成Message——你只想准备数据,然后发送。那么接下来要介绍的方法,正适合你。

Spring为转换定义了一个接口:org.springframework.jms.support.converter.MessageConverter,这个接口定义了下面的两个方法:
Message toMessage(Object object, Session session)
发送时用到
Object fromMessage(Message message)
接收时用到
一般情况下,我们既不需要为MessageConverter提供实现,也不需要面向MessageConverter进行编程,所以我们实在没有必要关注上面的两个方法,扫一眼,有个大概的印象就够了。

说回JmsTemplate,它定义了下面的方法来设置Converter:
public void setMessageConverter(MessageConverter messageConverter)
而且在初始化的时候,会自动赋值一个SimpleMessageConverter类型的实例,所以我们甚至也不需要关心setMessageConverter方法了。

说了这不多,总结一下,如果要用到转换,我们需要多做什么工作?答案是不需要!
下面是具有转换功能的发送的方法,与基本的发送的方法进行对比:
转换发送 基本发送

public void convertAndSend(Destination destination, Object message)
 将message转换成JMS的Message,并发送到指定的Destination
 public void convertAndSend(String destinationName, Object message)
 将message转换成JMS的Message,并发送到指定的Destination。
 public void convertAndSend(Object message)
 将message转换成JMS的Message,并发送到defaultDestination。
 public void send(Destination destination, MessageCreator messageCreator)
 将消息发送到指定的Destination
 public void send(String destinationName, MessageCreator messageCreator)
 将消息发送到指定的Destination。
 public void send(MessageCreator messageCreator)


将消息发送到defaultDestination。
这两个系列的方法相似度很高,只是在创建消息的问题上有不同的处理:转换发送隐藏了消息的创建,基本发送需要实现MessageCreator接口来创建消息。

接下来是一个demo,我们将上面的demo也拿下来,做一个对比:
转换发送
1 String message = "a message for test convertAndSend.";
2 jt.convertAndSend(DESTINATION_NAME, message);

基本发送
 复制代码
 复制代码
 1 jt.send(DESTINATION_NAME, new MessageCreator() {
 2
 3 public Message createMessage(Session session) throws JMSException {
 4 String text = "A test for MessageCreator.";
 5 Message message = session.createTextMessage(text);
 6 return message;
 7 }
 8 });


复制代码
复制代码
2比8,这纯粹是数学问题了。
3.转换、后处理再发送的方法
javax.jms.Message定义了很多的方法用来为消息添加头部信息或属性。但是如果我们要用转换并发送的方法,我们就接触不到Message类型的消息了,自然也无法为其添加任何信息。JmsTemplate提供了另一套发送的方法,允许我们使用自动转换,还允许我们能接触到转换后的消息,以便我们能做些什么。之后我们会返回处理后的Message,交给JmsTemplate发送。

Spring定义了org.springframework.jms.core.MessagePostProcessor接口来做后处理的事,它定义了一个唯一的方法:
 Message postProcessMessage(Message message)
 对消息进行处理,并返回处理后的消息让我们来看看这些方法,并与前文介绍的方法对比:
 转换、后处理、发送 转换、发送
 public void convertAndSend(Destination destination, Object message, MessagePostProcessor postProcessor)
 public void convertAndSend(String destinationName, Object message, MessagePostProcessor postProcessor)
 public void convertAndSend(Object message, MessagePostProcessor postProcessor)
 public void convertAndSend(Destination destination, Object message)
 将message转换成JMS的Message,并发送到指定的Destination
 public void convertAndSend(String destinationName, Object message)
 将message转换成JMS的Message,并发送到指定的Destination。
 public void convertAndSend(Object message)
 将message转换成JMS的Message,并发送到defaultDestination。
 再来看看两个demo的对比:
 转换、后处理、发送
 复制代码
 复制代码
 1 String message = "a message for test convertProcessAndSend.";
 2 jt.convertAndSend(DESTINATION_NAME, message,
 3 new MessagePostProcessor() {
 4 public Message postProcessMessage(Message message)
 5 throws JMSException {
 6 message.setIntProperty("order", 1);
 7 return message;
 8 }
 9 });
 复制代码
 复制代码
 转换、发送
 1 String message = "a message for test convertAndSend.";
 2 jt.convertAndSend(DESTINATION_NAME, message);


在转换、后处理、发送中,我们为Message设置了一个属性:order=1.
梦想一定要有,万一实现了!