`

spring boot实战整合RabbitMQ

 
阅读更多

前言

最近几篇文章将围绕消息中间件RabbitMQ展开,对于RabbitMQ基本概念这里不阐述,主要讲解RabbitMQ的基本用法、Java客户端API介绍、spring Boot与RabbitMQ整合、

Spring Boot与RabbitMQ整合源码分析。

 

RabbitMQ安装

 

在使用消息中间件RabbitMQ之前就是安装RabbitMQ。

 

  • 安装erlang:yum install erlang 
  • 下载RabbitMQ安装包: https://www.rabbitmq.com/releases/rabbitmq-server/v3.5.6/rabbitmq-server-generic-unix-3.5.6.tar.gz
  • 解压安装包、配置环境变量RABBITMQ_HOME
 
 
参考网址:https://www.rabbitmq.com/install-generic-unix.html
windows:  https://www.rabbitmq.com/install-windows.html
 

RabbitMQ配置

1.安装完成后需要对RabbitMQ进行配置,在etc/rabbitmq目录下创建两个文件:
  • rabbitmq-env.conf 环境信息配置
RABBITMQ_NODE_IP_ADDRESS=127.0.0.1  
RABBITMQ_NODE_PORT=5672  
RABBITMQ_NODENAME=node01

 

  • rabbitmq.config 核心配置文件

          [{rabbit, [{loopback_users, []}]}].  
该配置表示是的默认用户guest用户可以远程访问mq(广域网不能访问,内网可以访问)
 

2.启动RabbitMQ 执行命令 rabbitmq-server

RabbitMQ 3.5.4. Copyright (C) 2007-2015 Pivotal Software, Inc. ## ## Licensed under the MPL. See http://www.rabbitmq.com/ ## ## ########## Logs: /Users/liaokailin/software/rabbitmq_server-3.5.4/sbin/../var/log/rabbitmq/node01.log ###### ## /Users/liaokailin/software/rabbitmq_server-3.5.4/sbin/../var/log/rabbitmq/node01-sasl.log ########## Starting broker... completed with 0 plugins.

 

3. RabbitMQ提供WEB-UI管理控制台,使用 rabbitmq-plugins enable rabbitmq_management命令启用,重启后可以看到
 
        Starting broker... completed with 6 plugins.  

表明WEB-UI控制台启动成功,访问:http://localhost:15672/
 
 
登陆进入:
 
通过该控制台可以方便管理RabbitMQ。
 

创建Test用户

RabbitMQ默认使用guest用户,下面讲述如何创建一个test用户,最快捷的做法使用web管理控制台

 
 
这里使用命令创建:
  • rabbitmqctl add_user test test
  • rabbitmqctl set_user_tags test  administrator

    tag分为四种"management", "policymaker", "monitoring" "administrator" 详见 http://www.rabbitmq.com/management.html

RabbitMQ 其他

 
在实际使用RabbitMQ中还需要涉及到 RabbitMQ的集群、高可用(采用镜像队列实现)以后有机会再详细阐述,有兴趣可参考https://www.rabbitmq.com/documentation.html
 
 

RabbitMQ Java Client

 
RabbitMQ 客户端支持语言种类繁多,官方都一一举例:https://www.rabbitmq.com/getstarted.html
 
这里主要自己开发一个小的demo
 

消息消费者

操作步骤:
  1. 创建连接工厂ConnectionFactory
  2. 获取连接Connection
  3. 通过连接获取通信通道Channel
  4. 声明交换机Exchange:交换机类型分为四类

        FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念

            HeadersExchange :通过添加属性key-value匹配

            DirectExchange:按照routingkey分发到指定队列

            TopicExchange:多关键字匹配

  5. 声明队列Queue

  6. 将队列和交换机绑定

  7. 创建消费者

  8. 执行消息的消费

package org.lkl.mq.rabbitmq.test;  
  
import java.io.IOException;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.TimeoutException;  
  
import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
import com.rabbitmq.client.ConsumerCancelledException;  
import com.rabbitmq.client.QueueingConsumer;  
import com.rabbitmq.client.QueueingConsumer.Delivery;  
import com.rabbitmq.client.ShutdownSignalException;  
  
/**  
 * 客户端01  
 *   
 * @author liaokailin  
 * @version $Id: Receive01.java, v 0.1 2015年11月01日 下午3:47:58 liaokailin Exp $  
 */  
public class Receive01 {  
    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException,  
                                          ConsumerCancelledException, InterruptedException {  
        ConnectionFactory facotry = new ConnectionFactory();  
        facotry.setUsername("test");  
        facotry.setPassword("test");  
        facotry.setVirtualHost("test");  
        facotry.setHost("localhost");  
  
        Connection conn = facotry.newConnection(); //获取一个链接  
        //通过Channel进行通信  
        Channel channel = conn.createChannel();  
        int prefetchCount = 1;  
        channel.basicQos(prefetchCount); //保证公平分发  
  
        boolean durable = true;  
        //声明交换机  
        channel.exchangeDeclare(Send.EXCHANGE_NAME, "direct", durable); //按照routingKey过滤  
        //声明队列  
        String queueName = channel.queueDeclare("queue-01", true, true, false, null).getQueue();  
        //将队列和交换机绑定  
        String routingKey = "lkl-0";  
        //队列可以多次绑定,绑定不同的交换机或者路由key  
        channel.queueBind(queueName, Send.EXCHANGE_NAME, routingKey);  
  
        //创建消费者  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
          
        //将消费者和队列关联  
        channel.basicConsume(queueName, false, consumer); // 设置为false表面手动确认消息消费  
  
        //获取消息  
  
        System.out.println(" Wait message ....");  
        while (true) {  
            Delivery delivery = consumer.nextDelivery();  
            String msg = new String(delivery.getBody());  
            String key = delivery.getEnvelope().getRoutingKey();  
  
            System.out.println("  Received '" + key + "':'" + msg + "'");  
            System.out.println(" Handle message");  
            TimeUnit.SECONDS.sleep(3); //mock handle message  
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); //确定该消息已成功消费  
        }  
  
    }  
}  

消息生产者

操作步骤:
  1. 创建连接工厂ConnectionFactory
  2. 获取连接Connection
  3. 通过连接获取通信通道Channel
  4. 发送消息
 
 
package org.lkl.mq.rabbitmq.test;  
  
import java.io.IOException;  
import java.util.concurrent.TimeoutException;  
  
import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.ConfirmListener;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
import com.rabbitmq.client.MessageProperties;  
  
/**  
 * 消息publish  
 *   
 * @author liaokailin  
 * @version $Id: Send.java, v 0.1 2015年10月22日 下午3:48:09 liaokailin Exp $  
 */  
public class Send {  
    public final static String EXCHANGE_NAME = "test-exchange";  
  
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {  
        /**  
         * 配置amqp broker 连接信息  
         */  
        ConnectionFactory facotry = new ConnectionFactory();  
        facotry.setUsername("test");  
        facotry.setPassword("test");  
        facotry.setVirtualHost("test");  
        facotry.setHost("localhost");  
  
        Connection conn = facotry.newConnection(); //获取一个链接  
        //通过Channel进行通信  
        Channel channel = conn.createChannel();  
  
        // channel.exchangeDeclare(Send.EXCHANGE_NAME, "direct", true); //如果消费者已创建,这里可不声明  
        channel.confirmSelect(); //Enables publisher acknowledgements on this channel  
        channel.addConfirmListener(new ConfirmListener() {  
  
            @Override  
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {  
                System.out.println("[handleNack] :" + deliveryTag + "," + multiple);  
  
            }  
  
            @Override  
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
                System.out.println("[handleAck] :" + deliveryTag + "," + multiple);  
            }  
        });  
  
        String message = "lkl-";  
        //消息持久化 MessageProperties.PERSISTENT_TEXT_PLAIN  
        //发送多条信息,每条消息对应routekey都不一致  
        for (int i = 0; i < 10; i++) {  
            channel.basicPublish(EXCHANGE_NAME, message + (i % 2), MessageProperties.PERSISTENT_TEXT_PLAIN,  
                (message + i).getBytes());  
            System.out.println("[send] msg " + (message + i) + " of routingKey is " + (message + (i % 2)));  
        }  
  
    }  
}  
 

 

在设置消息被消费的回调前需显示调用
 
channel.confirmSelect()  

 

否则回调函数无法调用
 
先执行消费者,消费者会轮询是否有消息的到来,在web控制也可以观察哦~~,再启动生产者发送消息。

================================

前言

本篇主要讲述spring Boot与RabbitMQ的整合,内容非常简单,纯API的调用操作。 操作之间需要加入依赖Jar

1
2
3
<dependency
<groupId>org.springframework.boot</groupId
<artifactId>spring-boot-starter-amqp</artifactId>  <br>/dependency>

消息生产者


不论是创建消息消费者或生产者都需要ConnectionFactory

 

ConnectionFactory配置


创建AmqpConfig文件AmqpConfig.java(后期的配置都在该文件中)
 
@Configuration
public class AmqpConfig { 
   
    public static final String EXCHANGE   = "spring-boot-exchange"
    public static final String ROUTINGKEY = "spring-boot-routingKey"
   
    @Bean 
    public ConnectionFactory connectionFactory() { 
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); 
        connectionFactory.setAddresses("127.0.0.1:5672"); 
        connectionFactory.setUsername("guest"); 
        connectionFactory.setPassword("guest"); 
        connectionFactory.setVirtualHost("/"); 
        connectionFactory.setPublisherConfirms(true); //必须要设置 
        return connectionFactory; 
    
}

这里需要显示调用
connectionFactory.setPublisherConfirms(true);  
才能进行消息的回调。

RabbitTemplate

通过使用RabbitTemplate来对开发者提供API操作
@Bean  
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  
//必须是prototype类型  
public RabbitTemplate rabbitTemplate() {  
    RabbitTemplate template = new RabbitTemplate(connectionFactory());  
    return template;  
}  
 

 

这里设置为原型,具体的原因在后面会讲到
  在发送消息时通过调用RabbitTemplate中的如下方法
public void convertAndSend(String exchange, String routingKey, final Object object, CorrelationData correlationData)  
  • exchange:交换机名称
  • routingKey:路由关键字

  • object:发送的消息内容

  • correlationData:消息ID

因此生产者代码详单简洁

Send.java

 
@Component  
public class Send  {  
  
    private RabbitTemplate rabbitTemplate;  
  
    /**  
     * 构造方法注入  
     */  
    @Autowired  
    public Send(RabbitTemplate rabbitTemplate) {  
        this.rabbitTemplate = rabbitTemplate;  
    }  
  
    public void sendMsg(String content) {  
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());  
        rabbitTemplate.convertAndSend(AmqpConfig.EXCHANGE, AmqpConfig.ROUTINGKEY, content, correlationId);  
    }  
  
       
}  
 

     

       如果需要在生产者需要消息发送后的回调,需要对rabbitTemplate设置ConfirmCallback对象,由于不同的生产者需要对应不同的ConfirmCallback,如果rabbitTemplate设置为单例bean,则所有的rabbitTemplate

实际的ConfirmCallback为最后一次申明的ConfirmCallback。

下面给出完整的生产者代码:

 

 

 
package com.lkl.springboot.amqp;  
  
import java.util.UUID;  
  
import org.springframework.amqp.rabbit.core.RabbitTemplate;  
import org.springframework.amqp.rabbit.support.CorrelationData;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
  
/**  
 * 消息生产者  
 *   
 * @author liaokailin  
 * @version $Id: Send.java, v 0.1 2015年11月01日 下午4:22:25 liaokailin Exp $  
 */  
@Component  
public class Send implements RabbitTemplate.ConfirmCallback {  
  
    private RabbitTemplate rabbitTemplate;  
  
    /**  
     * 构造方法注入  
     */  
    @Autowired  
    public Send(RabbitTemplate rabbitTemplate) {  
        this.rabbitTemplate = rabbitTemplate;  
        rabbitTemplate.setConfirmCallback(this); //rabbitTemplate如果为单例的话,那回调就是最后设置的内容  
    }  
  
    public void sendMsg(String content) {  
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());  
        rabbitTemplate.convertAndSend(AmqpConfig.EXCHANGE, AmqpConfig.ROUTINGKEY, content, correlationId);  
    }  
  
    /**  
     * 回调  
     */  
    @Override  
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {  
        System.out.println(" 回调id:" + correlationData);  
        if (ack) {  
            System.out.println("消息成功消费");  
        } else {  
            System.out.println("消息消费失败:" + cause);  
        }  
    }  
  
}  
 

 

消息消费者

消费者负责申明交换机(生产者也可以申明)、队列、两者的绑定操作。

交换机

复制代码
/**  
     * 针对消费者配置  
        FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念  
        HeadersExchange :通过添加属性key-value匹配  
        DirectExchange:按照routingkey分发到指定队列  
        TopicExchange:多关键字匹配  
     */  
    @Bean  
    public DirectExchange defaultExchange() {  
        return new DirectExchange(EXCHANGE);  
    }  
 

 

在Spring Boot中交换机继承AbstractExchange类

队列

@Bean  
    public Queue queue() {  
        return new Queue("spring-boot-queue", true); //队列持久  
  
    } 

 

绑定

@Bean  
  public Binding binding() {  
      return BindingBuilder.bind(queue()).to(defaultExchange()).with(AmqpConfig.ROUTINGKEY);  
  } 
完成以上工作后,在spring boot中通过消息监听容器实现消息的监听,在消息到来时执行回调操作。
 

消息消费

 
@Bean  
  public SimpleMessageListenerContainer messageContainer() {  
      SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());  
      container.setQueues(queue());  
      container.setExposeListenerChannel(true);  
      container.setMaxConcurrentConsumers(1);  
      container.setConcurrentConsumers(1);  
      container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认  
      container.setMessageListener(new ChannelAwareMessageListener() {  
  
          @Override  
          public void onMessage(Message message, Channel channel) throws Exception {  
              byte[] body = message.getBody();  
              System.out.println("receive msg : " + new String(body));  
              channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认消息成功消费  
          }  
      });  
      return container;  
  }  
 

 


下面给出完整的配置文件:
 
package com.lkl.springboot.amqp;  
  
import org.springframework.amqp.core.AcknowledgeMode;  
import org.springframework.amqp.core.Binding;  
import org.springframework.amqp.core.BindingBuilder;  
import org.springframework.amqp.core.DirectExchange;  
import org.springframework.amqp.core.Message;  
import org.springframework.amqp.core.Queue;  
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;  
import org.springframework.amqp.rabbit.connection.ConnectionFactory;  
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;  
import org.springframework.amqp.rabbit.core.RabbitTemplate;  
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;  
import org.springframework.beans.factory.config.ConfigurableBeanFactory;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Scope;  
  
import com.rabbitmq.client.Channel;  
  
/**  
 * Qmqp Rabbitmq  
 *   
 * http://docs.spring.io/spring-amqp/docs/1.4.5.RELEASE/reference/html/  
 *   
 * @author lkl  
 * @version $Id: AmqpConfig.java, v 0.1 2015年11月01日 下午2:05:37 lkl Exp $  
 */  
  
@Configuration  
public class AmqpConfig {  
  
    public static final String EXCHANGE   = "spring-boot-exchange";  
    public static final String ROUTINGKEY = "spring-boot-routingKey";  
  
    @Bean  
    public ConnectionFactory connectionFactory() {  
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();  
        connectionFactory.setAddresses("127.0.0.1:5672");  
        connectionFactory.setUsername("guest");  
        connectionFactory.setPassword("guest");  
        connectionFactory.setVirtualHost("/");  
        connectionFactory.setPublisherConfirms(true); //必须要设置  
        return connectionFactory;  
    }  
  
    @Bean  
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  
    //必须是prototype类型  
    public RabbitTemplate rabbitTemplate() {  
        RabbitTemplate template = new RabbitTemplate(connectionFactory());  
        return template;  
    }  
  
    /**  
     * 针对消费者配置  
     * 1. 设置交换机类型  
     * 2. 将队列绑定到交换机  
     *   
     *   
        FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念  
        HeadersExchange :通过添加属性key-value匹配  
        DirectExchange:按照routingkey分发到指定队列  
        TopicExchange:多关键字匹配  
     */  
    @Bean  
    public DirectExchange defaultExchange() {  
        return new DirectExchange(EXCHANGE);  
    }  
  
    @Bean  
    public Queue queue() {  
        return new Queue("spring-boot-queue", true); //队列持久  
  
    }  
  
    @Bean  
    public Binding binding() {  
        return BindingBuilder.bind(queue()).to(defaultExchange()).with(AmqpConfig.ROUTINGKEY);  
    }  
  
    @Bean  
    public SimpleMessageListenerContainer messageContainer() {  
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());  
        container.setQueues(queue());  
        container.setExposeListenerChannel(true);  
        container.setMaxConcurrentConsumers(1);  
        container.setConcurrentConsumers(1);  
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认  
        container.setMessageListener(new ChannelAwareMessageListener() {  
  
            @Override  
            public void onMessage(Message message, Channel channel) throws Exception {  
                byte[] body = message.getBody();  
                System.out.println("receive msg : " + new String(body));  
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认消息成功消费  
            }  
        });  
        return container;  
    }  
  
}  
 

 


以上完成 Spring Boot与RabbitMQ的整合 

自动配置

在Spring Boot中实现了RabbitMQ的自动配置,在配置文件中添加如下配置信息
spring.rabbitmq.host=localhost  
spring.rabbitmq.port=5672  
spring.rabbitmq.username=test  
spring.rabbitmq.password=test  
spring.rabbitmq.virtualHost=test  
后会自动创建ConnectionFactory以及RabbitTemplate对应Bean,为什么上面我们还需要手动什么呢?
自动创建的ConnectionFactory无法完成事件的回调,即没有设置下面的代码
connectionFactory.setPublisherConfirms(true); 
 
具体分析见后续文章的源码解读.

 

=========================================

 

前言

本篇开始讲述Spring Boot如何整合RabbitMQ(实际上Spring就整合了RabbitMQ)。
 

RabbitAdmin

 

在上篇中遗留AmqpAdmin没有讲解,现在来看下该部分代码
public AmqpAdmin amqpAdmin(CachingConnectionFactory connectionFactory) {  
        return new RabbitAdmin(connectionFactory);  
    }  

 

创建RabbitAdmin实例,调用构造方法
public RabbitAdmin(ConnectionFactory connectionFactory) {  
    this.connectionFactory = connectionFactory;  
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null");  
    this.rabbitTemplate = new RabbitTemplate(connectionFactory);  
} 

 


创建连接工厂、rabbitTemplate,其中ConnectionFactory采用上一篇中自定义bean
public ConnectionFactory connectionFactory() {  
     CachingConnectionFactory connectionFactory = new CachingConnectionFactory();  
     connectionFactory.setAddresses("127.0.0.1:5672");  
     connectionFactory.setUsername("guest");  
     connectionFactory.setPassword("guest");  
     connectionFactory.setPublisherConfirms(true); //必须要设置  
     return connectionFactory;  
 }  

 

为CachingConnectionFactory实例,其缓存模式为通道缓存
private volatile CacheMode cacheMode = CacheMode.CHANNEL;  

 

 
接下来看下RabbitAdmin类定义:
public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, InitializingBean {  
...  
}  

 

实现接口AmqpAdmin(定义若干RabbitMQ操作父接口),这里需要强调的是InitializingBean,实现该接口则会调用afterPropertiesSet方法
 
public void afterPropertiesSet() {  
  
        synchronized (this.lifecycleMonitor) {  
  
            if (this.running || !this.autoStartup) {  
                return;  
            }  
  
            if (this.connectionFactory instanceof CachingConnectionFactory &&  
                    ((CachingConnectionFactory) this.connectionFactory).getCacheMode() == CacheMode.CONNECTION) {  
                logger.warn("RabbitAdmin auto declaration is not supported with CacheMode.CONNECTION");  
                return;  
            }  
  
            this.connectionFactory.addConnectionListener(new ConnectionListener() {  
  
                // Prevent stack overflow...  
                private final AtomicBoolean initializing = new AtomicBoolean(false);  
  
                @Override  
                public void onCreate(Connection connection) {  
                    if (!initializing.compareAndSet(false, true)) {  
                        // If we are already initializing, we don't need to do it again...  
                        return;  
                    }  
                    try {  
                           
                        initialize();  
                    }  
                    finally {  
                        initializing.compareAndSet(true, false);  
                    }  
                }  
  
                @Override  
                public void onClose(Connection connection) {  
                }  
  
            });  
  
            this.running = true;  
  
        }  
    }  
 

 

synchronized (this.lifecycleMonitor)加锁保证同一时间只有一个线程访问该代码,随后调用this.connectionFactory.addConnectionListener添加连接监听,各连接工厂关系:
 
 
实际调用为CachingConnectionFactory
public void addConnectionListener(ConnectionListener listener) {  
        super.addConnectionListener(listener);  
        // If the connection is already alive we assume that the new listener wants to be notified  
        if (this.connection != null) {  
            listener.onCreate(this.connection);  
        }  
    }  

 


此时connection为null,无法执行到listener.onCreate(this.connection); 往CompositeConnectionListener connectionListener中添加监听信息,最终保证在集合中
private List<ConnectionListener> delegates = new CopyOnWriteArrayList<ConnectionListener>();  

 

这里添加的监听代码执行,在后面调用时再来讲解。
 
至此~~ RabbitAdmin创建完成。 
 
 

Exchange

接下来继续来看AmqpConfig.java中的代码
@Bean  
  public DirectExchange defaultExchange() {  
      return new DirectExchange(EXCHANGE);  
  }  

 

以上代码创建一个交换机,交换机类型为direct
 

在申明交换机时需要指定交换机名称,默认创建可持久交换机
 

Queue

public Queue queue() {  
       return new Queue("spring-boot-queue", true); //队列持久  
   }  
 
 
默认创建可持久队列
 

Binding

@Bean  
   public Binding binding() {  
       return BindingBuilder.bind(queue()).to(defaultExchange()).with(AmqpConfig.ROUTINGKEY);  
   }  
 
BindingBuilder.bind(queue()) 实现为:
public static DestinationConfigurer bind(Queue queue) {  
        return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE);  
    }  

 


DestinationConfigurer通过name、type区分不同配置信息,其to()方法为重载方法,传递参数为四种交换机,分别返回XxxExchangeRoutingKeyConfigurer,其中with方法返回Bingding实例,因此在Binding信息中存储了
队列、交换机、路由key等相关信息
public class Binding extends AbstractDeclarable {  
  
    public static enum DestinationType {  
        QUEUE, EXCHANGE;  
    }  
  
    private final String destination;  
  
    private final String exchange;  
  
    private final String routingKey;  
  
    private final Map<String, Object> arguments;  
  
    private final DestinationType destinationType;  
...  
}  

以上信息理解都非常简单,下面来看比较复杂点的SimpleMessageListenerContainer
 

SimpleMessageListenerContainer

@Bean  
    public SimpleMessageListenerContainer messageContainer() {  
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());  
        container.setQueues(queue());  
        container.setExposeListenerChannel(true);  
        container.setMaxConcurrentConsumers(1);  
        container.setConcurrentConsumers(1);  
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认  
        container.setMessageListener(new ChannelAwareMessageListener() {  
  
            @Override  
            public void onMessage(Message message, Channel channel) throws Exception {  
                byte[] body = message.getBody();  
                System.out.println("receive msg : " + new String(body));  
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认消息成功消费  
            }  
        });  
        return container;  
    }  

 


查看其实现的接口,注意SmartLifecycle

 
接下来设置队列信息,在AbstractMessageListenerContainer
 

       private volatile List<StringqueueNames = new CopyOnWriteArrayList<String>();  

添加队列信息
    AbstractMessageListenerContainer#exposeListenerChannel设置为true

  
container.setMaxConcurrentConsumers(1);  
container.setConcurrentConsumers(1);  

 

设置并发消费者数量,默认情况为1
 
  1. container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认  

     

设置消费者成功消费消息后确认模式,分为两种
  • 自动模式,默认模式,在RabbitMQ Broker消息发送到消费者后自动删除
  • 手动模式,消费者客户端显示编码确认消息消费完成,Broker给生产者发送回调,消息删除
接下来设置消费者端消息监听,为privatevolatile Object messageListener 赋值
 
到这里消息监听容器也创建完成了,但令人纳闷的时,消费者如何去消费消息呢?从这里完全看不出来。那么接下来看下SmartLifecycle接口
 

SmartLifecycle

熟悉Spring都应该知道该接口,其定义为:

 
public interface SmartLifecycle extends Lifecycle, Phased {  
  
    boolean isAutoStartup();  
    void stop(Runnable callback);  
  
}

 

其中的isAutoStartup设置为true时,会自动调用Lifecycle接口中的start方法,既然我们为源码分析,也简单看下这个聪明的声明周期接口是如何实现它的聪明方法的
 

spring boot实战(第十篇)Spring boot Bean加载源码分析中讲到执行Bean加载时,调用AbstractApplicationContext#refresh(),其中存在一个方法调用finishRefresh()
protected void finishRefresh() {  
    // Initialize lifecycle processor for this context.  
    initLifecycleProcessor();  
  
    // Propagate refresh to lifecycle processor first.  
    getLifecycleProcessor().onRefresh();  
  
    // Publish the final event.  
    publishEvent(new ContextRefreshedEvent(this));  
  
    // Participate in LiveBeansView MBean, if active.  
    LiveBean
 
其中initLifecycleProcessor初始化生命周期处理器,
protected void initLifecycleProcessor() {  
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();  
   if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {  
       this.lifecycleProcessor =  
               beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);  
       if (logger.isDebugEnabled()) {  
           logger.debug("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");  
       }  
   }  
    else {  
        DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();  
        defaultProcessor.setBeanFactory(beanFactory);  
        this.lifecycleProcessor = defaultProcessor;  
        beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);  
        if (logger.isDebugEnabled()) {  
            logger.debug("Unable to locate LifecycleProcessor with name '" +  
                    LIFECYCLE_PROCESSOR_BEAN_NAME +  
                    "': using default [" + this.lifecycleProcessor + "]");  
        }  
    }  
} 
 
 
注册DefaultLifecycleProcessor对应bean

getLifecycleProcessor().onRefresh()调用DefaultLifecycleProcessor中方法onRefresh,调用startBeans(true)

 

private void startBeans(boolean autoStartupOnly) {  
   Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();  
   Map<Integer, LifecycleGroup> phases = new HashMap<Integer, LifecycleGroup>();  
   for (Map.Entry<String, ? extends Lifecycle> entry : lifecycleBeans.entrySet()) {  
       Lifecycle bean = entry.getValue();  
       if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {  
           int phase = getPhase(bean);  
           LifecycleGroup group = phases.get(phase);  
           if (group == null) {  
                group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);  
                phases.put(phase, group);  
            }  
            group.add(entry.getKey(), bean);  
        }  
    }  
    if (phases.size() > 0) {  
        List<Integer> keys = new ArrayList<Integer>(phases.keySet());  
        Collections.sort(keys);  
        for (Integer key : keys) {  
            phases.get(key).start();  
        }  
    }  
} 
 

其中

Map<String, LifecyclelifecycleBeans = getLifecycleBeans();  

 

获取所有实现Lifecycle接口bean,执行bean instanceof SmartLifecycle && ((SmartLifecycle)bean).isAutoStartup()判断,如果bean同时也为Phased实例,则加入到LifecycleGroup中,随后phases.get(key).start()调用start方法

 

接下来要做的事情就很明显:要了解消费者具体如何实现,查看SimpleMessageListenerContainer中的start是如何实现的。

 

至此~~整合RabbitMQ源码分析准备工作完成,下一篇中正式解读消费者的实现。

 

==============================

 

踩坑记录

近日在用spring boot架构一个微服务框架,服务发现与治理、发布REST接口各种轻松惬意。但是服务当设计MQ入口时,就发现遇到无数地雷,现在整理成下文,供各路大侠围观与嘲笑。

版本

当前使用的spring-boot-starter-amqp版本为2016.5发布的1.3.5.RELEASE

也许若干年后,你们版本都不会有这些问题了。:(

RabbitMQ

当需要用到MQ的时候,我的第一反映就是使用RabbitMQ,猫了一眼spring boot的官方说明,上面说spring boot为rabbit准备了spring-boot-starter-amqp,并且为RabbitTemplate和RabbitMQ提供了自动配置选项。暗自窃喜~~

瞅瞅[官方文档]http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rabbitmq和例子,SO EASY,再看一眼GITHUB上的官方例了,也有例子。

心情愉悦的照着例子,开干~~。

踩坑

十五分钟后的代码类似这样:

@Service
@RabbitListener(queues = "merchant")
public class MQReceiver  {
    protected Logger logger = Logger.getLogger(MQReceiver.class
            .getName()); 
  
    @RabbitHandler
    public void process(@Payload UpdateMerchant request) {
        UpdateMerchantResponse response = new UpdateMerchantResponse();
        logger.info(request.getMerchantId() + "->" + response.getReturnCode());
    }
}

消费信息后,应该记录一条日志。
结果得到只有org.springframework.amqp.AmqpException: No method found for class [B 这个异常,并且还无限循环抛出这个异常。。。

记得刚才官方文档好像说了异常什么的,转身去猫一眼,果然有:

If retries are not enabled and the listener throws an exception, by default the delivery will be retried indefinitely. You can modify this behavior in two ways; set the defaultRequeueRejected
 property to false
 and zero re-deliveries will be attempted; or, throw an AmqpRejectAndDontRequeueException
 to signal the message should be rejected. This is the mechanism used when retries are enabled and the maximum delivery attempts are reached.

知道了为啥会无限重试了,下面来看看为啥会抛出这个异常,google搜一下,貌似还有一个倒霉鬼遇到了这个问题

进去看完问题和大神的解答,豁然开朗。

There are two conversions in the @RabbitListener pipeline.
The first converts from a Spring AMQP Message to a spring-messaging Message.
There is currently no way to change the first converter from SimpleMessageConverter which handles String, Serializable and passes everything else as byte[].
The second converter converts the message payload to the method parameter type (if necessary).
With method-level @RabbitListeners there is a tight binding between the handler and the method.
With class-level @RabbitListener s, the message payload from the first conversion is used to select which method to invoke. Only then, is the argument conversion attempted.
This mechanism works fine with Java Serializable objects since the payload has already been converted before the method is selected.
However, with JSON, the first conversion returns a byte[] and hence we find no matching @RabbitHandler.
We need a mechanism such that the first converter is settable so that the payload is converted early enough in the pipeline to select the appropriate handler method.
A ContentTypeDelegatingMessageConverter is probably most appropriate.
And, as stated in AMQP-574, we need to clearly document the conversion needs for a @RabbitListener, especially when using JSON or a custom conversion.

得嘞,官方示例果然是坑,试试大神的解决方案,手动新增下转换。

  @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

然后在生产和消费信息的地方使用他们:

@RabbitListener(queues = "merchant", containerFactory="rabbitListenerContainerFactory")
public void process(@Payload UpdateMerchant request) { 
     UpdateMerchantResponse response = new UpdateMerchantResponse();
    logger.info(request.getMerchantId() + "->" + response.getReturnCode());
 }

再来一次,果然可以了

c.l.s.m.service.MQReceiver : 00000001->null

分享到:
评论
1 楼 天天学 2018-10-17  
SpringBoot整合RabbitMQ实战教程
网盘地址:https://pan.baidu.com/s/1kNPGJdg5iKdjBFaDop5HZA 密码: y5pe
备用地址(腾讯微云):https://share.weiyun.com/5P40875 密码:jsxqek

相关推荐

    spring-boot实战PDF 完整版和随书源码.7z

    《Spring Boot实战》是一本深度剖析Spring Boot框架的实践指南,旨在帮助开发者快速掌握Spring Boot的核心概念和技术。这本书深入浅出地介绍了如何使用Spring Boot构建高效、简洁的Java应用程序。随书源码提供了丰富...

    Spring Boot整合RabbitMQ开发实战详解

    Spring Boot 整合 RabbitMQ 开发实战详解 首先,需要了解 RabbitMQ 中的一些基本概念,包括交换器(Exchange)、队列(Queue)和绑定(Binding)。交换器就像路由器,把消息发到交换器,然后交换器再根据路由键...

    springBoot整合RabbitMQ案例

    SpringBoot整合RabbitMQ是一个常见的后端开发任务,它涉及到分布式消息传递和Java应用程序的集成。RabbitMQ是一个开源的消息代理和队列服务器,而SpringBoot是基于Spring框架的简化版,提供了快速构建应用程序的方式...

    Spring boot 实战,丁雪丰

    《Spring Boot实战》由丁雪丰撰写,是关于Spring Boot技术的一本实战指南。Spring Boot是目前企业级应用开发中的主流框架,它简化了基于Spring的应用程序开发过程,通过“约定优于配置”的原则,极大地提高了开发...

    1 SpringBoot整合RabbitMQ-整合配置篇-源码数据库

    1. **Spring Boot整合RabbitMQ**:Spring Boot通过`spring-boot-starter-amqp`起步依赖提供了与RabbitMQ集成的便利。首先,在`pom.xml`或`build.gradle`文件中引入此依赖,然后配置RabbitMQ服务器的连接信息,包括...

    Spring Boot 系列实战合集.zip

    在"Spring Boot 系列实战合集.zip"这个压缩包中,可能包含了多个关于 Spring Boot 实战的项目或者教程。"新建文件夹"可能是一个包含不同主题或阶段学习资料的组织结构,而"Spring-Boot-In-Action-master"则很可能是...

    boot-rabbitmq.zip

    `boot-rabbitmq.zip`中的代码示例可能涵盖了以上这些关键点,新手可以通过阅读和运行这些代码来快速掌握Spring Boot与RabbitMQ的整合。此外,实战教程可能会进一步介绍如何设计消息模型、如何处理异步通信、如何实现...

    springboot与rabbitmq结合的实战、实例项目

    3. **Spring Boot整合RabbitMQ** - **配置**: 在Spring Boot项目中,可以通过添加`spring-boot-starter-amqp`依赖来引入RabbitMQ支持。接着,在`application.properties`或`application.yml`中配置RabbitMQ服务器的...

    spring-boot-learning-demo-master.zip

    通过这个学习示例,开发者不仅能深入理解Spring Boot的特性,还能掌握如何在实际项目中整合RabbitMQ,提升系统的可扩展性和稳定性。这个压缩包为初学者提供了丰富的实践素材,对于提升Spring Boot和RabbitMQ的技能...

    精通 Spring Boot 42 讲

    15. **Spring Boot实战**:通过案例分析,展示Spring Boot在实际项目中的应用。 以上只是一部分可能的章节内容,实际的"精通 Spring Boot 42 讲"教程可能会更深入地探讨每个主题,并且包含更多实用的示例和实战经验...

    spring-boot-tutorials-master.zip

    在"spring-boot-tutorials-master.zip"这个压缩包中,我们找到了一系列关于Spring Boot的实战示例,涵盖了多个关键组件和技术,如Dubbo、RabbitMQ、Swagger2、缓存管理和JSP。接下来,我们将深入探讨这些知识点。 1...

    尚硅谷Spring Boot课堂笔记.rar

    此外,笔记可能还会涉及Spring Boot的测试、分布式系统支持(如消息队列RabbitMQ或Kafka)、微服务架构、Docker容器化部署等方面的内容,全面覆盖Spring Boot的开发流程和技术栈。 总之,《尚硅谷Spring Boot课堂...

    liuge1988-spring-boot-demo-master.zip

    《Spring Boot实战示例解析与应用》 Spring Boot是由Pivotal团队提供的全新框架,它为简化Spring应用程序的初始搭建以及开发过程提供了便利。通过自动配置、起步依赖和内嵌服务器,Spring Boot使得构建Java应用变得...

    RabbitMQ实战-多线程-springboot-rabbit.zip

    在Spring Boot项目中整合RabbitMQ,我们需要添加相应的依赖项到项目的pom.xml或build.gradle文件中。接着,我们可以通过Spring的@Configuration注解创建一个配置类,声明RabbitMQ的连接工厂、模板以及交换器和队列的...

    RabbitMQ-Day1-Code

    5. **Spring Boot整合RabbitMQ的代码**: 包含了如何在Spring Boot应用中设置RabbitMQ的配置,以及如何通过Spring提供的API发送和接收消息。 这些代码实例为初学者提供了一个良好的起点,帮助理解RabbitMQ的各种工作...

    redis缓存spring boot 高并发秒杀商品实战讲解视频

    redis缓存spring boot 高并发秒杀商品实战讲解视频。包括spring mvc ,mybatis ,redis,rabbitMQ消息队列等的整合

    spring整合

    文件"day02"可能包含更具体的整合示例或者第二天的学习内容,这可能涵盖了Spring与特定技术的实战整合,例如Spring Boot与Docker的整合,或者Spring Cloud与Netflix OSS组件的使用。 总之,Spring整合涵盖了许多...

    spring-boot-1.2.6.RELEASE.zip

    《Spring Boot 1.2.6.RELEASE:深入解析与应用实践》 Spring Boot作为一款由Pivotal团队开发的Java轻量级框架,自发布以来便受到广大开发者热烈追捧,其核心理念在于简化Spring应用的初始搭建以及开发过程。本次...

    spring-cloud-steam-rabbitmq-demo.zip

    《Spring Cloud Stream与RabbitMQ整合实战》 在微服务架构中,消息中间件起着至关重要的作用,它能够实现服务间的异步通信,提高系统的响应速度和可扩展性。本示例“spring-cloud-steam-rabbitmq-demo”将探讨如何...

Global site tag (gtag.js) - Google Analytics