JMS 在 Spring Boot 中的使用
當前環境
- Mac OS 10.11.x
- docker 1.12.1
- JDK 1.8
- SpringBoot 1.5
前言
基於之前一篇“一個故事告訴你什麼是訊息佇列”,瞭解了訊息佇列的使用場景以及相關的特性。本文主要講述訊息服務在 JAVA 中的使用。
市面上的有關訊息佇列的技術選型非常多,如果我們的程式碼框架要支援不同的訊息實現,在保證框架具有較高擴充套件性的前提下,我們勢必要進行一定的封裝。
在 JAVA 中,大可不必如此。因為 JAVA 已經制定了一套標準的 JMS 規範。該規範定義了一套通用的介面和相關語義,提供了諸如持久、驗證和事務的訊息服務,其最主要的目的是允許Java應用程式訪問現有的訊息中介軟體。就和 JDBC 一樣。
基本概念
在介紹具體的使用之前,先簡單介紹一下 JMS 的一些基本知識。這裡我打算分為 3 部分來介紹,即 訊息佇列(MQ)的連線、訊息傳送與訊息接收。
這裡我們的技術選型是 SpringBoot、JMS、ActiveMQ
為了更好的理解 JMS,這裡沒有使用 SpringBoot 零配置來搭建專案
MQ 的連線
使用 MQ 的第一步一定是先連線 MQ。因為這裡使用的是 JMS 規範,對於任何遵守 JMS 規範的 MQ 來說,都會實現相應的ConnectionFactory
介面,因此我們只需要建立一個ConnectionFactory
工廠類,由它來實現 MQ 的連線,以及封裝一系列特性的 MQ 引數。
例子:這裡我們以 ActiveMQ 為例,
maven 依賴:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> </dependencies>
建立 ActiveMQ 連線工廠:
@Bean public ConnectionFactory connectionFactory(){ ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(); connectionFactory.setBrokerURL(ActiveMQ_URL); connectionFactory.setUserName(ActiveMQ_USER); connectionFactory.setPassword(ActiveMQ_PASSWORD); return connectionFactory; }
訊息傳送
關於訊息的傳送,是通過 JMS 核心包中的JmsTemplate
類來實現的,它簡化了 JMS 的使用,因為在傳送或同步接收訊息時它幫我們處理了資源的建立和釋放。從它的作用也不難推測出,它需要引用我們上面建立的連線工廠,具體程式碼如下:
@Bean public JmsTemplate jmsQueueTemplate(){ return new JmsTemplate(connectionFactory()); }
JmsTemplate
建立完成後,我們就可以呼叫它的方法來傳送訊息了。這裡有兩個概念需要注意:
- 訊息會傳送到哪裡?-> 即需要指定傳送佇列的目的地(Destination),是可以在 JNDI 中進行儲存和提取的 JMS 管理物件。
- 傳送的訊息體具體是什麼?-> 實現了
javax.jms.Message
的物件,類似於 JAVA RMI 的 Remote 物件。
程式碼示例:
@Autowired private JmsTemplate jmsQueueTemplate; /** * 傳送原始訊息 Message */ public void send(){ jmsQueueTemplate.send("queue1", new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage("我是原始訊息"); } }); }
優化:當然,我們不用每次都通過MessageCreator
匿名類的方式來建立Message
物件,JmsTemplate
類中提供了物件實體自動轉換為Message
物件的方法,convertAndSend(String destinationName, final Object message)
。
優化程式碼示例:
/** * 傳送訊息自動轉換成原始訊息 */ public void convertAndSend(){ jmsQueueTemplate.convertAndSend("queue1", "我是自動轉換的訊息"); }
注:關於訊息轉換,還可以通過實現MessageConverter
介面來自定義轉換內容
訊息接收
講完了訊息傳送,我們最後來說說訊息是如何接收的。訊息既然是以Message
物件的形式傳送到指定的目的地,那麼訊息的接收勢必會去指定的目的地上去接收訊息。這裡採用的是監聽者的方式來監聽指定地點的訊息,採用註解@JmsListener
來設定監聽方法。
程式碼示例:
@Component public class Listener1 { @JmsListener(destination = "queue1") public void receive(String msg){ System.out.println("監聽到的訊息內容為: " + msg); } }
有了監聽的目標和方法後,監聽器還得和 MQ 關聯起來,這樣才能運作起來。這裡的監聽器可能不止一個,如果每個都要和 MQ 建立連線,肯定不太合適。所以需要一個監聽容器工廠的概念,即介面JmsListenerContainerFactory
,它會引用上面建立好的與 MQ 的連線工廠,由它來負責接收訊息以及將訊息分發給指定的監聽器。當然也包括事務管理、資源獲取與釋放和異常轉換等。
程式碼示例:
@Bean public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory()); //設定連線數 factory.setConcurrency("3-10"); //重連間隔時間 factory.setRecoveryInterval(1000L); return factory; }
場景
程式碼地址:https://github.com/jasonGeng88/springboot-jms
對 JMS 有了基本的理解後,我們就來在具體的場景中使用一下。
首先,我們需要先啟動 ActiveMQ,這裡我們以 Docker 容器化的方式進行啟動。
啟動命令:
docker run -d -p 8161:8161 -p 61616:61616 --name activemq webcenter/activemq
啟動成功後,在 ActiveMQ 視覺化介面檢視效果(http://localhost:8161):
點對點模式(單消費者)
下面介紹訊息佇列中最常用的一種場景,即點對點模式。基本概念如下:
- 每個訊息只能被一個消費者(Consumer)進行消費。一旦訊息被消費後,就不再在訊息佇列中存在。
- 傳送者和接收者之間在時間上沒有依賴性,也就是說當傳送者傳送了訊息之後,不管接收者有沒有正在執行,它不會影響到訊息被髮送到佇列。
- 接收者在成功接收訊息之後需向佇列應答成功。
程式碼實現(為簡化程式碼,部分程式碼沿用上面所述):
啟動檔案(Application.java)
@SpringBootApplication @EnableJms public class Application { ... /** * JMS 佇列的模板類 * connectionFactory() 為 ActiveMQ 連線工廠 */ @Bean public JmsTemplate jmsQueueTemplate(){ return new JmsTemplate(connectionFactory()); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
註解@EnableJms
設定在@Configuration
類上,用來宣告對 JMS 註解的支援。
訊息生產者(PtpProducer.java)
@Component public class PtpProducer { @Autowired private JmsTemplate jmsQueueTemplate; /** * 傳送訊息自動轉換成原始訊息 */ public void convertAndSend(){ jmsQueueTemplate.convertAndSend("ptp", "我是自動轉換的訊息"); } }
生產者呼叫類(PtpController.java)
@RestController @RequestMapping(value = "/ptp") public class PtpController { @Autowired private PtpProducer ptpProducer; @RequestMapping(value = "/convertAndSend") public Object convertAndSend(){ ptpProducer.convertAndSend(); return "success"; } }
訊息監聽容器工廠
@SpringBootApplication @EnableJms public class Application { ... /** * JMS 佇列的監聽容器工廠 */ @Bean(name = "jmsQueueListenerCF") public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory()); //設定連線數 factory.setConcurrency("3-10"); //重連間隔時間 factory.setRecoveryInterval(1000L); return factory; } ... }
訊息監聽器
@Component public class PtpListener1 { /** * 訊息佇列監聽器 * destination 佇列地址 * containerFactory 監聽器容器工廠, 若存在2個以上的監聽容器工廠,需進行指定 */ @JmsListener(destination = "ptp", containerFactory = "jmsQueueListenerCF") public void receive(String msg){ System.out.println("點對點模式1: " + msg); } }
演示
啟動專案啟動後,通過 REST 介面的方式來呼叫訊息生產者傳送訊息,請求如下:
curl -XGET 127.0.0.1:8080/ptp/convertAndSend
消費者控制檯資訊:
ActiveMQ 控制檯資訊:
列表說明:
- Name:佇列名稱。
- Number Of Pending Messages:等待消費的訊息個數。
- Number Of Consumers:當前連線的消費者數目,因為我們採用的是連線池的方式連線,初始連線數為 3,所以顯示數字為 3。
- Messages Enqueued:進入佇列的訊息總個數,包括出佇列的和待消費的,這個數量只增不減。
- Messages Dequeued:出了佇列的訊息,可以理解為是已經消費的訊息數量。
點對點模式(多消費者)
基於上面一個消費者消費的模式,因為生產者可能會有很多,同時像某個佇列傳送訊息,這時一個消費者可能會成為瓶頸。所以需要多個消費者來分攤消費壓力(消費執行緒池能解決一定壓力,但畢竟在單機上,做不到分散式分佈,所以多消費者是有必要的),也就產生了下面的場景。
程式碼實現
新增新的監聽器
@Component public class PtpListener2 { @JmsListener(destination = Constant.QUEUE_NAME, containerFactory = "jmsQueueListenerCF") public void receive(String msg){ System.out.println("點對點模式2: " + msg); } }
演示
這裡我們發起 10 次請求,來觀察消費者的消費情況:
這裡因為監聽容器設定了執行緒池的緣故,在實際消費過程中,監聽器消費的順序會有所差異。
釋出訂閱模式
除了點對點模式,釋出訂閱模式也是訊息佇列中常見的一種使用。試想一下,有一個即時聊天群,你在群裡傳送一條訊息。所有在這個群裡的人(即訂閱了該群的人),都會收到你傳送的資訊。
基本概念:
- 每個訊息可以有多個消費者。
- 釋出者和訂閱者之間有時間上的依賴性。針對某個主題(Topic)的訂閱者,它必須建立一個訂閱者之後,才能消費釋出者的訊息。
- 為了消費訊息,訂閱者必須保持執行的狀態。
程式碼實現
修改 JmsTemplate 模板類,使其支援釋出訂閱功能
@SpringBootApplication @EnableJms public class Application { ... @Bean public JmsTemplate jmsTopicTemplate(){ JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory()); jmsTemplate.setPubSubDomain(true); return jmsTemplate; } ... }
訊息生產者(PubSubProducer.java)
@Component public class PtpProducer { @Autowired private JmsTemplate jmsTopicTemplate; public void convertAndSend(){ jmsTopicTemplate.convertAndSend("topic", "我是自動轉換的訊息"); } }
生產者呼叫類(PubSubController.java)
@RestController @RequestMapping(value = "/pubsub") public class PtpController { @Autowired private PubSubProducer pubSubProducer; @RequestMapping(value = "/convertAndSend") public String convertAndSend(){ pubSubProducer.convertAndSend(); return "success"; } }
修改 DefaultJmsListenerContainerFactory 類,使其支援釋出訂閱功能
@SpringBootApplication @EnableJms public class Application { ... /** * JMS 佇列的監聽容器工廠 */ @Bean(name = "jmsTopicListenerCF") public DefaultJmsListenerContainerFactory jmsTopicListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory()); factory.setConcurrency("1"); factory.setPubSubDomain(true); return factory; } ... }
訊息監聽器(這裡設定2個訂閱者)
@Component public class PubSubListener1 { @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF") public void receive(String msg){ System.out.println("訂閱者1 - " + msg); } } @Component public class PubSubListener2 { @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF") public void receive(String msg){ System.out.println("訂閱者2 - " + msg); } }
演示
curl -XGET 127.0.0.1:8080/pubSub/convertAndSend
消費者控制檯資訊:
ActiveMQ 控制檯資訊:
總結
這裡只是對 SpringBoot 與 JMS 整合的簡單說明與使用,詳細的介紹可以檢視 Spring 的官方文件,我這裡也有幸參與 併發程式設計網 發起的 Spring 5 的翻譯工作,我主要翻譯了 Spring 5 的 JMS 章節,其內容對於上述 JMS 的基本概念,都有詳細的展開說明,有興趣的可以看一下,當然翻譯水平有限,英文好的建議看原文。
相關文章
- 如何提高在Spring Boot中使用MQ JMS的效率 -Mark TaylorSpring BootMQ
- Spring Boot(十一):Spring Boot 中 MongoDB 的使用Spring BootMongoDB
- Spring Boot(三):Spring Boot 中 Redis 的使用Spring BootRedis
- 透過Docker啟動Solace,並在Spring Boot透過JMS整合SolaceDockerSpring Boot
- 在 Spring Boot 中使用 RedisSpring BootRedis
- spring boot中zookeeper使用Spring Boot
- spring boot中redis使用Spring BootRedis
- Spring Boot中Dockerfile使用Spring BootDocker
- 在spring boot3中使用native imageSpring Boot
- 在Spring Boot框架中使用AOPSpring Boot框架
- 在spring boot專案(maven)中引入其他 spring boot專案Spring BootMaven
- Spring Boot中攔截器的使用Spring Boot
- Spring Boot(五):Spring Boot Jpa 的使用Spring Boot
- 在Java Spring Boot中的Akka流! -Lalit VatsalJavaSpring Boot
- 在Spring Boot中建立自己的啟動器Spring Boot
- 在Spring Boot中禁用CSRF保護的原因?Spring Boot
- EVCache快取在 Spring Boot中的實戰快取Spring Boot
- 關於在專案中使用spring3的jms出現的問題。Spring
- 嵌入式Redis伺服器在Spring Boot測試中的使用Redis伺服器Spring Boot
- 在 Spring Boot 中使用 JPA 和 MySQLSpring BootMySql
- Spring Boot(三):Spring Boot中的事件的使用 與Spring Boot啟動流程(Event 事件 和 Listeners監聽器)Spring Boot事件
- AspectJ 在 Spring 中的使用Spring
- 在 Spring Boot 中使用 HikariCP 連線池Spring Boot
- 在 Spring Boot 中使用搜尋引擎 ElasticsearchSpring BootElasticsearch
- springboot(十一):Spring boot中mongodb的使用Spring BootMongoDB
- springboot(三):Spring boot中Redis的使用Spring BootRedis
- Spring Boot中@Import三種使用方式!Spring BootImport
- 值得使用的Spring BootSpring Boot
- Spring Boot(十八):使用 Spring Boot 整合 FastDFSSpring BootAST
- Mybatis在Spring中的使用(三)MyBatisSpring
- 在 Kubernetes 上使用Spring Boot+ActiveMQSpring BootMQ
- Spring + JTA + JPA + JMSSpring
- Spring Boot 2.0(四):使用 Docker 部署 Spring BootSpring BootDocker
- Spring Boot(十六):使用 Jenkins 部署 Spring BootSpring BootJenkins
- 如何使用Spring Boot的ProfilesSpring Boot
- 使用Intellij中的Spring Initializr來快速構建Spring Boot工程IntelliJSpring Boot
- 使用DataSource-Proxy在Spring Boot中記錄SQL語句 - Vlad MihalceaSpring BootSQL
- 在Spring Boot中實現OAuth2.0認證Spring BootOAuth