我與ActiveMQ的恩怨情仇
概要
爲什麼寫道我與ActiveMQ的恩怨情仇 ,其實這一切緣於最初對ActiveMQ的學習和應用…….
ActiveMQ 介紹
ActiveMQ 是Apache出品,最流行的,能力強勁的開源消息總線。ActiveMQ 是一個完全支持JMS1.1和J2EE 1.4規範的 JMS Provider實現,儘管JMS規範出臺已經是很久的事情了,但是JMS在當今的J2EE應用中間仍然扮演着特殊的地位。
ActiveMQ特性
⒈ 多種語言和協議編寫客戶端。語言: Java,C,C++,C#,Ruby,Perl,Python,PHP。應用協議: OpenWire,Stomp REST,WS Notification,XMPP,AMQP
⒉ 完全支持JMS1.1和J2EE 1.4規範 (持久化,XA消息,事務)
⒊ 對spring的支持,ActiveMQ可以很容易內嵌到使用spring的系統裏面去,而且也支持Spring2.0的特性
⒋ 通過了常見J2EE服務器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的測試,其中通過JCA 1.5 resource adaptors的配置,可以讓ActiveMQ可以自動的部署到任何兼容J2EE 1.4 商業服務器上
⒌ 支持多種傳送協議:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
⒍ 支持通過JDBC和journal提供高速的消息持久化
⒎ 從設計上保證了高性能的集羣,客戶端-服務器,點對點
⒏ 支持Ajax
⒐ 支持與Axis的整合
⒑ 可以很容易得調用內嵌JMS provider,進行測試
乾貨開始
ActiveMQ組成
其實ActiveMQ的組成和其他消息隊列類似,或者基本相同,都有生產者生產消息,用於存消息的隊列(Queue)或者(Topics)。然後還有從隊列裏取消息的消費者。
基礎版的消費者和生產者
- 生產者
package com.hzfh.activiemq.service;
import com.alibaba.fastjson.JSONObject;
import com.hzfh.entity.MessageTest;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.stereotype.Component;
import javax.jms.*;
/**
* com.hzfh.activiemq.service
*
* @author rencc
* @Note 生產者發送消息 TextMessage
* @Date 2017-08-22 17:23
*/
@Component
public class ProducerModel {
private static final String url="tcp://127.0.0.1:61616";
private static final String queueName="2017-08-23";
public void sendMessage(MessageTest messageTest) throws JMSException {
//1,創建ConnectionFacytory
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
//2,創建連接Connection
Connection connection = connectionFactory.createConnection();
//3,啓動鏈接
connection.start();
//4創建會話
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5,創建一個目標
Destination destination = session.createQueue(queueName);
//6,創建一個生產者
MessageProducer producer = session.createProducer(destination);
//7,創建消息
TextMessage textMessage = session.createTextMessage(JSONObject.toJSONString(messageTest));
textMessage.setIntProperty("id",messageTest.getId());//帶"過濾"的消息選擇器
//8發佈消息
producer.send(textMessage);
//9,關閉連接
connection.close();
}
}
- 消費者
package com.hzfh.activiemq.service;
import com.alibaba.fastjson.JSONObject;
import com.hzfh.entity.MessageTest;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.stereotype.Component;
import javax.jms.*;
/**
* com.hzfh.activiemq.service
*
* @author rencc
* @Note 消費者消費發送消息 TextMessage
* @Date 2017-08-22 17:29
*/
@Component
public class ConsumerModel {
private static final String url="tcp://127.0.0.1:61616";
private static final String queueName="2017-08-22";
public MessageTest createCustomer(int id) throws JMSException, InterruptedException {
MessageTest messageTest = new MessageTest();
//1,創建ConnectionFacytory
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
//2,創建連接Connection
Connection connection = connectionFactory.createConnection();
//3,啓動鏈接
connection.start();
//4創建會話
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5,創建一個目標
Destination destination = session.createQueue(queueName);
//6 創建一個消費者,消息過濾選擇消費
MessageConsumer consumer = session.createConsumer(destination,"id ="+id);
//7創建一個監聽器
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
JSONObject parse = JSONObject.parseObject(textMessage.getText());
messageTest = JSONObject.toJavaObject(parse, MessageTest.class);
} catch (JMSException e) {
e.printStackTrace();
}
}
});
//8,關閉連接
connection.close();
return messageTest;
}
}
升級版生產者和消費者
所謂升級版,其實是ActiveMQ集成Spring Boot後,採用註解配置後監聽器的方式來實現,生產者生產消息,消費者監聽消費的隊列。(實時消費)
- Spring Boot 註解配置(application.properties)
spring.activemq.broker-url=tcp://localhost:61616
- 生產消息類
package com.hzfh.activiemq;
import com.alibaba.fastjson.JSONObject;
import com.hzfh.entity.MessageTest;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
import javax.jms.*;
/**
* com.hzfh.activiemq
*
* @author rencc
* @Note 生產消息的類
* @Date 2017-08-22 11:18
*/
public class CreateMessage implements MessageCreator{
@Override
public Message createMessage(Session session) throws JMSException {
MessageTest message = new MessageTest();
message.setId(110);
message.setMessage("測試Object");
String json = JSONObject.toJSONString(message);
return session.createTextMessage(json);
}
}
- 生產發送消息實現
僅僅是爲了實現效果,我在Spring Boot的啓動類裏直接使用發消息。
package com.hzfh;
import com.hzfh.activiemq.CreateMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jms.core.JmsTemplate;
@SpringBootApplication
public class WebsocketApplication implements CommandLineRunner {
@Autowired
JmsTemplate jmsTemplate;
public static void main(String[] args) {
SpringApplication.run(WebsocketApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
jmsTemplate.send("test",new CreateMessage());//test 爲隊列名
}
}
- 消息監聽器
package com.hzfh.activiemq;
import com.alibaba.fastjson.JSONObject;
import com.hzfh.entity.MessageTest;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* com.hzfh.activiemq
*
* @author rencc
* @Note 消息隊列監聽器
* @Date 2017-08-22 11:26
*/
@Component
public class Receiver {
private static final String destination="test";//要監聽的隊列名
@JmsListener(destination = destination)
public void receiveMessage(String message){
JSONObject parse = JSONObject.parseObject(message);
MessageTest messageTest = JSONObject.toJavaObject(parse, MessageTest.class);
System.out.println("接收到:"+messageTest.toString());
}
}
恩怨情仇
以上內容都是乾貨,自此之後開始嘮叨,我與ActiveMQ的恩怨情仇。
研究ActiveMQ是因爲,公司開發的基於Spring Boot架構的企業後臺管理系統,在工作流的待審待辦消息那兒,沒有實現實時的消息提醒,以至於用戶每次查看新消息都需要F5,用戶體驗性差。所以前幾天接到任務,實現公司現有系統的待審待辦消息實時提醒功能。
百度相關內容之後,發現實現方式要麼Ajax輪詢或者藉助消息中間件。至此決定採用ActiveMQ消息隊列。初步研究後,使用SpringBoot搭建一個Demo實現了後臺生產消息,監聽器消費消息。但是我需要的是要實現瀏覽器實時的更新提醒消息。所以深入研究之後,從官網上找到了ajax方式監聽消息隊列的方式。可就在此時,難題來了,ajax方式需要配置servlet來後臺處理,官網所有文檔指出的都是在web.xml裏配置servlet。
<!-- 配置支持ajax的jms -->
<context-param>
<param-name>org.apache.activemq.brokerURL</param-name>
<param-value>tcp://localhost:61616</param-value>
<description>連接到消息中間件的URL</description>
</context-param>
<servlet>
<servlet-name>AjaxServlet</servlet-name>
<servlet-class>org.apache.activemq.web.AjaxServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AjaxServlet</servlet-name>
<url-pattern>/amq/*</url-pattern>
</servlet-mapping>
但是SpringBoot是沒有web.xml的,後來就去研究SpringBoot註冊Servlet的方式,使用註解也好,還是在配置類裏註冊也好,都是不行。哎,頭疼。
- 註冊方式
package org.springboot.sample;
import org.springboot.sample.servlet.MyServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
public class SpringBootSampleApplication {
/**
* 使用代碼註冊Servlet
*
* @return
* @author SHANHY
* @create 2016年1月6日
*/
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new MyServlet(), "/amp/*");
}
public static void main(String[] args) {
SpringApplication.run(SpringBootSampleApplication.class, args);
}
}
但是還需要再MyServlet類前添加
@WebServlet(urlPatterns=”/xs/*”, description=”Servlet的說明”)
孰不知我要配置的是官方jar包裏的類,這怎麼加註解….後來打算,大不了重寫官方給的AjaxServlet.不管怎麼樣,重寫肯定OK的。
記得決定這樣做當時已經是下班了,回家的路上,我在10號線地鐵上反覆思考,假如這種方式可以,那如何保證隊列裏的消息和數據庫待審待辦表裏的數據永遠保持一致呢?隊列裏添加消息容易,但是刪除呢?思考良久發現不可行。至此,我兩天的研究的時間白費了。前期考察不充分,血淋林赤裸裸的教訓。
第三天痛定思痛,採用ajax輪詢實現。
寫在最後
ActiveMQ應用於異步消息同步,多用於系統之間消息同步,減緩用戶延遲等場景。