Spring AMQP 是基于 Spring 框架的AMQP消息解决方案,提供模板化的发送和接收消息的抽象层,提供基于消息驱动的 POJO的消息监听等,很大方便我们使用RabbitMQ程序的相关开发。
Spring AMQP包含一些模块,如:spring-amqp, spring-rabbit and spring-erlang等,每个模块分别由独立的一些Jar包组成.
<dependencies>
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.2.0version>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-amqpartifactId>
<version>2.0.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbitartifactId>
<version>2.0.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
<version>1.2.2.RELEASEversion>
dependency>
dependencies>
#IP地址
rabbitmq.host=ip
#端口号
rabbitmq.port=xxx
#用户名
rabbitmq.username=xxxx
#密码
rabbitmq.password=xxxxxx
#消费者监听的队列queue
rabbitmq.queuenames=rabbitMQ_test2,rabbitMQ_pro_lswd_server,rabbitMQ_pro_lswd_wecaht
#生产者监听消息queue
rabbitmq.produce.queuename=rabbitMQ_pro_lswd_server
#消息监听类
rabbitmq.listener.class=com.lswd.rabbitmq.listener.ServiceMessageListener
package org.lswd.rabbitmq.config;
import java.util.Collections;
import javax.inject.Inject;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
@EnableCaching
@PropertySource("classpath:db.properties")
public class RabbitmqConfig {
@Inject
Environment env;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory(
env.getProperty("rabbitmq.host"),
env.getProperty("rabbitmq.port", Integer.class)
);
factory.setUsername(env.getProperty("rabbitmq.username"));
factory.setPassword(env.getProperty("rabbitmq.password"));
return factory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory);
return rabbitAdmin;
}
@Bean
//AmqpTemplate配置,AmqpTemplate接口定义了发送和接收消息的基本操作
public AmqpTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
rabbitTemplate.setRetryTemplate(retryTemplate);
rabbitTemplate.setRoutingKey(env.getProperty("rabbitmq.produce.queuename"));
return rabbitTemplate;
}
/* @Bean public MessageListener messageListener() throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (MessageListener) Class.forName(env.getProperty("rabbitmq.listener.class")).newInstance(); }*/
@Bean
public ChannelAwareMessageListener channelAwareMessageListener() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (ChannelAwareMessageListener) Class.forName(env.getProperty("rabbitmq.listener.class")).newInstance();
}
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory,ChannelAwareMessageListener channelAwareMessageListener){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//container.setMessageListener(messageListener);
container.setChannelAwareMessageListener(channelAwareMessageListener);
Queue[] queues=new Queue[env.getProperty("rabbitmq.queuenames").split(",").length];
for (int i = 0; i < queues.length; i++) {
Queue queue=new Queue(env.getProperty("rabbitmq.queuenames").split(",")[i]);
queues[i]=queue;
}
container.setQueues(queues);
container.setConsumerArguments(Collections. singletonMap("x-priority",
Integer.valueOf(10)));
return container;
}
}
二者的区别可以从他们的抽象类的入参来说:一个是message(消息实体),一个是channel就是当前的通道。
ChannelAwareMessageListener的抽象类:
void onMessage(Message message,Channel channel) .
MessageListener接口的抽象类:
void onMessage(Message message).
- 建议笔者使用前者ChannelAwareMessageListener
多出的参数Channel可以很方便的提供监听之外的ack通知RabbitMq服务器功能。
由于MQ服务器对于消费端无反馈的,有重发机制,可能会有一条数据发送多次,并且一致保存在服务器中,久而久之就会由于数据量过大造成内存溢出的危险,而ack机制就是通过消费端发出通知给mq服务器,告诉服务器那条mq已经处理完毕,可以剔除。对于处理异常的,则可以重新回到mq服务器的队列中。而实现手动实现这个ack的关键点就是实现接口:ChannelAwareMessageListener.如何实现手动ack?
手动ack就是在当前channel里面调用basic***的方法,并传入当前消息的tagId就可以了。
这里的ack通知分为三种情况:
- a.消费端正常处理完成一条mq时,ack mq服务器可以移除此条mq的方法
//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
参数解释:
deliveryTag:该消息的index
multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
- b. 消费端处理完成一条mq时发生异常,ack 会将此条mq重新放到mq服务器队列queue中
//ack返回false,此条mq并重新回到队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
参数解释:
deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列-c.就是消费端主动拒绝mq服务器发送mq过来。
//拒绝消息
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
参数解释:
deliveryTag:该消息的index
requeue:被拒绝的是否重新入队列
channel.basicNack 与 channel.basicReject
的区别在于basicNack可以拒绝多条消息,而basicReject一次只能拒绝一条消息
package com.lswd.rabbitmq.listener;
import java.io.UnsupportedEncodingException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class ServiceMessageListener implements MessageListener{//消息监听类虚实现MessageListener接口
@Override
public void onMessage(Message message) {
try {
//消息内容
String body = new String(message.getBody(), "UTF-8");
System.out.println("消息内容:::::::::::"+body);
//消息队列
System.out.println(message.getMessageProperties().getConsumerQueue());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
package com.lswd.rabbitmq.listener;
import java.io.IOException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;
@Component
public class ServiceMessageListener implements ChannelAwareMessageListener{
@Override
public void onMessage(Message message, Channel channel) throws Exception {
// TODO Auto-generated method stub
String body = new String(message.getBody(), "UTF-8");
System.out.println("消息内容:::::::::::"+body);
System.out.println(message.getMessageProperties().getConsumerQueue());
boolean mqFlag=false;//业务处理
//还有一个点就是如何获取mq消息的报文部分message?
if(mqFlag){
basicACK(message,channel);//处理正常--ack
}else{
basicNACK(message,channel);//处理异常--nack
}
}
//正常消费掉后通知mq服务器移除此条mq
private void basicACK(Message message,Channel channel){
try{
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch(IOException e){
System.out.println("通知服务器移除mq时异常,异常信息:"+e);
}
}
//处理异常,mq重回队列
private void basicNACK(Message message,Channel channel){
try{
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}catch(IOException e){
System.out.println("mq重新进入服务器时出现异常,异常信息:"+e);
}
}
}
@WebAppConfiguration
@RunWith(value = SpringJUnit4ClassRunner.class)
//指定spring配置类
@ContextConfiguration(classes = { WebConfig.class, AppConfig.class})
public class RabbitmqTest{
private @Inject AmqpTemplate rabbitTemplate;
@Test
public void pushMeun(){
MessageProperties properties= new MessageProperties();
properties.setConsumerQueue("rabbitMQ_test2");
rabbitTemplate.send(new Message("12.34".getBytes(), properties));
}
}