因爲最近開發了一個後臺系統中有推送消息的功能,最開始的做法是在後臺系統一個表單頁面填寫推送信息,渠道等,點擊提交完成推送。
單人或者單渠道推送的時候後臺系統直接完成推送,返回成功到頁面。後來渠道增加或者接受人從單個對象變成集合的時候系統就出問題了,頁面會一直卡在程序運行後纔會跳轉。
由於趕進度且只是爲了完成需求,當時就直接new了一個線程去完成推送,主方法不用等結果直接返回成功,然後線程完成推送後去做異步處理。後來沒事幹的時候想起來這一塊覺得添加一個序列比較合適,就有了接下來的記錄。
關於activemq、rabbitmq、kafka區別和介紹請閱讀
https://blog.csdn.net/lifaming15/article/details/79942793
一:springmvc集成activemq
本人框架是基於maven的springMVC框架
1:添加jar包
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.11.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.0.8.RELEASE</version>
</dependency>
這裏的spring-jms的版本一定要和系統中spring的版本一致,剛開始沒有注意到這個問題,是直接看jar包拷貝的maven依賴,後來系統啓動一致報什麼注入失敗的問題,找了好久才知道是版本衝突。
2:添加spring配置文件spring-context-jms.xml
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="${activemq.host}" />
</bean>
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="connectionFactory" ref="targetConnectionFactory" />
<property name="maxConnections" value="10" />
</bean>
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="pooledConnectionFactory" />
</bean>
<!--這個是隊列目的地,點對點的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>queue1</value>
</constructor-arg>
</bean>
<!--這個是主題目的地,一對多的
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
-->
<!-- 定義監聽消息隊列(Queue),queue2-->
<bean id="messageQueueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 設置消息隊列的名字 -->
<constructor-arg>
<value>queue2</value>
</constructor-arg>
</bean>
<!-- 配置消息隊列監聽者(Queue),代碼下面給出,只有一個onMessage方法 -->
<bean id="queueMessageListener" class="com.jeeplus.modules.jms.listener.QueueMessageListener" />
<!-- 消息監聽容器(Queue),配置連接工廠,監聽的隊列是queue2,監聽器是上面定義的監聽器 ,系統裏直接使用監聽 -->
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="messageQueueDestination" />
<property name="messageListener" ref="queueMessageListener" />
</bean>
<!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<property name="connectionFactory" ref="connectionFactory" />
<!-- 消息轉換器
<property name="messageConverter" ref="objConverter"/>
-->
<property name="defaultDestination" ref="queueDestination" />
<property name="receiveTimeout" value="10000" />
</bean>
<!-- 類型轉換器
<bean id="objConverter" class="com.jeeplus.modules.jms.converter.ObjectConverter"/>
-->
<!--queue消息生產者 -->
<bean id="producterService" class="com.jeeplus.modules.jms.service.ProducterService">
<property name="jmsTemplate" ref="jmsTemplate"></property>
</bean>
<!--queue消息消費者 -->
<bean id="consumerService" class="com.jeeplus.modules.jms.service.ConsumerService">
<property name="jmsTemplate" ref="jmsTemplate"></property>
</bean>
上邊註釋寫的其實已經很詳細了。配置連接,定義序列,配置spring工具,配置監聽,大概就可以了。
3:java代碼實現
首先在系統中其實用到的沒有消費者,因爲我的消費者其實就是監聽,處理邏輯在監聽中實現了。vo文件夾可以自定義object類型。
監聽QueueMessageListener:
package com.jeeplus.modules.jms.listener;
import com.jeeplus.modules.sms.common.utils.PushMsgUtil;
import com.jeeplus.modules.sms.sendmessage.entity.MessageSend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.*;
public class QueueMessageListener implements MessageListener {
protected Logger logger = LoggerFactory.getLogger(getClass());
//當收到消息時,自動調用該方法。
public void onMessage(Message message) {
MessageSend tm = new MessageSend();
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
try {
tm = (MessageSend) objectMessage.getObject();
System.out.println("ConsumerMessageListener收到了自定義消息:\t"
+ tm);
logger.info("ConsumerMessageListener收到了自定義消息:\t"
+ tm);
PushMsgUtil.pushMsg(tm);
} catch (JMSException e) {
e.printStackTrace();
}
}
if (message instanceof TextMessage) {
TextMessage tx = (TextMessage) message;
try {
System.out.println("ConsumerMessageListener收到了文本消息:\t"
+ tx.getText());
logger.info("ConsumerMessageListener收到了文本消息:\t"
+ tx.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
生產者ProducterService:
package com.jeeplus.modules.jms.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import java.io.Serializable;
/**
* Created by huiyunfei on 2017/7/20.
*/
public class ProducterService {
protected Logger logger = LoggerFactory.getLogger(getClass());
private JmsTemplate jmsTemplate;
/**
* 向指定隊列發送消息
*/
public void sendMessage(Destination destination, final String msg) {
System.out.println("向隊列" + destination.toString() + "發送了消息------------" + msg);
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(msg);
}
});
}
/**
* 向默認隊列發送消息
*/
public void sendMessage(final String msg) {
String destination = jmsTemplate.getDefaultDestination().toString();
System.out.println("向隊列" +destination+ "發送了消息------------" + msg);
jmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(msg);
}
});
}
/**
* 向指定隊列發送自定義消息
*/
public void sendMessage(Destination destination, final Object msg) {
System.out.println("向隊列" + destination.toString() + "發送了消息------------" + msg);
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage((Serializable) msg);
//return session.createTextMessage(ObjectConverter.class.);
}
});
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
}
消費者:
package com.jeeplus.modules.jms.service;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.TextMessage;
/**
* Created by huiyunfei on 2017/7/20.
*/
public class ConsumerService {
private JmsTemplate jmsTemplate;
/**
* 接受消息
*/
public void receive(Destination destination) {
TextMessage tm = (TextMessage) jmsTemplate.receive(destination);
try {
System.out.println("從隊列" + destination.toString() + "收到了消息:\t"
+ tm.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
}
4:測試類
package com.jeeplus.modules.jms;
import com.jeeplus.modules.jms.service.ConsumerService;
import com.jeeplus.modules.jms.service.ProducterService;
import com.jeeplus.modules.jms.vo.TestMessage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import javax.jms.Destination;
/**
* Created by huiyunfei on 2017/7/20.
*/
/**
* @Description: activemq消息隊列測試
* @Author:huiyunfei
* @Date: 2017/7/21
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {
"classpath:spring-context.xml"})
public class SpringJmsTest {
/**
* 隊列名queue1
*/
@Autowired
private Destination queueDestination;
/**
* 隊列名queue2
*/
@Autowired
private Destination messageQueueDestination;
/**
* 隊列消息生產者
*/
@Autowired
@Qualifier("producterService")
private ProducterService producer;
/**
* 隊列消息消費者
*/
@Autowired
@Qualifier("consumerService")
private ConsumerService consumer;
/**
* 測試生產者向queue1發送消息
*/
@Test
public void testProduce() {
String msg = "Hello world!";
producer.sendMessage(msg);
}
/**
* 測試消費者從queue1接受消息
*/
@Test
public void testConsume() {
consumer.receive(queueDestination);
}
/**
* 測試消息監聽
*
* 1.生產者向隊列queue2發送消息
*
* 2.ConsumerMessageListener監聽隊列,並消費消息
*/
@Test
public void testSend() {
producer.sendMessage(messageQueueDestination, "Hello China~~~~~~~~~~~~~~~");
}
/**
* 測試自定義消息監聽
*
* 1.生產者向隊列queue2發送消息
*
* 2.ConsumerMessageListener監聽隊列,並消費消息
*/
@Test
public void testCustomSend() {
TestMessage testMessage=new TestMessage();
testMessage.setId("1");
testMessage.setUser("yunfei");
testMessage.setTitle("title");
testMessage.setContent("----------hello test message----------");
producer.sendMessage(messageQueueDestination, testMessage);
}
}
二:springboot集成rabbitmq
配置文件:
redis:
host: 127.0.0.1
password:
port: 6379
maxWaitMillis: 10000
maxTotal: 200
testOnBorrow: true
testOnReturn: true
timeout: 3600
maxIdle: 200
pom:
<!-- 添加springboot對amqp的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
rabbitmq三種模式:
Direct是RabbitMQ默認的交換機模式,也是最簡單的模式.即創建消息隊列的時候,指定一個BindingKey.當發送者發送消息的時候,指定對應的Key.當Key和消息隊列的BindingKey一致的時候,消息將會被髮送到該消息隊列中.
topic轉發信息主要是依據通配符,隊列和交換機的綁定主要是依據一種模式(通配符+字符串),而當發送消息的時候,只有指定的Key和該模式相匹配的時候,消息纔會被髮送到該消息隊列中.
Fanout是路由廣播的形式,將會把消息發給綁定它的全部隊列,即便設置了key,也會被忽略.
測試:
配置文件:
#我測試的時候這個配置文件壓根就沒有添加,也能正常使用,rabbitmq應該是使用了默認的配置,而我剛好就是默認的rabbitmq配置比如端口之類的
spring.application.name=spirng-boot-rabbitmq-sender
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
directconfig類:
package com.example.rabbitmq.direct;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by [email protected] on 2019/5/20
* 配置Queue(消息隊列).那注意由於採用的是Direct模式,需要在配置Queue的時候,指定一個鍵,使其和交換機綁定
*/
@Configuration
public class SendConf {
@Bean
public Queue queue() {
return new Queue("hello2");
}
}
topic配置類:
package com.example.rabbitmq.topic;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by [email protected] on 2019/5/22
*/
@Configuration
public class SendExchangeConf {
@Bean(name="message")
public Queue queueMessage() {
return new Queue("topic.message");
}
@Bean(name="messages")
public Queue queueMessages() {
return new Queue("topic.messages");
}
@Bean
public TopicExchange exchange() {
return new TopicExchange("exchange");
}
@Bean
Binding bindingExchangeMessage(@Qualifier("message") Queue queueMessage, TopicExchange exchange) {
return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
}
@Bean
Binding bindingExchangeMessages(@Qualifier("messages") Queue queueMessages, TopicExchange exchange) {
return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");//*表示一個詞,#表示零個或多個詞
}
}
fanout配置類:
package com.example.rabbitmq.fanout;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by [email protected] on 2019/5/22
*/
@Configuration
public class SendFanoutConf {
@Bean(name="Amessage")
public Queue AMessage() {
return new Queue("fanout.A");
}
@Bean(name="Bmessage")
public Queue BMessage() {
return new Queue("fanout.B");
}
@Bean(name="Cmessage")
public Queue CMessage() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");//配置廣播路由器
}
//下面代碼註釋掉也可以接收到消息
// @Bean
// Binding bindingExchangeA(@Qualifier("Amessage") Queue AMessage, FanoutExchange fanoutExchange) {
// return BindingBuilder.bind(AMessage).to(fanoutExchange);
// }
//
// @Bean
// Binding bindingExchangeB(@Qualifier("Bmessage") Queue BMessage, FanoutExchange fanoutExchange) {
// return BindingBuilder.bind(BMessage).to(fanoutExchange);
// }
//
// @Bean
// Binding bindingExchangeC(@Qualifier("Cmessage") Queue CMessage, FanoutExchange fanoutExchange) {
// return BindingBuilder.bind(CMessage).to(fanoutExchange);
// }
}
接收類三種模式公用一個:
package com.example.rabbitmq.topic;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Created by [email protected] on 2019/5/22
*/
@Component
public class HellowReceive {
@RabbitListener(queues="hello") //監聽器監聽指定的Queue
public void processC(User obj) {
System.out.println("Receive hello:"+obj.toString());
}
@RabbitListener(queues="hello2") //監聽器監聽指定的Queue
public void processB(User obj) {
System.out.println("Receive hello2:"+obj.toString());
}
@RabbitListener(queues="fanout.A") //監聽器監聽指定的Queue
public void process1(String str) {
System.out.println("fanout.A:"+str);
}
@RabbitListener(queues="fanout.B") //監聽器監聽指定的Queue
public void process2(String str) {
System.out.println("fanout.B:"+str);
}
@RabbitListener(queues="topic.message") //監聽器監聽指定的Queue
public void process1(String str) {
System.out.println("message:"+str);
}
@RabbitListener(queues="topic.messages") //監聽器監聽指定的Queue
public void process2(String str) {
System.out.println("messages:"+str);
}
}
發送類三種模式公用一個測試類:
package com.example.rabbitmq.direct;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Created by [email protected] on 2019/5/21
*/
@RunWith(SpringRunner.class)
@SpringBootTest(value={"DEPLOY=dev"})
public class HelloSenderTest {
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void direct(){
User user=new User();
user.setId(1);
user.setName("yunfei");
amqpTemplate.convertAndSend("hello2",user);
}
@Test
public void topic(){
amqpTemplate.convertAndSend("exchange","topic.message","123");
}
@Test
public void fanout(){
amqpTemplate.convertAndSend("fanoutExchange","","123");
}
}
結果:
非fanout模式都需要指定序列quene的routingkey,direct會發給key一致的序列,topic會發給匹配規則的序列,fanout發送給所有綁定的序列。