SpringMVC注解方式整合RabbitMQ

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/BuFanQi_Info/article/details/80435123

Spring AMQP 是基于 Spring 框架的AMQP消息解决方案,提供模板化的发送和接收消息的抽象层,提供基于消息驱动的 POJO的消息监听等,很大方便我们使用RabbitMQ程序的相关开发。
Spring AMQP包含一些模块,如:spring-amqp, spring-rabbit and spring-erlang等,每个模块分别由独立的一些Jar包组成.

整合Jar包

 <dependencies>
    <!-- RabbitMQ的Java Client库 -->
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.amqp/spring-amqp -->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-amqp</artifactId>
        <version>2.0.3.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.amqp/spring-rabbit -->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>2.0.3.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        <version>1.2.2.RELEASE</version>
    </dependency>
  </dependencies>

rabbitmq.properties

#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

RabbitmqConfig配置文件

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
    <!-- queue litener  观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象-->  
    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. <String, Object> 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一次只能拒绝一条消息

消息监听实现MessageListener

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();
        }
    }
}

消息监听实现ChannelAwareMessageListener

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);
        }
    }
}

RabbitmqTest测试类

@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));
    }
}

猜你喜欢

转载自blog.csdn.net/BuFanQi_Info/article/details/80435123
今日推荐