JMS消息发送

目录

    • 概述
    • 1.搭建 JMS 环境
    • 2.使用JmsTemplate 发送消息
    • 3.接收JMS 消息

概述

JMS是一个Java标准,定义了使用消息代理(message broker)的通用API,在2001年提出。长期以来,JMS一直是Java 中实现异步消息的首选方案。在JMS 出现之前每个消息代理都有其私有的API,这就使得不同代理之间的消息代码很难互通。但是借助JMS所有遵从规范的实现都使用通用的接口,这就类似于JDBC为数据库操作提供了通用的接口。
Spring通过基于模板的抽象为JMS功能提供了支持,这个模板就是JmsTemplate。借助 JmsTemplate,能够非常容易地在消息生产方发送队列和主题消息,消费消息的一方也能够非常容易地接收这些消息。Spring 还支持消息驱动POJO的理念:这是一个简单的Java对象,能够以异步的方式响应队列或主题上到达的消息。
我们将会讨论SpringJMS 的支持,包括JmsTemplate 和消息驱动POJO。我们的关注点主要在于SpringJMS 消息的支持,如果你想要了解关于JMS的更多内容,请参阅 Bruce SnyderDejan BosanacRob Davies 合著的ActiveMQ in Action (Manning;2011年)。
在发送和接收消息之前,我们首先需要一个消息代理(broker),它能够在消息的生产者和消费者之间传递消息。对 Spring JMS 的探索就从在 Spring 中搭建消息代理开始吧。

1.搭建 JMS 环境

在使用JMS之前我们必须要将JMS客户端添加到项目的构建文件中。借助 SpringBoot,这再简单不过了。我们要做的就是添加一个 starter 依赖到构建文件中。但是,首先,我们需要决定该使用Apache ActiveMQ 还是更新的Apache ActiveMQ Artemis代理如果使用Apache ActiveMQ,那么需要添加如下的依赖到项目的 pom.xml文件中:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

如果使用ActiveMQ Artemis,那么 starter 依赖如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-artemis</artifactId>
</dependency>

在使用 Spring Initializr (或者使用 IDE 作为 Initializr 的前端)的时候,我们能够以starter 依赖的方式为项目选择这两个方案。它们分别显示为“Spring for Apache ActiveMQ5”和“Spring for Apache ActiveMQ Artemis。如图:

在这里插入图片描述
Artemis是重新实现的下一代ActiveMQ,使ActiveMQ变成了遗留方案。因此,TacoCloud(本次项目主题)会选择使用Artemis。但是在编写发送和接收消息的代码方面,选择哪种方案几乎没有什么区别。唯一需要注意的重要差异就是如何配置 Spring 创建到代理的连接。

运行Artemis代理
要运行本次实验中的代码,我们需要有一个Artemis 代理。如果没有可运行的Artemis
实例,可以参阅Artemis文档中的指南。

默认情况下,Spring 会假定Artemis 代理在 localhost的61616端口运行。对于开发来说,这样是没有问题的,但是一旦要将应用部署到生产环境,我们就需要设置一些属性来告诉 Spring 如何访问代理。常用的属性如表所示。

属性描述
spring.artemis.host代理的主机
spring.artemis.port代理的端口
spring.artemis.user用来访问代理的用户(可选)
spring.artemis.password用来访问代理的密码(可选)

例如,如下的application.yml文件条目可以用于一个非开发的环境:

spring:
	artemis:
		host: artemis.tacocloud.com
		port: 61617
		user: tacoweb
		password: 13tm31n

这会让 Spring 创建到 Artemis代理的连接,该 Artemis 代理监听 artemis.tacocloud.com的61617端口。它还为应用设置了与代理交互的凭证信息。凭证信息是可选的,但是对于生产环境来说,我们推荐使用它们。
如果你选择使用ActiveMQ 而不是 Artemis,那么需要使用 ActiveMQ 特定的属性如表所示:

属性描述
spring.activemq.broker-url代理的 URL
spring.activemq.user用来访问代理的用户(可选)
spring.activemq.password用来访问代理的密码(可选)
spring.activemq.in-memory是否启用在内存中运行的代理(默认为 true)

需要注意,ActiveMQ 代理不是分别设置代理的主机和端口,而是使用了一个名为spring.activemgbroker-url 的属性来指定代理的地址。URL应该是“tcp://”协议的地址,如下面的YAML片段所示:

spring:
	activemg:
		broker-url: tcp://activemq.tacocloud.com
		user: tacoweb
		password: 13tm31n

不管选择 Artemis还是ActiveMQ,在本地开发运行时,都不需要配置这些属性。
但是,使用ActiveMQ时,则需要将 spring.activemq.in-memory 属性设置为 false,防止 Spring启动内存中运行的代理。内存中运行的代理看起来很有用,但是只有在同个应用中发布和消费消息的情况下,才能使用它(这限制了它的用途 )。
在继续下面的内容之前,我们要安装并启动一个 Artemis(或 ActiveMQ)代理而不是选择使用嵌人式的代理。
现在,我们已经在构建文件中添加了对JMS starter 依赖,代理也已经准备好将消息从一个应用传递到另一个应用,接下来,我们就可以开始发送消息了。

2.使用JmsTemplate 发送消息

JMS starter 依赖(不管是 Artemis 还是 ActiveMQ)添加到构建文件之后,Spring Boot会自动配置一个 JmsTemplate(以及其他内容),我们可以将它注入其他 bean,并使用它来发送和接收消息
JmsTemplateSpringJMS集成支持功能的核心与Spring其他面向模板的组件类似,JmsTemplate消除了大量传统使用JMS时所需的样板代码如果没有JmsTemplate我们就需要编写代码来创建与消息代理的连接和会话,还要编写更多的代码来处理发送消息过程中可能出现的异常。JmsTemplate 能够让我们关注真正要做的事情: 发送消息JmsTemplate有多个用来发送消息的方法,包括:

// 发送原始的消息
void send(MessageCreator messageCreator) throws JmsException;
void send(Destination destination, MessageCreator messageCreator)
							throws JmsException;
void send(String destinationName, MessageCreator messageCreator)
							throws JmsException;
//发送根据对象转换而成的消息
void convertAndSend(Object message) throws JmsException;
void convertAndSend(Destination destination, Object message)
							throws JmsException;
void convertAndSend(String destinationName, Object message)
							throws JmsException;
//发送根据对象转换而成的消息,且带有后期处理的功能
void convertAndSend(Object message, MessagePostProcessor postProcessor) 
							throws JmsException;
void convertAndSend(Destination destination,Object messaqe, MessagePostProcessor postProcessor) 
							throws JmsException;
void convertAndSend(String destinationName, Object message, MessagePostProcessor postProcessor) 
							throws JmsException;

我们可以看到,上面的代码实际上只展示了两个方法,也就是 send()convertAndSend(),但每个方法都有重载形式以支持不同的参数。如果我们仔细观察一下,convertAndSend()的各种形式又可以分成两个子类。在考虑这些方法作用的时候,我们对它们进行细分:

  • 3个send() 方法都需要 MessageCreator 来生成Message 对象;
  • 3个 convertAndSend()方法会接受 Object 对象,并且会在幕后自动将 Object
    换为Message;
  • 3个 convertAndSend()会自动将 Object 转换为 Message,但同时还能接受一个MessagePostProcessor 对象,用来在发送之前对 Message 进行自定义。

这 3种方法分类都分别包含 3 个重载方法,它们的区别在于指定JMS 目的地(队列或主题)的方式:

  • 有1个方法不接受目的地参数,它会将消息发送至默认的目的地:
  • 有1个方法接受 Destination 对象,该对象指定了消息的目的地;
  • 有1个方法接受 String,它通过名字的形式指定了消息的目的地,

为了让这些方法真正发挥作用,我们看一下下面程序中的JmsOrderMessagingService。它使用了形式最简单的 send()方法。

//使用send()方法将订单发送至默认的目的地
package tacos.messaging;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

@Service
public class JmsOrderMessagingService implements OrderMessagingService {
    private JmsTemplate jms;

    @Autowired
    public JmsOrderMessagingService(JmsTemplate jms) {
        this.jms = jms;
    }

    @Override
    public void sendOrder(TacoOrder order) {
        jms.send(new MessageCreator() {
            @Override
            public Message createMessage(Session session)
                    throws JMSException {
                return session.createObjectMessage(order);

            }
        });
    }
}
   

sendOrder()方法调用了jms.send(),并传递了 MessageCreator 接口的一个匿名内部类实现。这个实现类重写了 createMessage()方法,从而能够通过给定的 TacoOrder 对象创建新的消息对象。
因为面向JMS功能的JmsOrderMessagingService实现了更通用的OrderMessagingService接口,所以为了使用这个类,我们可以将其注入 OrderApiController,并在创建订单的时候调用 sendOrder()方法,如下所示:

@RestController
@RequestMapping(path = "/api/orders",
        produces = "application/json")
@CrossOrigin(origins = "http;//localhost:8080")
public class OrderApiController {
    private OrderRepository repo;
    private OrderMessagingService messageService;

    public OrderApiController(OrderRepository repo, OrderMessagingService messageService) {
        this.repo = repo;
        this.messageService = messageService;
    }

    @PostMapping(consumes = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public TacoOrder postOrder(@RequestBody TacoOrder order) {
        messageService.sendOrder(order);
        return repo.save(order);
    }
    
    .....

}

现在,我们通过 Taco Cloud 的 Web 站点创建订单时,就会有一条消息发送至代理以便将其路由至另外一个接收订单的应用。不过,我们还没有任何接收消息的功能。即便如此,依然可以使用Artemis 控制台来查看队列的内容。请参阅 Artemis 文档了解实
现的细节。不知道你的感觉如何,但是我认为程序清单中的代码虽然比较简单,但还是有点啰唆。声明匿名内部类的过程使得原本很简单的方法调用变得很复杂。我们发现MessageCreator是一个函数式接口,所以可以通过lambda表达式简化 sendOrder()方法:

@Override
public void sendOrder(TacoOrder order) {
	jms.send(session -> session.createObjectMessage(order));
}

但是需要注意,对 jms.send()的调用并没有指定目的地。为了让它能够运行,我们需要通过名为spring.jms.template.default-destination的属性声明一个默认的目的地名称。例如,可以在application.yml文件中这样设置该属性:

spring:
  jms:
    template:
      default-destination: tacocloud.order.queue

在很多场景下,使用默认的目的地是最简单的可选方案。借助它,我们只声明一次目的地名称就可以了,代码只关心发送消息,而不关心消息会发到哪里。但是如果要将消息发送至默认目的地之外的其他地方,那么就需要通过为 send()设置参数来指定。
其中一种方式是传递 Destination对象作为 send()方法的第一个参数。最简单的方式就是声明一个Destination bean 并将其注人处理消息的 bean。例如,如下的 bean 声明了Taco Cloud 订单队列的 Destination:

@Bean
public Destination orderQueue() (
	return new ActiveMQQueue("tacocloud.order.queue");
}

在需要通过JMS 发送和接收消息的应用中,这个 bean 方法可以添加到任意的配类里面。但是为了良好的代码组织,最好将其添加到一个专为消息相关的配置所创建配置类中,比如MessagingConfig
很重要的一点是,这里的 ActiveMQQueue 来源于 Artemis(来自 org.apache.activemq.artemis,jmsclient 包)。如果你使用 ActiveMQ(而不是 Artemis),同样会找到一个名为ActiveMQQueue的类(来自org.apache.activemq.command包)。
Destination bean 注人JmsOrderMessagingService 之后,调用 send()的时候,我们就可以使用它来指定目的地了:

private Destination orderQueue;
@Autowired
public JmsOrderMessagingService(JmsTemplate jms,Destination orderQueue) {
	this.jms= jms;
	this.orderQueue = orderQueue;
}
@Override
public void sendOrder(TacoOrder order) {
	jms.send(
		orderQueue, 
		session -> session.createObjectMessage(order));
}

通过Destination 指定目的地时,其实可以设置 Destination的更多属性,而不仅仅是目的地的名称。但是,在实践中,除了目的地名称,我们几乎不会设置其他的属性。因此,使用名称作为send()的第一个参数会更加简单:

@Override
public void sendOrder(TacoOrder order) (
	jms,send("tacocloud.order.queue",
		session -> session.createObjectMessage(order));
}

尽管 send()方法使用起来并不是特别困难(通过 lambda表达式来实现 MessageCreator更是如此),但是它要求我们提供 MessageCreator,还是有些复杂。如果能够只指定要发送的对象(以及可能要用到的目的地),岂不是更简单?这其实就是 convetAndSend()的工作原理。接下来,我们看一下这种方式。

消息发送之前进行转换

JmsTemplatesconvertAndSend()方法简化了消息的发布因为它不再需要MessageCreator。我们将要发送的对象直接传递给 convertAndSend(),这个对象在发送之前会被转换成 Message
例如,在如下重新实现的 sendOrder()方法中,使用 convertAndSend()TacoOrder对象发送到给定名称的目的地

@Override
public void sendOrder(TacoOrder order) {
	jms.convertAndSend("tacocloud.order.queue", order);
}

send()方法类似,convertAndSend()将会接受一个Destination对象或String值来确定目的地,我们也可以完全忽略目的地,这样一来,消息会发送到默认目的地上。
不管使用哪种形式的convertAndSend(),传递给convertAndSend()TacoOrder 都会在发送之前转换成Message。在底层,这是通过MessageConverter 的实现类来完成的
它替我们完成了将对象转换成Message 的任务。

配置消息转换器

MessageConverterSpring定义的接口,它只有两个需要实现的方法:

public interface MessageConverter {
	Message toMessage(Object object, Session session)throws JMSException, MessaqeConversionException;
	Object fromMessage(Message message)
}

尽管这个接口实现起来很简单,但我们通常并没有必要创建自定义的实现。Spring已经提供了多个实现,如表所示。
Spring 为通用的转换任务提供了多个消息转换器(所有的消息转换器都位于org.springframework,jms.support.converter 包中)。

消息转换器功能
MappingJackson2MessageConverter使用Jackson 2 JSON库实现消息与JSON格式的相互转换。
MarshallingMessageConverter使用JAXB库实现消息与XML格式的相互转换。
MessagingMessageConverter对于消息载荷,使用底层的MessageConverter实现抽象MessagejavaxjmsMessageMessage 的相互转换,同时会使用JmsHeaderMapper实现JMS头信息与标准消息头信息的相互转换。
SimpleMessageConverter实现StringTextMessage 的相互转换、字节数组与BytesMessage的相互转换、MapMapMessage 的相互转换,以及Serializable对象与ObjectMessage 的相互转换。

默认情况下,将会使用 SimpleMessngeConverter,但是它需要被发送的对象实现serializable。这种办法也不错,但有时候我们可能想要使用其他的消息转换器来消除这种限制,比如MappingJackson2MessageConverter
为了使用不同的消息转换器,我们必须将选中的消息转换器实例声明为一个bean。例如如下的 bean 声明将会使用 MappingJackson2MessageConverter 替代 SimpleMessageConverter:

@Bean
public MappingJackson2MessageConverter messageConverter(){
	MappingJackson2MessageConverter messageConverter=new MappingJackson2MessageConverter();
	messageConverter.setTypeIdPropertyName("_typeId");
	return messageConverter;
}

在需要通过JMS 发送和接收消息的应用中,这个 bean 方法可以添加到任意的配置类里面,包括定义DestinationMessagingConfig 中。

需要注意,在返回之前,我们调用了 MappingJackson2MessageConvertersetTypeloPropertyName()方法。这非常重要,因为这样能够让接收者知道传人的消息需要转换成什么类型。默认情况下,它会包含要转换的类型的全限定类名。但是,这要求接收端也包含相同的类型,并且具有相同的全限定类名,未免不够灵活。
为了提升灵活性,我们可以通过调用消息转换器的 setTypeldMappings()方法将一个合成类型名映射到实际类型上。举例来说,消息转换器 bean 方法的如下代码变更会将一个合成的 TacoOrder 类型ID映射为TacoOrder 类:

@Bean
public MappingJackson2MessageConverter messageConverter() {
	MappingJackson2MessageConverter messageConverter =new MappingJackson2MessageConverter();
	messageConverter.setTypeIdPropertyName("_typeId");
	Map<StringClass<?>> typeIdMappings = new HashMap<Stringclass<?>>();
	typeIdMappings.put("order", TacoOrder.class);
	messageConverter.setTypeIdMappings(typeIdMappings);
	return messageConverter;
}

这样,消息的_typeld 属性中就不会发送全限定类型,而是会发送 TacoOrder 值了在接收端的应用中会配置类似的消息转换器,将 TacoOrder 映射为它自己能够理解的单类型。在接收端的订单可能位于不同的包中、有不同的类名,甚至可以只包含发送者Order 属性的一个子集。

对消息进行后期处理

假设在经营利润丰厚的 Web 业务之外,Taco Cloud 还决定开几家实体的连锁 taco店。
鉴于任何一家餐馆都可能成为 Web 业务的运行中心,需要有一种方式告诉厨房订单的来源。这样一来,厨房的工作人员就能为店面里的订单和 Web 上的订单执行不同的流程。
我们可以在 TacoOrder 对象上添加一个新的 source属性,让它携带订单来源的相关信息:如果是在线订单,就将其设置为 WEB;如果是店面里的订单,就将其设置为STORE。但是,这需要我们同时修改 Web 站点的 TacoOrder 和厨房应用的 TacoOrde类,但实际上,只有准备 taco 的人需要该信息。
有种更简单的方案是为消息添加一个自定义的头部,让它携带订单的来源。如果使用send()方法来发送 taco 订单,就可以通过调用 Message 对象的 setStringProperty()方法非常容易地实现该功能:

jms.send("tacocloud.order.queuen",
	session->{
	Message message = session.createObjectMessage(order);
	message.setStringProperty("X_ORDER_ SOURCE","WEB");
	});

但是,这里的问题在于我们并没有使用 send()。使用 convertAndSend()方法时Message 是在底层创建的,我们无法访问到它。
幸好,还有一种方式能够在发送之前修改底层创建的 Message 对象。我们可以传递个MessagePostProcessr 作为 convertAndSend()的最后一个参数,借助它,我们可以在Message 创建之后做任何想做的事情。如下的代码依然使用了 convertAndSend(),但是它能够在消息发送之前,使用MessagePostProcessor 添加X_ORDER SOURCE头信息:

jms.convertAndSend("tacocloud.order.queue",order, new MessagePostProcessor(){
	@Override
	public Message postProcessMessage(Message message) throws JMSException {
		message.setStringProperty("X_ ORDER_SOURCE","WEB");
		return message;
	}
});

你可能已经发现,MessagePostProcessor 是一个函数式接口。这意味着我们可以将匿名内部类替换为 lambda 表达式,进一步简化它:

jms.convertAndSend("tacocloud,order.queue", order,
	message ->{
	message.setStringProperty("X_ORDER_ SOURCE","WEB");
	return message;
	});

尽管在这里我们只是将这个特殊的 MessagePostProcessor 用到了本次 convertAndSend()方法调用中,但是你可能会发现代码在不同的地方多次调用 convertAndSend(),它们均会用到相同的MessagePostProcessor。在这种情况下,方法引用是比 lambda 表达式更好的方案,因为它能避免不必要的代码重复:

@GetMapping("/convertAndSend/order")
public String convertAndSendOrder() {
	TacoOrder order = buildOrder();
	jms.convertAndSend("tacocloud.order.queue",order,.
			this::addOrderSource);
	return "Convert and sent order";
	}
private Message addorderSource (Message message) throws JMSException {
	message.setStringProperty("X_ORDER_ SOURCE","WEB);
	return message;
}

我们已经看到了多种发送消息的方式。但是如果只发送消息而无人接收,那么这其实没有什么价值。接下来,我们看一下如何使用 Spring 和JMS 接收消息。

3.接收JMS 消息

在消费消息时,我们可以选择遵循拉取模式(pull model)或推送模式(push model)前者会在我们的代码中请求消息并一直等到消息到达,而后者则会在消息可用的时候自动在你的代码中执行。
JmsTemplate 提供了多种方式来接收消息,但它们使用的都是拉取模式。我们可以调用其中的某个方法来请求消息,这样线程会一直阻塞直到一个消息抵达为止(这可能马上发生,也可能需要等待一会儿)。
另外,我们也可以使用推送模式。在这种情况下,我们会定义一个消息监听器,每当有消息可用时,它就会被调用。
这两种方案适用于不同使用场景。通常人们觉得推送模式是更好的方案,因为它不会阻塞线程。但是,在某些场景下,如果消息抵达的速度太快,监听器可能会过载,而拉取模式允许消费者声明自己何时为接收新消息做好准备。
我们首先关注JmsTemplate提供的拉取模式。

使用JmsTemplate 接收消息

JmsTemplate提供了多个对代理的拉取方法,其中包括:

Message receive() throws JmsException;
Message receive(Destination destination) throws JmsException;
Message receive(String destinationName) throws JmsException;
Object receiveAndConvert() throws JmsException;
Object receiveAndConvert(Destination destination) throws JmsException;
Object receiveAndConvert(String destinationName) throws JmsException;

我们可以看到,这6个方法简直就是 JmsTemplatesend()convertAndSend()方法的镜像。receive()方法接收原始的 Message,而receiveAndConvert()则会使用一个配置好的消息转换器将消息转换成领域对象。对于其中的每种方法,我们都可以指定 Destination或者包含目的地名称的 String 值,若不指定,则从默认目的地拉取消息。
为了实际看一下它是如何运行的,我们编写代码从 tacocloud.order.queue 目的地拉取一个 TacoOrder 对象。下方程序展现了 OrderReceiver,这个服务组件会使用JmsTemplate.receive()来接收订单数据。

//从队列拉取订单
package tacos.kitchen.messaging.jms;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

import javax.jms.Message;

@Component
public class JmsOrderReceiver implements OrderReceiver {
    private JmsTemplate jms;
    private MessageConverter converter;

    @Autowired
    public JmsOrderReceiver(JmsTemplate jms, MessageConverter converter) {
        this.jms = jms;
        this.converter = converter;
    }

    public TacoOrder receiveOrder() {
        Message message = jms.receive("tacocloud.order.queue");
        return (TacoOrder) converter.fromMessage(message);
    }
}

这里我们使用 String 值来指定从哪个目的地拉取订单。receive()返回的是没有经过转换的 Message,但是,我们真正需要的是 Message 中的 TacoOrder,所以接下来要做的事情就是使用注入的消息转换器对消息进行转换。消息中的 type ID 属性将会指导转换器将消息转换成 TacoOrder,但它返回的是 Object,因此在最终返回之前要进行类型转换。
如果要探查消息的属性和消息头信息,接收原始的 Message 对象可能会非常有用但是,通常来讲,我们只需要消息的载荷。将载荷转换成领域对象是一个需要两步操作的过程,而且它需要将消息转换器注入到组件中。如果只关心载荷,那么使用receiveAndConvert()会更简单一些。下面将展现如何使用 receiveAndConvert()替换 receive()来重新实现JmsOrderReceiver

//接收已经转换好的 TocoOrder 对象
package tacos.kitchen.messaging.jms;
import org.springframework.jms.core.JmsTemplate;
//接收已经转换好的 TocoOrder 对象
package tacos.kitchen.messaging.jms;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.OrderReceiver;

@Component
public class JmsOrderReceiver implements OrderReceiver {

    private JmsTemplate jms;

    public JmsOrderReceiver(JmsTemplate jms) {
        this.jms = jms;
    }

    @Override
    public TacoOrder receiveOrder() {
        return (TacoOrder) jms.receiveAndConvert("tacocloud.order.queue");
    }
}

新版本的JmsOrderReceiverreceieveOrder()方法简化到了只包含一行代码。同时我们不再需要注人 MessageConverter,因为所有的操作都会在 receiveAndConvert()方法的“幕后”完成。
在继续下面的内容学习之前,我们考虑一下如何在 Taco Cloud 厨房应用中使用receiveOrder()Taco Cloud 厨房中的厨师可能会按下一个按钮或者采取其他操作,表明准备好制作 taco。此时,receiveOrder()会被调用,然后对 receive()receiveAndConvert()的调用会阻塞。在订单消息抵达之前,这里不会发生任何的事情。一旦订单抵达,对receiveOrder()的调用会把该订单的信息返回,订单的详细信息会展现给厨师,这样他就可以开始制作了。对于拉取模式来说,这似乎是一种很自然的选择。
接下来,我们看一下如何通过声明JMS监听器来实现推送模式

声明消息监听器

拉取模式需要显式调用 receive()receiveAndConvert()接收消息,与之不同,消息监听器是一个被动的组件。在消息抵达之前,它会一直处于空闲状态。
要创建能够对 JMS 消息做出反应的消息监听器,我们需要为组件中的某个方法添加@JmsListener 注解。下面程序展示了一个新的 OrderListener 组件。它会被动地监听消息,而不主动请求消息。

//监听订单消息的 OrdeListener组件
package tacos.kitchen.messaging.jms.listener;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import tacos.kitchen.KitchenUI;

@Profile("jms-listener")
@Component
public class OrderListener {
    private KitchenUI ui;

    @Autowired
    public OrderListener(KitchenUI ui) {

        this.ui = ui;
    }

    @JmsListener(destination = "tacocloud.order.queue")
    public void receiveOrder(Tacoorder order) {
        ui.displayOrder(order);
    }
}

receiveOrder()方法使用了 JmsListener 注解,这样它就会监听 tacocloud.order.queue目的地的消息。该方法不需要使用JmsTemplate,也不会被我们的应用显式调用。相反Spring中的框架代码会等待消息抵达指定的目的地,当消息到达时,receiveOrder()方法会自动调用,并且会将消息中的 TacoOrder 载荷作为参数。
在很多方面,@JmsListener 注解都和Spring MVC中的请求映射注解很相似,比如@GetMapping@PostMapping。在 Spring MVC 中带有请求映射注解的方法会响应指定路径的请求。与之类似,使用@JmsListener 注解的方法会对到达指定目的地的消息做出响应。
消息监听器通常被视为最佳选择,因为它不会导致阻塞,并且能够快速处理多个消息但是在Taco Cloud 中,它可能并不是最佳的方案。在系统中,厨师是一个重要的瓶颈。他可能无法在接收到订单的时候立即准备 taco。当新订单出现在屏幕上的时候,上一个订单可能刚刚完成一半。厨房用户界面需要在订单到达时进行缓冲,避免给厨师带来过重的负载。
这并不是说消息监听器不好。相反,如果消息能够快速得到处理,那这是非常适合的方案。但是,如果消息处理器需要根据自己的时间请求更多消息,那么 JmsTemplate提供的拉取模式会更加合适。
JMS 是由标准 Java 规范定义的,所以它得到了众多代理实现的支持,在 Java 中实现消息时,它是很常见的可选方案。但是JMS 有一些缺点,尤其是作为 Java 规范的它只能用在 Java应用中。RabbitMQKafka 等较新的消息传递方案克服了这些缺点,可以用于JVM 之外的其他语言和平台。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/308659.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB 启动提示错误code=killed, signal=ABRT

1.停止MongoDB sudo systemctl stop mongod 2.检查数据损坏 sudo mongod --repair --dbpath /var/lib/mongodb 3.赋权限 chown -R mongodb:mongodb /var/lib/mongodb chown mongodb:mongodb /tmp/mongodb-27017.sock 如果不赋权限&#xff0c;启动的时候则会提示 4.启动Mo…

python 工作目录 与 脚本所在目录不一致

工作目录&#xff1a;执行脚本的地方 我以为工作目录会是当前执行脚本的目录位置&#xff0c;但其实不是&#xff0c;例如&#xff1a; 图中红色文件为我执行的脚本文件&#xff0c;但是实际的工作目录是PYTHON LEARNING 可以用如下代码查询当前工作目录&#xff1a; import os…

2024年甘肃省职业院校技能大赛 “信息安全管理与评估”赛项样题卷①

2024年甘肃省职业院校技能大赛 高职学生组电子与信息大类信息安全管理与评估赛项样题 第一阶段&#xff1a;第二阶段&#xff1a;模块二 网络安全事件响应、数字取证调查、应用程序安全第二阶段 网络安全事件响应第一部分 网络安全事件响应第二部分 数字取证调查第三部分 应用程…

redis可视化工具 RedisInsight

redis可视化工具 RedisInsight 1、RedisInsight是什么2、下载RedisInsight3、使用RedisInsight4、其他redsi可视化工具 1、RedisInsight是什么 RedisInsight 是一个用于管理和监控 Redis 数据库的图形用户界面&#xff08;GUI&#xff09;工具。它是由 Redis Labs 开发的&…

Linux驱动学习—输入子系统

1、什么是输入子系统&#xff1f; 输入子系统是Linux专门做的一套框架来处理输入事件的&#xff0c;像鼠标&#xff0c;键盘&#xff0c;触摸屏这些都是输入设备&#xff0c;但是这邪恶输入设备的类型又都不是一样的&#xff0c;所以为了统一这些输入设备驱动标准应运而生的。…

CHS_01.2.1.1+2.1.3+进程的概念、组成、特征

CHS_01.2.1.12.1.3进程的概念、组成、特征 进程进程的概念 进程的组成——PCB进程的组成——PCB进程的组成——程序段、数据段知识滚雪球&#xff1a;程序是如何运行的&#xff1f;进程的组成进程的特征 知识回顾与重要考点 从这个小节开始 我们会正式进入第二章处理机管理相关…

【前端】使用javascript开发一个在线RGB颜色转换

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是是《前端》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌…

领英Linkedin自动跳转中国站点的解决方案

linkedin放弃中国市场后&#xff0c;在国内打开linkedin.com&#xff0c;会自动跳转到 linkedin.cn&#xff0c;无法与国际友人在同一个平台上联系。 按照搜到的方法尝试解决&#xff0c;包括修改浏览器默认语言、清除浏览数据、使用软路由上的插件给 linkedin.com设置从国外线…

CentOS本地部署SQL Server数据库无公网ip环境实现远程访问

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

使用 CMake 和 Ninja 构建 C/C++ 项目的教程

使用 CMake 和 Ninja 构建 C/C 项目的教程 CMake 是一个跨平台的开源构建工具&#xff0c;它简化了项目的构建过程。而 Ninja 是一个快速、轻量级的构建系统&#xff0c;与 CMake 配合使用可以提高项目的构建效率。本教程将向你介绍如何使用 CMake 和 Ninja 来构建你的 C/C 项…

版本控制背景知识

版本控制背景知识 本文是关于 Git 系列文章的导读&#xff0c;我们先介绍一下版本控制的背景知识。 什么是版本控制 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它将什么时候、什么人更改了文件的什么内容等信息如实记录下来…

代码随想录算法训练营第一天|数组理论基础、704二分查找、27移除元素

数组理论基础 一维数组 数组中的元素在内存空间中是连续的数组名与数组中第一个元素的地址相同&#xff08;一维数组&#xff09;数组的下标从0开始删除数组的元素其实是用后面的元素覆盖掉要删除的元素数组的长度不能改变 二维数组 二维数组是按照行存储的&#xff0c;也是…

Vue入门四(组件介绍与定义|组件之间的通信)

文章目录 一、组件介绍与定义介绍定义1&#xff09;全局组件2&#xff09;局部组件 二、组件之间的通信1&#xff09;父组件向子组件传递数据2&#xff09;子传父通信 一、组件介绍与定义 介绍 组件(Component)是Vue.js 最强大的功能之一&#xff0c;它是html、css、js等的一个…

STK 特定问题建模(五)频谱分析(第二部分)

文章目录 简介三、链路分析3.1 星地链路干扰分析3.2 频谱分析 简介 本篇对卫星通信中的频谱利用率、潜在干扰对频谱的影响进行分析&#xff0c;以LEO卫星信号对GEO通信链路影响为例&#xff0c;分析星地链路频谱。 建模将从以下几个部分开展&#xff1a; 1、GEO星地通信收发机…

稀疏矩阵的三元组表示----(算法详解)

目录 基本算法包括&#xff1a;&#xff08;解释都在代码里&#xff09; 1.创建 2.对三元组元素赋值 3.将三元组元素赋值给变量 4.输出三元组 5.转置&#xff08;附加的有兴趣可以看看&#xff09; 稀疏矩阵的概念&#xff1a;矩阵的非零元素相较零元素非常小时&#xff…

极少数据就能微调大模型,一文详解LoRA等方法的运作原理

原文&#xff1a;极少数据就能微调大模型&#xff0c;一文详解LoRA等方法的运作原理 最近和大模型一起爆火的&#xff0c;还有大模型的微调方法。 这类方法只用很少的数据&#xff0c;就能让大模型在原本表现没那么好的下游任务中“脱颖而出”&#xff0c;成为这个任务的专家…

大气精美网站APP官网HTML源码

源码介绍 大气精美网站APP官网源码&#xff0c;好看实用&#xff0c;记事本修改里面的内容即可&#xff0c;喜欢的朋友可以拿去研究 下载地址 蓝奏云&#xff1a;https://wfr.lanzout.com/itqxN1ko2ovi CSDN免积分下载&#xff1a;https://download.csdn.net/download/huayu…

大型语言模型与知识图谱的完美结合:从LLMs到RAG,探索知识图谱构建的全新篇章

最近,使用大型语言模型(LLMs)和知识图谱(KG)开发 RAG(Retrieval Augmented Generation)流程引起了很大的关注。在这篇文章中,我将使用 LlamaIndex 和 NebulaGraph 来构建一个关于费城费利斯队(Philadelphia Phillies)的 RAG 流程。 我们用的是开源的 NebulaGraph 来…

redis 主从同步和故障切换的几个坑

数据不一致 当我们从节点读取一个数据时&#xff0c;和主节点读取的数据不一致&#xff0c;这是因为主从同步的命令是异步进行的&#xff0c;一般情况下是主从同步延迟导致的&#xff0c;为什么会延迟&#xff0c; 主要二个原因 1、网络状态不好 2、网络没问题&#xff0c;从节…

电脑找不到d3dcompiler43.dll怎么修复,教你5个可靠的方法

d3dcompiler43.dll是Windows操作系统中的一个重要动态链接库文件&#xff0c;主要负责Direct3D编译器的相关功能。如果“d3dcompiler43.dll丢失”通常会导致游戏无法正常运行或者程序崩溃。为了解决这个问题&#xff0c;我整理了以下五个解决方法&#xff0c;希望能帮助到遇到相…