本文记录Sping In Action5 第8章 发送异步消息JMS(ActiveMQ Artemis)中的踩坑情况。
第8章的源码请自行github或gitee搜索,或参考一下。
GitHub - habuma/spring-in-action-5-samples: Home for example code from Spring in Action 5.
https://gitee.com/drop1et/spring-in-action-5-samples/
win7 x64
jdk 1.8
idea 2018.3
ActiveMQ Artemis版本号:apache-artemis-2.19.1
下载、安装、启动步骤请网搜。可参考https://www.jianshu.com/p/b2734268aaaf
官网https://activemq.apache.org/components/artemis/
注意使用cmd窗口将artemis启动后,不要关闭cmd窗口,以便保留artemis。
注意,因jms使用的并非MQTT协议,因此使用可视化图形界面时将看不到springboot JMS发送和接收的消息,可通过IDEA控制台中springboot程序的输出日志查看代码自定义日志的发送接收消息。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-artemis</artifactId>
- </dependency>
- #ActiveMQ Artemis 配置
- #ActiveMQ Artemis broker主机
- spring.artemis.host=localhost
- #ActiveMQ Artemis broker端口
- spring.artemis.port=61616
- #ActiveMQ Artemis broker 访问用户名
- spring.artemis.user=admin
- #ActiveMQ Artemis broker 访问密码
- spring.artemis.password=admin
- #指定jms发送消息的目的地(主体)
- spring.jms.template.default-destination=wdhqueue
- package com.wdh.tacocloud_jms.messaging;
-
- import com.wdh.tacocloud_jms.domain.Order;
- import lombok.extern.slf4j.Slf4j;
- 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;
-
- /**
- * @author WangDH
- * @create 2022-09-27 14:32
- */
- @Slf4j
- @Service
- public class JmsOrderMessagingService implements OrderMessagingService {
-
- private JmsTemplate jmsTemplate;
-
- @Autowired
- public JmsOrderMessagingService(JmsTemplate jmsTemplate) {
- this.jmsTemplate = jmsTemplate;
- }
-
-
- @Override
- public void sendOrder(Order order) {
- log.info("JmsOrderMessagingService start sendOrder start.");
-
- //方式一
- // jmsTemplate.send(
- // new MessageCreator() {
- // @Override
- // public Message createMessage(Session session) throws JMSException {
- // //return session.createObjectMessage("Hello from wdhSpringBoot");
- // return session.createObjectMessage(order);
- // }
- // }
- // );
-
- // //方式2
- // jmsTemplate.send("wdhqueue",
- // new MessageCreator() {
- // @Override
- // public Message createMessage(Session session) throws JMSException {
- // //return session.createObjectMessage("Hello from wdhSpringBoot");
- // return session.createObjectMessage(order);
- // }
- // }
- // );
-
-
-
- // //方式3
- // jmsTemplate.convertAndSend("wdhqueue",order);
-
-
- //方式3
- jmsTemplate.convertAndSend("wdhqueue",order,this::addOrderSource);
-
- log.info("JmsOrderMessagingService start sendOrder end.");
- }
-
- private Message addOrderSource(Message message) throws JMSException{
- message.setStringProperty("X_ORDER_SOURCE","WEB");
- return message;
- }
- }
- package com.wdh.tacocloud_jms.api;
-
- import com.wdh.tacocloud_jms.data.OrderRepository;
- import com.wdh.tacocloud_jms.domain.Order;
- import com.wdh.tacocloud_jms.messaging.OrderMessagingService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.dao.EmptyResultDataAccessException;
- import org.springframework.http.HttpStatus;
- import org.springframework.web.bind.annotation.*;
-
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.Optional;
-
- /**
- * @author WangDH
- * @create 2022-09-23 9:55
- */
- @Slf4j
- @RestController
- @RequestMapping(path="/orders",produces = "application/json")
- @CrossOrigin(origins = "*")
- public class OrderApiController {
-
- private OrderRepository orderRepo;
- private OrderMessagingService orderMessagingService;
-
-
- public OrderApiController(OrderRepository orderRepo
- ,OrderMessagingService orderMessagingService
- ) {
- this.orderMessagingService=orderMessagingService;
- this.orderRepo = orderRepo;
- }
-
- @GetMapping("/")
- public String getDefault(){
-
- log.info("######### OrderApiController enter getDefault");
-
-
- return "ok,this is OrderApiController getDefault return";
- }
-
- @GetMapping("/sendJMS")
- public String sendJMS(){
-
- log.info("######### OrderApiController enter sendJMS");
-
- Date date=new Date();
- SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String time=formatter.format(date);
-
-
- Order order=new Order();
- order.setId((long) 81);
- order.setDeliveryName("jmsTestName"+time);
- orderMessagingService.sendOrder(order);
-
- return "ok,this is OrderApiController sendJMS return。time="+time;
- }
-
-
- }
要点1:【推模型】OrderListener和【拉模型】JmsOrderReceiver互斥,二者代码只能选择一个
要点2:如果同时使用【推模型】OrderListener和【拉模型】JmsOrderReceiver,则【推模型】OrderListener会优先截取队列的消息,导致【拉模型】JmsOrderReceiver接收到的数据为空。
要点3:【拉模型】类JmsOrderReceiver的receiveOrder()方法中的jmsTemplate.receiveAndConvert()会阻塞直到发送端有数据发送。
消息接收端程序采用SpringMVC+Thymeleaf
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-artemis</artifactId>
- </dependency>
- #ActiveMQ Artemis 配置
- #ActiveMQ Artemis broker主机
- spring.artemis.host=localhost
- #ActiveMQ Artemis broker端口
- spring.artemis.port=61616
- #ActiveMQ Artemis broker 访问用户名
- spring.artemis.user=admin
- #ActiveMQ Artemis broker 访问密码
- spring.artemis.password=admin
- #指定jms发送消息的目的地(主体)
- spring.jms.template.default-destination=wdhqueue
- package com.wdh.tacocloud_kitchen.kitchen.messaging.jms;
-
- import com.wdh.tacocloud_kitchen.OrderReceiver;
- import com.wdh.tacocloud_kitchen.domain.Order;
- import lombok.extern.slf4j.Slf4j;
- 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.JMSException;
- import javax.jms.Message;
- import java.util.Date;
-
- /**
- * @author WangDH
- * @create 2022-09-27 17:21
- * 注意,这种方式是[拉模型]
- */
-
- @Slf4j
- @Component
- public class JmsOrderReceiver implements OrderReceiver {
-
- // //方式1
- private JmsTemplate jmsTemplate;
- private MessageConverter messageConverter;
-
- @Autowired
- public JmsOrderReceiver(JmsTemplate jmsTemplate, MessageConverter messageConverter) {
- this.jmsTemplate = jmsTemplate;
- this.messageConverter = messageConverter;
- }
-
- @Override
- public Order receiveOrder() throws JMSException {
-
- Message message= jmsTemplate.receive("wdhqueue");
- Order order=(Order)messageConverter.fromMessage(message);
-
- return order;
- }
-
-
- //方式2
- private JmsTemplate jmsTemplate;
-
- @Autowired
- public JmsOrderReceiver(JmsTemplate jmsTemplate) {
- this.jmsTemplate = jmsTemplate;
- }
-
- @Override
- public Order receiveOrder() {
-
- jmsTemplate.setReceiveTimeout(10000);//设置10000ms的等待时间,防止receiveAndConvert阻塞
-
- log.info("JmsOrderReceiver receiveOrder,准备接收队列消息,请在队列发送端发送消息。 receiveAndConvert start at "+(new Date()).toString());
-
- Order order=(Order)jmsTemplate.receiveAndConvert("wdhqueue");
-
- log.info("JmsOrderReceiver receiveOrder receiveAndConvert end at "+(new Date()).toString());
- return order;
- }
- }
- package com.wdh.tacocloud_kitchen.kitchen.messaging.jms.listener;
-
- import com.wdh.tacocloud_kitchen.KitchenUI;
- import com.wdh.tacocloud_kitchen.domain.Order;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jms.annotation.JmsListener;
- import org.springframework.stereotype.Component;
-
- /**
- * @author WangDH
- * @create 2022-09-27 17:33
- *
- * 建立监听器以配合消息队列实现【推模型】
- *
- * 注意,这个与JmsOrderReceiver的拉模型互斥,二者代码只能选择一个
- * 如果同时使用【推模型】OrderListener和【拉模型】JmsOrderReceiver,
- * 则【推模型】OrderListener会优先截取队列的消息,导致【拉模型】JmsOrderReceiver接收到的数据为空
- */
- @Slf4j
- @Component
- public class OrderListener {
-
- private KitchenUI kitchenUI;
-
- @Autowired
- public OrderListener(KitchenUI kitchenUI) {
- this.kitchenUI = kitchenUI;
- }
-
- @JmsListener(destination = "wdhqueue")
- public void receiveOrder(Order order){
-
- kitchenUI.displayOrder(order);
-
- }
-
- }
1.保证ActiveMQ artemis正在运行中
2.启动发送端springboot程序TacocloudJmsApplication(端口8080)
3.启动接收端springboot程序TacocloudKitchenApplication(端口8081)
4.打开浏览器输入【http://localhost:8090/orders/sendJMS】以调用发送端程序的rest服务,回车后因spring security要求先输入用户名user,再根据idea中提示的密码复制过来再回车,登录后,网页提示消息已发送。
5.接收端【拉模式】,在发送端已发送消息后, 打开浏览器输入【http://localhost:8081/orders/receive】回车会跳转到receiveOrder.html显示相应信息。(如果再次发送消息,则需要手动刷新浏览器以再次拉取消息)
6.接收端【推模式】,在发送端已发送消息后,在IDEA的控制台中即可看到TacocloudKitchenApplication程序输出的日志显示OrderListener已收到消息。