Spring Boot整合RabbitMQ及源码解析

Spring Boot与消息概述
  1. 大多数应用中,可通过消息服务中间件来提升系统的异步性能,扩展解耦能力。消息服务中有两个重要的概念。
    消息代理(message broker)和目的地(destination)
    当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定的目的地

  2. 消息队列主要有两种形式的目的地

    1. 队列(queue):点对点消息通信(point-to-point)
    2. 主题(topic):发送(publish)/订阅(subscribe)消息通信
  3. 点对点式:

    1. 消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列。
    2. 消息只有唯一的发送者和接收者,但并不是只能有一个接收者。
  4. 发布订阅式

    1. 发送者(发布者)发送消息到主题,多个接收者(订阅)监听订阅到主题,那这会在消息到达同时收到消息。
  5. JMS(Java Message Service)Java消息服务:
    1)基于JVM消息代理的规范,ActiveMQ,HornetMQ是JMS实现

  6. AMQP(Advanced Message Queuing Protocal)

    1. 高级消息队列协义,也是一个消息代理的规范,兼容JMS
    2. Rabbit MQ是AMQP的实现。
二 RabbitMQ 概述

  RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
  Message:消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

  Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。

  Exchange :交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

  Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

  Binding:绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Exchange 和Queue的绑定可以是多对多的关系。
Connection:网络连接,比如一个TCP连接。

  Channel:信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
  Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
  Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
在这里插入图片描述
  这是我网上看见的一篇博客的内容,我觉得总结得还是很好的,因此,我就摘抄下来,希望对后面的内容理解有帮助。

  我们还是老办法,先举一个简单的例子,再从这个例子出发,再来研究源码。先看一个例子。

  1. yml配置文件中配置如下内容
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eb:
  config:
    rabbitQueue:
      aaaa: test1
  1. 创建Rabbit配置文件
@Configuration
@Slf4j
public class RabbitConfig {
    @Bean
    public Queue rabbitTestQueue(@Value("${eb.config.rabbitQueue.aaaa}") String queueName) {
        return new Queue(queueName);
    }
}

配置文件中,也没有多余的代码,就是直接创建一个bean名称为rabbitTestQueue的队列,但是需要注意的一点是,队列名字,我们没有直接用常量来处理,而是用yml配置文件中中配置的值。聪明的读者,可以想一想,为什么要这样做。

  1. 创建消息监听器,其实按用途来说,应该是消费者
@Component
@Slf4j
public class RabbitBussListener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestQueue.name}")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            System.out.println("接收到消息:" + message);
        } catch (Exception e) {
            log.error("处理异常", e);
        } finally {

        }
    }
}

rabbitTestQueue是什么呢?好像配置文件中没有这样的配置啊。

  1. 开始测试
@Value("${eb.config.rabbitQueue.aaaa}")
public String routingKey1;

@Autowired
private RabbitTemplate rabbitTemplate;

@RequestMapping("rabbitTest")
public String rabbitTest() {
    String message = "测试" + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(routingKey1,message);
    System.out.println("发送消息为:" + message );
    return "Sucess";
}

【测试结果】
在这里插入图片描述
  相信聪明的读者肯定会想,Spring Boot 使用RabbitMQ 这么简单,之前用Spring MVC 在xml中配置Rabbit 的生产者消费者,实现难用了,觉得像做梦一般,是不是?我自己也觉得,竟然如此简单。
  在分析源码之前,先来看看我们之前留下的疑问,聪明的读者知道用意了吗?首先生产者发送一个路由键为$ {eb.config.rabbitQueue.aaaa}的消息到默认交换器,交换器根据路由键将消息分发给己经注册好名字为$ {eb.config.rabbitQueue.aaaa}队列,己经注册好的消费者从队列名为$ {eb.config.rabbitQueue.aaaa}订阅到消息,开始消费,测试结果如上,打印出 接收到消息:【测试1624529329473】消息内容。
  既然不好从正面说服,那我们从反面来说,假如,队列名和路由键我们定义为一个常量。而我的本地开发环境和测试环境,使用相同的RabbitMQ,为了开发方便,一般情况,测试环境和本地开发环境使用相同的RabbitMQ,就不需要本地安装了。当你在本地开发环境发送一条消息,可能消息会被测试环境的消费者消费,而测试环境的消费者没有你本地刚刚开发的代码,这个时候,你可能每次改一下消费者的代码,就发布一个测试环境,当然 ,还有一种更加简单的方法。直接修改常量队列名称,这样,消息就不会跑到其他机器了。其实你们不要笑的,我以前开发的一个项目就是这样做的,每次要测试消费者代码时,修改一个常量值,消息就不会跑到其他机器上去了,在提交代码时,又忘记改回来,导致队列名总是改变,测试环境还好,但是线上环境可能就会带来一个问题了,假如你在发布项目时,RabbitMQ中有很多消息堆积在服务器上,此时你发布项目后,消费者订阅的路由键变了,RabbitMQ上堆积的消息,就不会被消费掉,带来意想不到的问题。有的小伙伴说这样写,就没有问题了吗 ?@RabbitListener(queues = “# {rabbitTestQueue.name}”),因为本地环境和测试环境的,可以配置成不一样,这样,本地调试时,消息就不可能跑到测试环境中,再加上,一般我们开发小伙伴同时几个人本地调试,这种可能性很少,一般都是开发好了代码,再启动项目进行调试,因为我们电脑性能问题,一般不会边开发,边启动项目,即使发现同时几个人在调试,我改一下本地的配置文件内容,消息就不会跑到其他开发者电脑中去了,即使改了,也不会影响到测试环境,更不会影响到线上环境的消费,因为你只会改本地开发环境的配置文件,对于小项目来说,开发测试线上环境都使用同一个RabbitMQ的公司来说,这样做就更加重要了。
  我相信经过我的分析,应该理解@RabbitListener(queues = “# {rabbitTestQueue.name}”)这个配置的意义了。

  我们之前的博客中分析了MybatisAutoConfiguration源码实现,那根据名字,我们猜一下,Rabbit的主配置类为RabbitAutoConfiguration,在代码中找一下,果不其然,有RabbitAutoConfiguration类。
  同样,我们猜测他在classpath下的META-INF/spring.factories中有配置。
在这里插入图片描述
  因此,Spring Boot在启动时,会调用processDeferredImportSelectors加载该配置类,这些都是之前的Spring Boot 系列博客中分析的逻辑,这里就不再赘述,只是将一个结论性的东西说出来了。先来看RabbitAutoConfiguration类

@Configuration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {

...省略
}

  因为我们导入了spring-boot-starter-amqp包,因此RabbitTemplate和Channel肯定存在,而之前我们也分析过EnableConfigurationProperties注解的实现原理,yml的rabbit相关配置肯定会注入到RabbitProperties属性中。并且RabbitProperties本身作为bean注入到容器中,而RabbitAutoConfiguration Import 了RabbitAnnotationDrivenConfiguration类,接下来,我们先来看看RabbitAnnotationDrivenConfiguration类的实现。

@Configuration
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {

    private final ObjectProvider<MessageConverter> messageConverter;

    private final ObjectProvider<MessageRecoverer> messageRecoverer;

    private final RabbitProperties properties;

    RabbitAnnotationDrivenConfiguration(ObjectProvider<MessageConverter> messageConverter,
            ObjectProvider<MessageRecoverer> messageRecoverer,
            RabbitProperties properties) {
        this.messageConverter = messageConverter;
        this.messageRecoverer = messageRecoverer;
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean
    public SimpleRabbitListenerContainerFactoryConfigurer rabbitListenerContainerFactoryConfigurer() {
        SimpleRabbitListenerContainerFactoryConfigurer configurer = new SimpleRabbitListenerContainerFactoryConfigurer();
        configurer.setMessageConverter(this.messageConverter.getIfUnique());
        configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique());
        configurer.setRabbitProperties(this.properties);
        return configurer;
    }

    @Bean
    @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer,
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    @EnableRabbit
    @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
    protected static class EnableRabbitConfiguration {
    }
}

  显然EnableRabbit一定存在,则RabbitAnnotationDrivenConfiguration肯定会被实例化。那么我们来看RabbitAnnotationDrivenConfiguration第一个实例化的bean。

@Bean
@ConditionalOnMissingBean
public SimpleRabbitListenerContainerFactoryConfigurer rabbitListenerContainerFactoryConfigurer() {
    SimpleRabbitListenerContainerFactoryConfigurer configurer = new SimpleRabbitListenerContainerFactoryConfigurer();
    configurer.setMessageConverter(this.messageConverter.getIfUnique());
    configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique());
    configurer.setRabbitProperties(this.properties);
    return configurer;
}

  SimpleRabbitListenerContainerFactoryConfigurer的实例化很简单,就是设置了一下属性,但是还是相同的问题,你不用担心RabbitProperties为空,为什么呢?因为RabbitAnnotationDrivenConfiguration肯定会比SimpleRabbitListenerContainerFactory先实例化。因此,在RabbitAnnotationDrivenConfiguration实例化时,就己经注入了RabbitProperties属性,因此,你不用担心RabbitProperties属性为空。
  接下来,我们继续来看rabbitListenerContainerFactory的实例化。

@Bean
@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
        SimpleRabbitListenerContainerFactoryConfigurer configurer,
        ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    configurer.configure(factory, connectionFactory);
    return factory;
}

  我们需要注意的一点是,在上一个方法实例化的SimpleRabbitListenerContainerFactoryConfigurer方法的参数传入。第二个参数ConnectionFactory,我们过一会再来分析,我们看configure方法到底做了哪些事情。

public void configure(SimpleRabbitListenerContainerFactory factory,
        ConnectionFactory connectionFactory) {
    Assert.notNull(factory, "Factory must not be null");
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
    //设置 ConnectionFactory 以用于获取 RabbitMQ 连接。 
    factory.setConnectionFactory(connectionFactory);
    if (this.messageConverter != null) {
    	//设置消息转换器
        factory.setMessageConverter(this.messageConverter);
    }
    RabbitProperties.AmqpContainer config = this.rabbitProperties.getListener()
            .getSimple();
    //设置是否在初始化后自动启动容器。 默认为“真”; 将此设置为“false”以允许通过 start() 方法手动启动。
    factory.setAutoStartup(config.isAutoStartup());
    if (config.getAcknowledgeMode() != null) {
    	//控制容器在消息确认方面的行为的标志。
        factory.setAcknowledgeMode(config.getAcknowledgeMode());
    }
    if (config.getConcurrency() != null) {
    	//指定要创建的并发消费者的数量。
        factory.setConcurrentConsumers(config.getConcurrency());
    }
    if (config.getMaxConcurrency() != null) {
    	//设置消费者数量的上限;
        factory.setMaxConcurrentConsumers(config.getMaxConcurrency());
    }
    if (config.getPrefetch() != null) {
    	//告诉代理在单个请求中要向每个消费者发送多少条消息。通常可以将其设置得相当高以提高吞吐量。
        factory.setPrefetchCount(config.getPrefetch());
    }
    if (config.getTransactionSize() != null) {
        factory.setTxSize(config.getTransactionSize());
    }
    if (config.getDefaultRequeueRejected() != null) {
    	//设置消息被拒绝时的默认行为,例如因为侦听器抛出异常。
        factory.setDefaultRequeueRejected(config.getDefaultRequeueRejected());
    }
    if (config.getIdleEventInterval() != null) {
    	//以毫秒为单位发出 ListenerContainerIdleEvents 的频率。
        factory.setIdleEventInterval(config.getIdleEventInterval());
    }
    ListenerRetry retryConfig = config.getRetry();
    if (retryConfig.isEnabled()) {
        RetryInterceptorBuilder<?> builder = (retryConfig.isStateless()
                ? RetryInterceptorBuilder.stateless()
                : RetryInterceptorBuilder.stateful());
        //最大的偿试次数
        builder.maxAttempts(retryConfig.getMaxAttempts());
        //每一次偿试的时间间隔
        builder.backOffOptions(retryConfig.getInitialInterval(),
                retryConfig.getMultiplier(), retryConfig.getMaxInterval());
        MessageRecoverer recoverer = (this.messageRecoverer != null
                ? this.messageRecoverer : new RejectAndDontRequeueRecoverer());
        builder.recoverer(recoverer);
        factory.setAdviceChain(builder.build());
    }
}

  上述代码中,Spring Boot帮我们创建了一个默认SimpleRabbitListenerContainerFactory容器,这个容器到底有什么用呢?
  我们先来看一种场景,我们做了一个短信发送服务,因为短信比较多,为了不影响主业务,同时也为了减轻服务器的压力,我们将短信内容发送到RabbitMQ上,再由RabbitMQ的消费者来发送短信,这样,就不会影响主业务速度,但是因为机器有限,线上只有两台机器,同一时间发送的短信比较多的情况,因为消费者消费不过来,直接导致短信发送有延迟,这个时候,肯定有人会想,那好办,我们将消费者那端做成异步,不就不会阻塞了嘛,但你想过没有,如果做成异步,那和你在发送端就直接做成异步,又有什么区别呢?都不需要经过RabbitMQ就有相同的效果了。如果一下流量比较大的话,可能还是会将服务器搞挂掉的。肯定又有小伙伴想到了,我在消费端用用固定数量的线程池大小来消费,不就做到了,每次只消费一部分消息了嘛,感觉是这么一回事,接下来,我们就为这个想法来测试一把。

  1. 重写消费者
@Component
@Slf4j
public class RabbitFixPoolListener {

    private static ExecutorService pool = Executors.newFixedThreadPool(5);
    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestQueue.name}")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            pool.submit(new MycallableA(message));
        } catch (Exception e) {
            log.error("处理异常", e);
        } finally {
        }
    }
    static class MycallableA implements Callable {
        private String message ;
        public MycallableA(String message) {
            this.message = message;
        }
        @Override
        public Object call() throws Exception {
            Thread.sleep(3000);
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String bs[] = message.split(" ");
            System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
            return null;
        }
    }
}

上面需要注意的是,我们创建一个固定大小线程池,固定大小为5,在线程池中,每个消息等待3秒。再打印出消费的消息。
2. 创建生产者

@RequestMapping("rabbitTest2")
public String rabbitTest2() {
    for(int i = 0 ;i < 100;i ++){
        String message = "测试 " + i + " " + System.currentTimeMillis();
        rabbitTemplate.convertAndSend(routingKey1,message);
        log.info(" 发送消息为:" + message );
    }
    return "Sucess";
}

生产者没有什么稀奇的,只不过同时发送100条消息。但是需要注意的是,千万不要将convertAndSend写成convertSendAndReceive,不然达不到效果。convertSendAndReceive的意思,就是必需消费者消费掉消息,才能发送下一条消息。

开始测试
在这里插入图片描述
从上述结果中来看,确实达到了一次消费5条消息的效果,但是有没有读者会疑惑呢?但是发现一个现象,消息还没有消费完,Rabbit 服务器上却没有消息了。在这里插入图片描述
  按道理,消息还没有被消费完,消息应该是Rabbit服务器的队列中,而不是不见了,为了查找是不是线程池导致的,我们去掉线程池。

@RabbitHandler
@RabbitListener(queues = "#{rabbitTestQueue.name}")
public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
    try {
       	// pool.submit(new MycallableA(message));
        Thread.sleep(3000);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    } catch (Exception e) {
        log.error("处理异常", e);
    } finally {
    }
}

  再次测试,看一下效果。
在这里插入图片描述
  神奇的现象出现了。发现队列中,存在没有被消费掉的消息90个,这个是为什么呢?我们再来理一下线程池的逻辑。即使我们用的是固定大小的线城池每次只消费5个消费,但是线程池是有队列的,如果没有及时被消费掉,这些消息将会被存储到线程池的等待队列中。也就是说,这些消息最终还是一次性被消费者全部拉取,存储在本地的队列中,而newFixedThreadPool,每次从队列中取固定数量的消息进行消费。是不是这个猜想呢?我们改回原来的代码。在加入线程池之前,我们先打印一条接收消息的日志。

@RabbitHandler
@RabbitListener(queues = "#{rabbitTestQueue.name}")
public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
    try {
        // pool.submit(new MycallableA(message));
        Thread.sleep(3000);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    } catch (Exception e) {
        log.error("处理异常", e);
    } finally {
    }
}

在这里插入图片描述
  虽然效果达到了,但是也带来了问题,假如,此时服务器被重启了,所有的消息,将全部丢失,这不是Rabbit的问题,是我们人为写代码导致的问题,为了解决这个问题,我们将消费机制改成手动确认。

@Component
@Slf4j
public class RabbitFixPoolListener {

    private static ExecutorService pool = Executors.newFixedThreadPool(5);

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestQueue.name}")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String bs[] = message.split(" ");
            System.out.println("-------接收到消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
            pool.submit(new MycallableA(channel,delivertTag,message));
        } catch (Exception e) {
            log.error("处理异常", e);
        } finally {
        }
    }
    
    static class MycallableA implements Callable {
        private String message ;
        private Channel channel;
        private long delivertTag;

        public MycallableA(Channel channel,long delivertTag,String message) {
            this.message = message;
            this.channel = channel;
            this.delivertTag = delivertTag;
        }
        @Override
        public Object call() throws Exception {
            Thread.sleep(3000);
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String bs[] = message.split(" ");
            System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
            channel.basicAck(delivertTag,true);
            return null;
        }
    }
}

【测试结果】
在这里插入图片描述
在这里插入图片描述

  而此时,你会发现一个神奇的现象,线程池变得无效。变成一个一个消费了,这是为什么呢?这是为什么呢?因为手动确认机制,RabbitMQ为了保证每个消息都被正常消费,因此,只会当消费者调用了channel.basicAck(delivertTag,true);才会将下一条消息推送给消费者。因此,即使你使用线程池,也需要在线程池中处理完业务逻辑后,调用basicAck后,RabbitMQ才会将下一条消息推送给消费者,因此,你会发现,线程池无效。经过一番测试之后,发现使用线程池真是 猪八戒照镜子 —— 里外不是人,不使用手动确认机制,如果重启服务器,所有存储在队列中的消息都丢失,如果使用手动确认机制,发现线程池又无用了,消费速度过慢,可能存在消息延迟消费。
  那怎么办呢?此时此刻,还能有什么办法来解决这个问题呢?既然是消费者因为一次只拉取一条消息,那么,那我们让消费者一次性多拉取几条消息,看效果如何。

spring:
  rabbitmq:
    host: 172.16.157.242
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 5

  我们在yml文件中,rabbitmq的配置下,增加prefetch: 5参数,说明消费者一次性订阅5条数据,因为消费者一次性默认是订阅一条数据的,现在一次性订阅5条数据。再来看效果
在这里插入图片描述
在这里插入图片描述

  从测试效果来看,确实实现了我们想要的效果,一次性消费5条消息,但是遗憾的是,抛出了一个错误,同时还出现了消息重复消费的情况,为什么会抛出这样一个错误呢?
在这里插入图片描述
  从上图中,我们知道,导致抛出异常的原因,是因为批量确认时delivertTag值导致的,比如接收到5条delivertTag分别是1,2,3,4,5,又将delivertTag放到了异步线程中去了,因为每条消息业务处理时长不一样,先ack delivertTag为5的消息,再来ack delivertTag为1的消息,因为是批量确认机制,如果ack delivertTag 为5的消息,证明delivertTag 小于5的消息全部接收成功,此时服务器可能不再有 delivertTag 为1的这个值,此时, 你再ack delivertTag为1,则RabbitMQ就会抛出错误了,为什么消息会被重复消费呢?
在这里插入图片描述
  从上图来看,进入异步线程,先ack deliveryTag为2的消息【测试2】,然后再ack deliveryTag为1的消息【测试0】,此时服务器unknown delivery tag 1,因此抛出错误,并关闭信道,但是既然ack 了消息【测试2】和 消息【测试0】,RabbitMQ认为消息【测试2】和【测试0】是消费成功了的,但是其他的消息,【测试 2】【测试 3】【测试 4】,他认为没有消费成功,消息【测试 2】【测试 3】【测试 4】会重新发送,因为在接收到消息【测试 5】和【测试 6】,此时因为ack deliveryTag为1时,因为deliveryTag 为1 不存在,导致信道关闭,所以导致了【测试 5】和【测试 6】也被重复消费。为什么在ack 【测试 5】的消息时,也抛出了unknown delivery tag 6 错误(【测试 5】消息对应的 deliveryTag为6 ),而不会导致消息重复消费问题呢?因为消息【测试 5】的信道在ack deliveryTag为1时,信道就被关闭了,没有其他消息在这个信道上,所以不会影起消息重复消费问题。如果想保证ack deliveryTag的值不会因为线程池中执行导致批量ack时时deliveryTag的值的无序性带来的问题,那将ack 的逻辑放到主线程不就可以了吗?(备注:上面提到的,为什么在批量ack时不能 ack deliveryTag 2 之后 再 ack deliveryTag 1 ,因为,RabbitMQ设计批量ack的目的就是为了提高吞吐量,当你ack deliveryTag 2之后,他就认为 deliveryTag 小于等于2的消息全部确认,而deliveryTag 小于2的消息,不需要再ack,从而达到提高吞吐量目的,而如果你想ack deliveryTag 2 之后,再ack deliveryTag 1 不会报错,只能单独ack ,也就是channel.basicAck(delivertTag,false);的第二个参数为false,这样,不同消息的ack就不会影响了,这些内容在我之前的RabbitMq初识(一)的博客中讲得很清楚了,如果这一块不懂的小伙伴,可以去看看) 。
在这里插入图片描述
测试效果如下
在这里插入图片描述
  但是发到主线程,又出现了之前的问题,所有的消息全部被接收掉,放到队列中了,即使用了ack,也不报错,但是又回到了原来的问题,就是所有的消息被加入到线程池的队列中了,如果重启,还是会导致消息丢失。
  因此,为了不让程序报错,同时又能让程序同时异步消费掉多条消息,还有另外一种办法,就是将ack确认,由批量确认变成单独确认。
在这里插入图片描述
  终于实现了消息异步消费,同时又让程序不报错,但是有强迫症的小伙伴,肯定觉得这个代码,看起来不是那么简单,又不是那样优雅。有什么更好的办法呢,机制的小伙伴肯定会想到用SimpleRabbitListenerContainerFactory,因为我们开头就是分析到SimpleRabbitListenerContainerFactory源码这一块,就来举例了。
  接下来,我们来分析SimpleRabbitListenerContainerFactory的使用。

1.创建消息接收容器

@Bean(name = "simpleRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    // 设置消费者数量
    listenerContainerFactory.setConcurrentConsumers(5);
    listenerContainerFactory.setMaxConcurrentConsumers(5);
    // 预处理消息个数
    listenerContainerFactory.setPrefetchCount(5);
    // 开启消息确认机制listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    return listenerContainerFactory;
}
  1. 创建消费者代码
@Component
@Slf4j
public class RabbitBussListener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestQueue.name}",containerFactory = "simpleRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            System.out.println("-------接收到消息:" + message);
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String bs[] = message.split(" ");
            System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
        } catch (Exception e) {
            log.error("处理异常", e);
        } finally {
            try {
                channel.basicAck(delivertTag,true);
            } catch (IOException e) {
                log.error("确实消息失败",e);
            }
        }
    }

}

  此时,再来测试。
在这里插入图片描述
在这里插入图片描述
  因为,我们在simpleRabbitListenerContainerFactory容器中,设置了5个消费者,每个消费者一次订阅5条消息,所以,未确认的消息是25条,即使进行重启,消息也不会丢失掉。同时又实现了异步消费消息。
  上面,只是在生产中的一个关于SimpleRabbitListenerContainerFactory使用的例子,还是言归正传,继续看我们的源码。
  我们回头看这个方法rabbitListenerContainerFactory(),这个方法中,第二个参数。ConnectionFactory connectionFactory,这个又是从哪里注入的呢?我们加到RabbitAutoConfiguration类,其实有一个RabbitConnectionFactoryCreator静态内部类。

@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
protected static class RabbitConnectionFactoryCreator {

    @Bean
    public CachingConnectionFactory rabbitConnectionFactory(RabbitProperties config)
            throws Exception {
        RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
        if (config.determineHost() != null) {
        	//设置ip
            factory.setHost(config.determineHost());
        }
        //设置端口
        factory.setPort(config.determinePort());
        if (config.determineUsername() != null) {
	        //设置用户名
            factory.setUsername(config.determineUsername());
        }
        if (config.determinePassword() != null) {
        	//设置密码
            factory.setPassword(config.determinePassword());
        }
        if (config.determineVirtualHost() != null) {
        	//设置虚似主机
            factory.setVirtualHost(config.determineVirtualHost());
        }
        
        if (config.getRequestedHeartbeat() != null) {
            factory.setRequestedHeartbeat(config.getRequestedHeartbeat());
        }
        RabbitProperties.Ssl ssl = config.getSsl();
        if (ssl.isEnabled()) {
            factory.setUseSSL(true);
            if (ssl.getAlgorithm() != null) {
                factory.setSslAlgorithm(ssl.getAlgorithm());
            }
            factory.setKeyStore(ssl.getKeyStore());
            factory.setKeyStorePassphrase(ssl.getKeyStorePassword());
            factory.setTrustStore(ssl.getTrustStore());
            factory.setTrustStorePassphrase(ssl.getTrustStorePassword());
        }
        //设置连接超时时间
        if (config.getConnectionTimeout() != null) {
            factory.setConnectionTimeout(config.getConnectionTimeout());
        }
        factory.afterPropertiesSet();
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
                factory.getObject());
        connectionFactory.setAddresses(config.determineAddresses());
        //设置消息发布到交换器确认监听器
        connectionFactory.setPublisherConfirms(config.isPublisherConfirms());
        //设置消息发布从交换器到队列接收确认监听器监听器
        connectionFactory.setPublisherReturns(config.isPublisherReturns());
        //信道的缓存大小
        if (config.getCache().getChannel().getSize() != null) {
            connectionFactory
                    .setChannelCacheSize(config.getCache().getChannel().getSize());
        }
        //缓存模式有两种,CHANNEL和CONNECTION模式
        if (config.getCache().getConnection().getMode() != null) {
            connectionFactory
                    .setCacheMode(config.getCache().getConnection().getMode());
        }
        //设置缓存中连接尺寸大小
        if (config.getCache().getConnection().getSize() != null) {
            connectionFactory.setConnectionCacheSize(
                    config.getCache().getConnection().getSize());
        }
        //设置信道超时时间
        if (config.getCache().getChannel().getCheckoutTimeout() != null) {
            connectionFactory.setChannelCheckoutTimeout(
                    config.getCache().getChannel().getCheckoutTimeout());
        }
        return connectionFactory;
    }
}

  rabbitListenerContainerFactory()方法中第二个参数ConnectionFactory,来自于RabbitConnectionFactoryCreator配置中Bean的注入,最终ConnectionFactory就是CachingConnectionFactory包装类对象,而真正的rabbitConnectionFactory在RabbitConnectionFactoryBean的getObject方法中获得,接下来,我们来看看rabbitConnectionFactory是怎样获得的。

public void afterPropertiesSet() throws Exception {
    if (isSingleton()) {
        this.initialized = true;
        this.singletonInstance = createInstance();
        this.earlySingletonInstance = null;
    }
}

protected final ConnectionFactory connectionFactory = new ConnectionFactory();

protected ConnectionFactory createInstance() throws Exception {
    if (this.useSSL) {
        setUpSSL();
    }
    return this.connectionFactory;
}

public final T getObject() throws Exception {
    if (isSingleton()) {
        return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
    }
    else {
        return createInstance();
    }
}

  最终CachingConnectionFactory的rabbitConnectionFactory属性,其实是ConnectionFactory对象。而CachingConnectionFactory只是一个connectionFactory的一个代理类,可能有小伙伴对上面的些rabbit配置参数不是很了解,如果想了解的话,可以去看我的RabbitMq初识(一)Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析 这两篇博客,都有详细的说明。
  接来下,我们来看看RabbitAnnotationDrivenConfiguration下的EnableRabbitConfiguration类。

@EnableRabbit
@ConditionalOnMissingBean(name = "org.springframework.amqp.rabbit.config.internalRabbitListenerAnnotationProcessor" )
protected static class EnableRabbitConfiguration {

}

  当容器中缺失internalRabbitListenerAnnotationProcessorbean时,就会创建EnableRabbitConfiguration,此时EnableRabbit注解就起作用了。而EnableRabbit注解的作用是什么呢?全局搜索一下,好像没有看到EnableRabbit.class注解解析的代码。
在这里插入图片描述
  再看看EnableRabbit注解的源码。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RabbitBootstrapConfiguration.class)
public @interface EnableRabbit {
}

  额,终于看到我们想看到的了。Spring Boot在启动时,会加载所有Bean上的Import类的BeanDefinition到容器中。EnableRabbit注解的作用,就是注册RabbitBootstrapConfiguration的BeanDefinition。接下来,我们继续来看RabbitBootstrapConfiguration的内部实现。

@Configuration
public class RabbitBootstrapConfiguration {

    @Bean(name = org.springframework.amqp.rabbit.config.internalRabbitListenerAnnotationProcessor)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public RabbitListenerAnnotationBeanPostProcessor rabbitListenerAnnotationProcessor() {
        return new RabbitListenerAnnotationBeanPostProcessor();
    }

    @Bean(name = "org.springframework.amqp.rabbit.config.internalRabbitListenerEndpointRegistry")
    public RabbitListenerEndpointRegistry defaultRabbitListenerEndpointRegistry() {
        return new RabbitListenerEndpointRegistry();
    }
}

  上面又注册了两个Bean,一个是RabbitListenerAnnotationBeanPostProcessor,另外一个是RabbitListenerEndpointRegistry,这两个Bean有什么用呢?
  第一,我们先来看RabbitListenerAnnotationBeanPostProcessor的实现,在看实现之前,我们来看看RabbitListenerAnnotationBeanPostProcessor的类关系结构。
在这里插入图片描述
  实现这些接口有什么用,疑问先留在这里,后面,我们再来分析。我们先来看一下bean的生命周期图。因为所有的bean都会经历postProcessAfterInitialization方法处理。
在这里插入图片描述
  这句话什么意思呢?只要我们写一个bean,实现BeanPostProcessor接口,并注册到容器中,那么所有的bean在实例化时,都会被我们自定义的MyBeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法处理,处理的位置如上图所示。

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("car")){
            Car car = (Car)bean;
            if(car.getColor() == null){
                System.out.println("调用 BeanPostProcessor.postProcessBeforeInitialization()");
                car.setColor("黑色");
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("car")){
            Car car = (Car)bean;
            if(car.getMaxSpeed() >= 200){
                System.out.println("调用 BeanPostProcessor.postProcessAfterInitialization() ,  将 maxSpeed 调整为200 。 ");
                car.setMaxSpeed(180);
            }
        }
        return bean;
    }
}

  上面只是一个例子,但是对于单个bean的属性处理时,一般我们不会BeanPostProcessor来处理,不如实现InitializingBean接口,在afterPropertiesSet方法中来处理属性更加合理一些,如果需要对所有Bean进行判断时,如RabbitListenerAnnotationBeanPostProcessor处理器,需要对所有的Bean进行轮询判断类上是否有RabbitListener和RabbitListeners注解时,这个时候用BeanPostProcessor来处理,就在所难免。
  RabbitListenerAnnotationBeanPostProcessor的postProcessBeforeInitialization方法没有做什么逻辑,接下来,我们来看postProcessAfterInitialization方法的内部实现。

public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
	Class<?> targetClass = AopUtils.getTargetClass(bean);
	//缓存作用
	TypeMetadata metadata = this.typeCache.get(targetClass);
	if (metadata == null) {
		metadata = buildMetadata(targetClass);
		this.typeCache.putIfAbsent(targetClass, metadata);
	}
	//对method上有RabbitListener或RabbitListeners的方法进行处理
	for (ListenerMethod lm : metadata.listenerMethods) {
		for (RabbitListener rabbitListener : lm.annotations) {
			processAmqpListener(rabbitListener, lm.method, bean, beanName);
		}
	}
	//对类上有RabbitListener或RabbitListeners的方法进行处理
	if (metadata.handlerMethods.length > 0) {
		processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
	}
	return bean;
}

  无论是方法上有RabbitListener注解还是类上有RabbitListener注解的情况,还是先要得到metadata,才知道怎样处理。接下来,我们来看看buildMetadata方法的实现。

private TypeMetadata buildMetadata(Class<?> targetClass) {
	//查看类上是否有RabbitListener或RabbitListeners注解
	Collection<RabbitListener> classLevelListeners = findListenerAnnotations(targetClass);
	final boolean hasClassLevelListeners = classLevelListeners.size() > 0;
	final List<ListenerMethod> methods = new ArrayList<ListenerMethod>();
	final List<Method> multiMethods = new ArrayList<Method>();
	//递归遍历类,父类,接口中的所有方法
	ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {

		@Override
		public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
			//查看方法上是否有RabbitListener和RabbitListeners注解
			Collection<RabbitListener> listenerAnnotations = findListenerAnnotations(method);
			if (listenerAnnotations.size() > 0) {
				methods.add(new ListenerMethod(method,
						listenerAnnotations.toArray(new RabbitListener[listenerAnnotations.size()])));
			}
			//如果类上有RabbitListener和RabbitListeners注解,则查找有RabbitHandler注解的方法
			if (hasClassLevelListeners) {
				RabbitHandler rabbitHandler = AnnotationUtils.findAnnotation(method, RabbitHandler.class);
				if (rabbitHandler != null) {
					multiMethods.add(method);
				}
			}

		}
	//过虑掉桥接方法和Object方法
	}, ReflectionUtils.USER_DECLARED_METHODS);
	if (methods.isEmpty() && multiMethods.isEmpty()) {
		return TypeMetadata.EMPTY;
	}
	//将所有方法中配置了RabbitListener或RabbitListeners的方法存储于TypeMetadata的listenerMethods属性中
	//将所有的类上配置了RabbitListener或RabbitListeners,但方法上配置了RabbitHandler的方法存储于TypeMetadata的handlerMethods属性中
	//将所有类上配置的RabbitListener信息存储于TypeMetadata的classAnnotations属性中
	return new TypeMetadata(
			methods.toArray(new ListenerMethod[methods.size()]),
			multiMethods.toArray(new Method[multiMethods.size()]),
			classLevelListeners.toArray(new RabbitListener[classLevelListeners.size()]));
}

public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
	Method[] methods = getDeclaredMethods(clazz);
	for (Method method : methods) {
		if (mf != null && !mf.matches(method)) {
			continue;
		}
		try {
			mc.doWith(method);
		}
		catch (IllegalAccessException ex) {
			throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
		}
	}
	if (clazz.getSuperclass() != null) {
		doWithMethods(clazz.getSuperclass(), mc, mf);
	}
	else if (clazz.isInterface()) {
		for (Class<?> superIfc : clazz.getInterfaces()) {
			doWithMethods(superIfc, mc, mf);
		}
	}
}

private Collection<RabbitListener> findListenerAnnotations(Class<?> clazz) {
	Set<RabbitListener> listeners = new HashSet<RabbitListener>();
	RabbitListener ann = AnnotationUtils.findAnnotation(clazz, RabbitListener.class);
	if (ann != null) {
		listeners.add(ann);
	}
	RabbitListeners anns = AnnotationUtils.findAnnotation(clazz, RabbitListeners.class);
	if (anns != null) {
		Collections.addAll(listeners, anns.value());
	}
	return listeners;
}

  从源码的解析角度来看,我们至少知道了RabbitListener的几种用法。

  1. 直接在类上配置RabbitListener或RabbitListeners
@Component
@RabbitListeners({@RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory"),
        @RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory")})
@RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory")
public class MyRabbitListener {
    
    @RabbitHandler
    public void recive(){
        
    }
}

类上可以注解RabbitListeners,或RabbitListener,或两者都有,方法上必需有@RabbitHandler修饰的方法

  1. 类上不需要写RabbitListeners或RabbitListener注解
@Component
public class MyRabbitListener {

    @RabbitListeners({@RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory"),
        @RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory")})
    @RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory")
    public void recive(){

    }
}

  方法上标注RabbitListeners和RabbitListener注解,此时方法上RabbitHandler无效。因为源码不会去解析。
  RabbitListeners,RabbitListener,及RabbitHandler注解的使用后,我们接着看之前的代码。

private void processMultiMethodListeners(RabbitListener[] classLevelListeners, Method[] multiMethods,
		Object bean, String beanName) {
	List<Method> checkedMethods = new ArrayList<Method>();
	//遍历所有的方法 ,如果是jdk代理,获取父接口的方法
	for (Method method : multiMethods) {
		checkedMethods.add(checkProxy(method, bean));
	}
	for (RabbitListener classLevelListener : classLevelListeners) {
		MultiMethodRabbitListenerEndpoint endpoint = new MultiMethodRabbitListenerEndpoint(checkedMethods, bean);
		endpoint.setBeanFactory(this.beanFactory);
		processListener(endpoint, classLevelListener, bean, bean.getClass(), beanName);
	}
}

protected void processAmqpListener(RabbitListener rabbitListener, Method method, Object bean, String beanName) {
	//如果是jdk动态代理,获取其父接口的方法
	Method methodToUse = checkProxy(method, bean);
	MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
	endpoint.setMethod(methodToUse);
	endpoint.setBeanFactory(this.beanFactory);
	processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
}

  发现RabbitListeners或RabbitListener注解在类上,还是在方法上的区别在于一个是用MultiMethodRabbitListenerEndpoint来处理监听器,另外一个是用MethodRabbitListenerEndpoint来注册监听器,同时在注册方法时,一个是设置methods,另外一个是设置method,而MultiMethodRabbitListenerEndpoint继承MethodRabbitListenerEndpoint。源码如下

public class MultiMethodRabbitListenerEndpoint extends MethodRabbitListenerEndpoint {
	private final List<Method> methods;
	private DelegatingInvocableHandler delegatingHandler;
	public MultiMethodRabbitListenerEndpoint(List<Method> methods, Object bean) {
		this.methods = methods;
		setBean(bean);
	}

	@Override
	protected HandlerAdapter configureListenerAdapter(MessagingMessageListenerAdapter messageListener) {
		List<InvocableHandlerMethod> invocableHandlerMethods = new ArrayList<InvocableHandlerMethod>();
		for (Method method : this.methods) {
			invocableHandlerMethods.add(getMessageHandlerMethodFactory()
					.createInvocableHandlerMethod(getBean(), method));
		}
		this.delegatingHandler = new DelegatingInvocableHandler(invocableHandlerMethods, getBean(), getResolver(),
				getBeanExpressionContext());
		return new HandlerAdapter(this.delegatingHandler);
	}

}

  上述源码上看不出太多的东西,但是在invoke方法时,肯定是使用了DelegatingInvocableHandler代理的,这个疑问先留在这里。后面,我们再来分析。
  接下来。我们接着来看processListener方法。

protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,
		Object adminTarget, String beanName) {
	endpoint.setBean(bean);
	// messageHandlerMethodFactory默认为RabbitHandlerMethodFactoryAdapter
	endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
	endpoint.setId(getEndpointId(rabbitListener));
	//设置队列名称
	endpoint.setQueueNames(resolveQueues(rabbitListener));
	String group = rabbitListener.group();
	//如果提供,此侦听器的侦听器容器将添加到以该值为名称的 bean 中,
	//类型为 {@code Collection{<MessageListenerContainer>}。 
	//例如,这允许对集合进行迭代以启动/停止容器的子集 。@return组的 bean 名称。@自 1.5
	if (StringUtils.hasText(group)) {
		Object resolvedGroup = resolveExpression(group);
		if (resolvedGroup instanceof String) {
			endpoint.setGroup((String) resolvedGroup);
		}
	}
	// 设置容器中的单个消费者是否将独占使用队列,防止其他消费者从队列接收消息。 @param 独占 {@code boolean} 标志。
	endpoint.setExclusive(rabbitListener.exclusive());
	//设置处理的优先级
	String priority = resolve(rabbitListener.priority());
	if (StringUtils.hasText(priority)) {
		try {
			endpoint.setPriority(Integer.valueOf(priority));
		}
		catch (NumberFormatException ex) {
			throw new BeanInitializationException("Invalid priority value for " +
					rabbitListener + " (must be an integer)", ex);
		}
	}
	//设置rabbitAdmin
	String rabbitAdmin = resolve(rabbitListener.admin());
	if (StringUtils.hasText(rabbitAdmin)) {
		Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve RabbitAdmin by bean name");
		try {
			endpoint.setAdmin(this.beanFactory.getBean(rabbitAdmin, RabbitAdmin.class));
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new BeanInitializationException("Could not register rabbit listener endpoint on [" +
					adminTarget + "], no " + RabbitAdmin.class.getSimpleName() + " with id '" +
					rabbitAdmin + "' was found in the application context", ex);
		}
	}
	RabbitListenerContainerFactory<?> factory = null;
	String containerFactoryBeanName = resolve(rabbitListener.containerFactory());
	//设置RabbitListenerContainerFactory,这个之前举例过
	if (StringUtils.hasText(containerFactoryBeanName)) {
		Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
		try {
			factory = this.beanFactory.getBean(containerFactoryBeanName, RabbitListenerContainerFactory.class);
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new BeanInitializationException("Could not register rabbit listener endpoint on [" +
					adminTarget + "] for bean " + beanName + ", no " +
					RabbitListenerContainerFactory.class.getSimpleName() + " with id '" +
					containerFactoryBeanName + "' was found in the application context", ex);
		}
	}
	//注册RabbitListenerEndpoint对象
	this.registrar.registerEndpoint(endpoint, factory);

  上面都是一些设置队列参数设置相关的操作,首先,我们来看,设置队列的名字的方法resolveQueues。

private String[] resolveQueues(RabbitListener rabbitListener) {
	String[] queues = rabbitListener.queues();
	QueueBinding[] bindings = rabbitListener.bindings();
	if (queues.length > 0 && bindings.length > 0) {
		throw new BeanInitializationException("@RabbitListener can have 'queues' or 'bindings' but not both");
	}
	List<String> result = new ArrayList<String>();
	if (queues.length > 0) {
		for (int i = 0; i < queues.length; i++) {
			//解析表达式,如#{rabbitTestQueuebbbb.name},解析表达式获取队列名称
			Object resolvedValue = resolveExpression(queues[i]);
			resolveAsString(resolvedValue, result);
		}
	}
	else {
		//如果配置了bindings,而不是queues,注册队列,交换器
		return registerBeansForDeclaration(rabbitListener);
	}
	return result.toArray(new String[result.size()]);
}

  这个方法告诉我们,设置队列名字有两种方式,如下

public class MyRabbitListener {
    @RabbitListener(queues = "#{rabbitTestQueuebbbb.name}")
    public void recive() {
    }
}
public class MyRabbitListener {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "HORSE_ANNOTATION_QUEUE", durable = "true"),
            exchange = @Exchange(value = "HORSE_ANNOTATION_EXCHANGE", ignoreDeclarationExceptions = "true"),
            key = "HORSE_ANNOTATION_KEY"))
    public void recive() {

    }
}

  第一种情况,直接设置队列名称,看起来比较简单,实则不易,就像之前提到的,将#{rabbitTestQueue.name}表达式,解析成rabbitTestQueue队列name属性值,而第二种情况,都不需要在其他地方声明Queue了,直接在注解中声明队列,接下来,我们看其内部实现。

private String[] registerBeansForDeclaration(RabbitListener rabbitListener) {
	List<String> queues = new ArrayList<String>();
	if (this.beanFactory instanceof ConfigurableBeanFactory) {
		for (QueueBinding binding : rabbitListener.bindings()) {
			String queueName = declareQueue(binding);
			queues.add(queueName);
			//申明交换器,绑定队列
			declareExchangeAndBinding(binding, queueName);
		}
	}
	return queues.toArray(new String[queues.size()]);
}

private String declareQueue(QueueBinding binding) {
	org.springframework.amqp.rabbit.annotation.Queue bindingQueue = binding.value();
	String queueName = (String) resolveExpression(bindingQueue.value());
	boolean exclusive = false;
	boolean autoDelete = false;
	if (!StringUtils.hasText(queueName)) {
		//设置队列的唯一名称
		queueName = UUID.randomUUID().toString();
		// exclusive: 仅创建者可以使用的私有队列,断开后自动删除
		if (!StringUtils.hasText(bindingQueue.exclusive())
				|| resolveExpressionAsBoolean(bindingQueue.exclusive())) {
			exclusive = true;
		}
		// auto_delete: 当所有消费客户端连接断开后,是否自动删除队列
		if (!StringUtils.hasText(bindingQueue.autoDelete())
				|| resolveExpressionAsBoolean(bindingQueue.autoDelete())) {
			autoDelete = true;
		}
	}
	else {
		exclusive = resolveExpressionAsBoolean(bindingQueue.exclusive());
		autoDelete = resolveExpressionAsBoolean(bindingQueue.autoDelete());
	}
	Queue queue = new Queue(queueName,
			resolveExpressionAsBoolean(bindingQueue.durable()),
			exclusive,
			autoDelete,
			resolveArguments(bindingQueue.arguments()));
	queue.setIgnoreDeclarationExceptions(resolveExpressionAsBoolean(bindingQueue.ignoreDeclarationExceptions()));
	((ConfigurableBeanFactory) this.beanFactory).registerSingleton(queueName + ++this.increment, queue);
	return queueName;
}

  理解上面代码,其实还是相对简单的,就是从QueueBinding注解中获取Queue信息,获取队列的属性,如exclusive,autoDelete及arguments参数,声明队列,注册到容器中。接下来,我们来看看,交换器如何绑定。

private void declareExchangeAndBinding(QueueBinding binding, String queueName) {
	org.springframework.amqp.rabbit.annotation.Exchange bindingExchange = binding.exchange();
	//获取交换器名字
	String exchangeName = resolveExpressionAsString(bindingExchange.value(), "@Exchange.exchange");
	//获取交换器类型
	String exchangeType = resolveExpressionAsString(bindingExchange.type(), "@Exchange.type");
	//获取路由键
	String routingKey = resolveExpressionAsString(binding.key(), "@QueueBinding.key");
	Exchange exchange;
	Binding actualBinding;
	//声明DIRECT交换器
	if (exchangeType.equals(ExchangeTypes.DIRECT)) {
		exchange = directExchange(bindingExchange, exchangeName);
		actualBinding = new Binding(queueName, DestinationType.QUEUE, exchangeName, routingKey,
				resolveArguments(binding.arguments()));
	}
	//声明FANOUT交换器
	else if (exchangeType.equals(ExchangeTypes.FANOUT)) {
		exchange = fanoutExchange(bindingExchange, exchangeName);
		actualBinding = new Binding(queueName, DestinationType.QUEUE, exchangeName, "",
				resolveArguments(binding.arguments()));
	}
	//声明TOPIC交换器
	else if (exchangeType.equals(ExchangeTypes.TOPIC)) {
		exchange = topicExchange(bindingExchange, exchangeName);
		actualBinding = new Binding(queueName, DestinationType.QUEUE, exchangeName, routingKey,
				resolveArguments(binding.arguments()));
	}
	//声明HEADERS交换器
	else if (exchangeType.equals(ExchangeTypes.HEADERS)) {
		exchange = headersExchange(bindingExchange, exchangeName);
		actualBinding = new Binding(queueName, DestinationType.QUEUE, exchangeName, routingKey,
				resolveArguments(binding.arguments()));
	}
	else {
		throw new BeanInitializationException("Unexpected exchange type: " + exchangeType);
	}
	AbstractExchange abstractExchange = (AbstractExchange) exchange;
	//internal 设置是否是RabbitMQ内部使用,默认false。如果设置为 true ,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
	abstractExchange.setInternal(resolveExpressionAsBoolean(bindingExchange.internal()));
	abstractExchange.setDelayed(resolveExpressionAsBoolean(bindingExchange.delayed()));
	abstractExchange.setIgnoreDeclarationExceptions(resolveExpressionAsBoolean(bindingExchange.ignoreDeclarationExceptions()));
	((AbstractDeclarable) actualBinding)
			.setIgnoreDeclarationExceptions(resolveExpressionAsBoolean(binding.ignoreDeclarationExceptions()));
	//注册交换器
	((ConfigurableBeanFactory) this.beanFactory).registerSingleton(exchangeName + ++this.increment,
			exchange);
	// 注册绑定
	((ConfigurableBeanFactory) this.beanFactory).registerSingleton(
			exchangeName + "." + queueName + ++this.increment, actualBinding);
}


private Exchange directExchange(org.springframework.amqp.rabbit.annotation.Exchange bindingExchange,
		String exchangeName) {
	return new DirectExchange(exchangeName,
			//durable 设置是否持久 durab 设置为 true 表示持久化, 反之是非持久,设置为true则将Exchange存盘,即使服务器重启数据也不会丢失
			resolveExpressionAsBoolean(bindingExchange.durable()),
			//autoDelete 设置是否自动删除,当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange,简单来说也就是如果该Exchange没有和任何队列Queue绑定则删除
			resolveExpressionAsBoolean(bindingExchange.autoDelete()),
			resolveArguments(bindingExchange.arguments()));
}

private Exchange fanoutExchange(org.springframework.amqp.rabbit.annotation.Exchange bindingExchange,
		String exchangeName) {
	return new FanoutExchange(exchangeName,
			resolveExpressionAsBoolean(bindingExchange.durable()),
			resolveExpressionAsBoolean(bindingExchange.autoDelete()),
			resolveArguments(bindingExchange.arguments()));
}

private Exchange topicExchange(org.springframework.amqp.rabbit.annotation.Exchange bindingExchange,
		String exchangeName) {
	return new TopicExchange(exchangeName,
			resolveExpressionAsBoolean(bindingExchange.durable()),
			resolveExpressionAsBoolean(bindingExchange.autoDelete()),
			resolveArguments(bindingExchange.arguments()));
}

private Exchange headersExchange(org.springframework.amqp.rabbit.annotation.Exchange bindingExchange,
		String exchangeName) {
	return new HeadersExchange(exchangeName,
			resolveExpressionAsBoolean(bindingExchange.durable()),
			resolveExpressionAsBoolean(bindingExchange.autoDelete()),
			resolveArguments(bindingExchange.arguments()));
}

  如果对RabbitMQ熟练使用的小伙伴,对上面的理解不难,无非就是注册交换器,注册绑定关系而已。
  关于group的使用,目前也只知道,设置了MethodRabbitListenerEndpoint的group属性为注解配置的内容,而用来做什么,目前也还不清楚,我们接着分析源码。后面有机会,再来看怎样使用。接下来,我们来分析registerEndpoint方法 。

public void registerEndpoint(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory) {
	Assert.notNull(endpoint, "Endpoint must be set");
	Assert.hasText(endpoint.getId(), "Endpoint id must be set");
	AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
	synchronized (this.endpointDescriptors) {
		if (this.startImmediately) { 
			//注册消息监听器容器
			this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
					resolveContainerFactory(descriptor), true);
		}
		else {
			this.endpointDescriptors.add(descriptor);
		}
	}
}

  默认情况下startImmediately为false,因此,RabbitListenerEndpoint被封装成AmqpListenerEndpointDescriptor对象存储于endpointDescriptors集合中。再来看RabbitListenerAnnotationBeanPostProcessor实现了SmartInitializingSingleton接口,因此调用beanFactory的preInstantiateSingletons方法中。
在这里插入图片描述
  只要实现了SmartInitializingSingleton接口,都会调用其afterSingletonsInstantiated方法 。接下来,我们来看看RabbitListenerAnnotationBeanPostProcessor的afterSingletonsInstantiated方法内部实现。

public void afterSingletonsInstantiated() {
	//设置RabbitListenerEndpointRegistrar的beanFactory
	this.registrar.setBeanFactory(this.beanFactory);
	
	if (this.beanFactory instanceof ListableBeanFactory) {
		Map<String, RabbitListenerConfigurer> instances =
				((ListableBeanFactory) this.beanFactory).getBeansOfType(RabbitListenerConfigurer.class);
		for (RabbitListenerConfigurer configurer : instances.values()) {
			configurer.configureRabbitListeners(this.registrar);
		}
	}
	if (this.registrar.getEndpointRegistry() == null) {
		if (this.endpointRegistry == null) {
			Assert.state(this.beanFactory != null,
					"BeanFactory must be set to find endpoint registry by bean name");
			this.endpointRegistry = this.beanFactory.getBean(
					org.springframework.amqp.rabbit.config.internalRabbitListenerEndpointRegistry,
					RabbitListenerEndpointRegistry.class);
		}
		//默认设置endpointRegistry为RabbitListenerEndpointRegistry
		this.registrar.setEndpointRegistry(this.endpointRegistry);
	}

	//设置registrar的默认消息监听器的容器名称为rabbitListenerContainerFactory
	if (this.containerFactoryBeanName != null) {
		this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName);
	}
	//设置消息方法处理器工厂
	MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
	if (handlerMethodFactory != null) {
		this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
	}
	//registrar的afterPropertiesSet方法
	this.registrar.afterPropertiesSet();
	this.typeCache.clear();
}

  上面的其他代码还好,都是直接设置bean的属性,我们来举个例子,假如,我需要设置messageHandlerMethodFactory,怎么办呢?

@Component
public class MyRabbitListenerConfigurer implements RabbitListenerConfigurer {
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        MessageHandlerMethodFactory messageHandlerMethodFactory = createDefaultMessageHandlerMethodFactory(registrar);
        registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
    }


    private MessageHandlerMethodFactory createDefaultMessageHandlerMethodFactory(RabbitListenerEndpointRegistrar registrar ) {
        DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory();
        defaultFactory.setBeanFactory((BeanFactory) getFieldValueByFieldName("beanFactory", registrar));
        defaultFactory.afterPropertiesSet();
        return defaultFactory;
    }

    private Object getFieldValueByFieldName(String fieldName, Object object) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            //设置对象的访问权限,保证对private的属性的访问
            field.setAccessible(true);
            return  field.get(object);
        } catch (Exception e) {

            return null;
        }
    }
}

  显然,通过自定义一个MyRabbitListenerConfigurer类,实现RabbitListenerConfigurer接口,即可得到RabbitListenerEndpointRegistrar,此时就可以对RabbitListenerEndpointRegistrar的属性方法为所欲为了。

public void afterPropertiesSet() {
	registerAllEndpoints();
}

protected void registerAllEndpoints() {
	synchronized (this.endpointDescriptors) {
		for (AmqpListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
			this.endpointRegistry.registerListenerContainer(
					descriptor.endpoint, resolveContainerFactory(descriptor));
		}
		this.startImmediately = true;  // trigger immediate startup
	}
}

public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory) {
	registerListenerContainer(endpoint, factory, false);
}

  上面代码的核心逻辑就就是遍历endpointDescriptors,注册消息监听器,下面是注册消息监听器代码,但是遗憾的是,startImmediately参数为false,因此不会执行startIfNecessary方法 。

public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
                                      boolean startImmediately) {
	Assert.notNull(endpoint, "Endpoint must not be null");
	Assert.notNull(factory, "Factory must not be null");

	String id = endpoint.getId();
	Assert.hasText(id, "Endpoint id must not be empty");
	synchronized (this.listenerContainers) {
		Assert.state(!this.listenerContainers.containsKey(id),
				"Another endpoint is already registered with id '" + id + "'");
		//创建消息监听器
		MessageListenerContainer container = createListenerContainer(endpoint, factory);
		//将消息监听器保存到容器中
		this.listenerContainers.put(id, container);
		//如果RabbitListener分组
		if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
			List<MessageListenerContainer> containerGroup;
			if (this.applicationContext.containsBean(endpoint.getGroup())) {
				containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
			}
			else {
				containerGroup = new ArrayList<MessageListenerContainer>();
				this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
			}
			containerGroup.add(container);
		}
		//如果startImmediately为true,则容器创建好之后立即启动
		//可惜,默认为false
		if (startImmediately) {
			startIfNecessary(container);
		}
	}
}

  如果在RabbitListener注解中配置了group属性,则会创建一个MessageListenerContainer集合,以group为bean的名称保存到容器中,RabbitListener的group属性怎么用,目前还不知道,等后面看到使用,我们再来举例,但是上面关键代码还是创建消息监听器容器。下面来看看创建消息监听器容器的逻辑。

protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,
		RabbitListenerContainerFactory<?> factory) {
	//创建消息监听器容器
	MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);

	if (listenerContainer instanceof InitializingBean) {
		try {
			((InitializingBean) listenerContainer).afterPropertiesSet();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Failed to initialize message listener container", ex);
		}
	}

	int containerPhase = listenerContainer.getPhase();
	if (containerPhase < Integer.MAX_VALUE) {  // a custom phase value
		if (this.phase < Integer.MAX_VALUE && this.phase != containerPhase) {
			throw new IllegalStateException("Encountered phase mismatch between container factory definitions: " +
					this.phase + " vs " + containerPhase);
		}
		this.phase = listenerContainer.getPhase();
	}

	return listenerContainer;
}

  下面是创建消息监听器的代码逻辑。

public C createListenerContainer(RabbitListenerEndpoint endpoint) {
	//创建SimpleMessageListenerContainer实例
	C instance = createContainerInstance();
	//将注解RabbitListener上配置的设置到容器实例中
	if (this.connectionFactory != null) {
		//Rabbit 的connection工厂 
		instance.setConnectionFactory(this.connectionFactory);
	}
	if (this.errorHandler != null) {
		//错误处理器
		instance.setErrorHandler(this.errorHandler);
	}
	if (this.messageConverter != null) {
		//消息转换器
		instance.setMessageConverter(this.messageConverter);
	}
	if (this.acknowledgeMode != null) {
		//消息确认模式,确认机制  manual 手工   auto 自动 none 不做处理
		instance.setAcknowledgeMode(this.acknowledgeMode);
	}
	if (this.channelTransacted != null) {
		//是否开启信道事务
		instance.setChannelTransacted(this.channelTransacted);
	}
	if (this.autoStartup != null) {
		//消息监听器容器是否随着Spring 容器启动而启动,默认为true
		instance.setAutoStartup(this.autoStartup);
	}
	if (this.phase != null) {
		instance.setPhase(this.phase);
	}
	instance.setListenerId(endpoint.getId());
	//为容器设置queues,exclusive,rabbitamin参数,arguments参数
	endpoint.setupListenerContainer(instance);
	//将SimpleRabbitListenerContainerFactory中配置的内容设置到容器中
	initializeContainer(instance);
	return instance;
}

protected SimpleMessageListenerContainer createContainerInstance() {
	return new SimpleMessageListenerContainer();
}

  上面主要为容器设置链接工厂,错误处理器,消息转换器,是否手动确认,信道事务,是否自动启动SimpleMessageListenerContainer,接下来看queues,exclusive,rabbitamin参数,arguments参数的等参数值设置。

public void setupListenerContainer(MessageListenerContainer listenerContainer) {
	SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) listenerContainer;

	boolean queuesEmpty = getQueues().isEmpty();
	boolean queueNamesEmpty = getQueueNames().isEmpty();
	if (!queuesEmpty && !queueNamesEmpty) {
		throw new IllegalStateException("Queues or queue names must be provided but not both for " + this);
	}
	//设置queues
	if (queuesEmpty) {
		Collection<String> names = getQueueNames();
		container.setQueueNames(names.toArray(new String[names.size()]));
	}
	else {
		Collection<Queue> instances = getQueues();
		container.setQueues(instances.toArray(new Queue[instances.size()]));
	}
	//设置队列的exclusive
	container.setExclusive(isExclusive());
	if (getPriority() != null) {
		Map<String, Object> args = new HashMap<String, Object>();
		args.put("x-priority", getPriority());
		container.setConsumerArguments(args);
	}

	if (getAdmin() != null) {
		//设置RabbitAdmin
		container.setRabbitAdmin(getAdmin());
	}
	setupMessageListener(listenerContainer);
}

  上面主要对listenerContainer容器中的基本参数设置,没有什么太多的技术含量,只不过将之前注解中解析得到的参数设置到listenerContainer中而已。

private void setupMessageListener(MessageListenerContainer container) {
	MessageListener messageListener = createMessageListener(container);
	Assert.state(messageListener != null, "Endpoint [" + this + "] must provide a non null message listener");
	container.setupMessageListener(messageListener);
}

protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
	Assert.state(this.messageHandlerMethodFactory != null,
			"Could not create message listener - MessageHandlerMethodFactory not set");
	MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
	
	messageListener.setHandlerMethod(configureListenerAdapter(messageListener));
	String replyToAddress = getDefaultReplyToAddress();
	if (replyToAddress != null) {
		messageListener.setResponseAddress(replyToAddress);
	}
	
	MessageConverter messageConverter = container.getMessageConverter();
	if (messageConverter != null) {
		//设置消息转换器
		messageListener.setMessageConverter(messageConverter);
	}
	if (getBeanResolver() != null) {
		messageListener.setBeanResolver(getBeanResolver());
	}
	return messageListener;
}

protected MessagingMessageListenerAdapter createMessageListenerInstance() {
	return new MessagingMessageListenerAdapter(this.bean, this.method);
}

public MessagingMessageListenerAdapter(Object bean, Method method) {
	this.messagingMessageConverter = new MessagingMessageConverterAdapter(bean, method);
}

private MessagingMessageConverterAdapter(Object bean, Method method) {
	this.bean = bean;
	this.method = method;
	this.inferredArgumentType = determineInferredType();
	if (logger.isDebugEnabled() && this.inferredArgumentType != null) {
		logger.debug("Inferred argument type for " + method.toString() + " is " + this.inferredArgumentType);
	}
}
protected HandlerAdapter configureListenerAdapter(MessagingMessageListenerAdapter messageListener) {
	InvocableHandlerMethod invocableHandlerMethod =
			this.messageHandlerMethodFactory.createInvocableHandlerMethod(getBean(), getMethod());
	return new HandlerAdapter(invocableHandlerMethod);
}

public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
	InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(bean, method);
	handlerMethod.setMessageMethodArgumentResolvers(this.argumentResolvers);
	return handlerMethod;
}

public HandlerAdapter(InvocableHandlerMethod invokerHandlerMethod) {
	this.invokerHandlerMethod = invokerHandlerMethod;
	this.delegatingHandler = null;
}

  上面的代码非常繁锁,但是又不得不提,因为后面的很多代码都需要用到上面的关系及配置,我们先来理一理他们之间的关系。首先创建了一个SimpleMessageListenerContainer容器,然后为这个容器设置各种注解中配置的参数,再创建了一个MessagingMessageListenerAdapter对象,再创建了一个InvocableHandlerMethod对象,这个对象存储了消息监听器的Methdod和Bean,同时为InvocableHandlerMethod设置argumentResolvers【方法参数解析器】,这些argumentResolvers有什么用呢?实现原理和Spring MVC一样,从RabbitMQ 的message参数中取出相应的值,封装到消息监听器方法参数中。然后再创建HandlerAdapter对象,将InvocableHandlerMethod对象设置到HandlerAdapter的invokerHandlerMethod属性中,同时将HandlerAdapter对象设置到MessagingMessageListenerAdapter的handlerMethod属性中,此时再将SimpleMessageListenerContainer容器中的messageConverter设置到MessagingMessageListenerAdapter对象的属性中。 同时再将MessagingMessageListenerAdapter对象设置成SimpleMessageListenerContainer容器的messageListener属性。接下来,就来看initializeContainer方法,细心的小伙伴肯定会发现,initializeContainer方法,无非是将SimpleRabbitListenerContainerFactory中的配置信息设置到SimpleMessageListenerContainer的相应属性中,而SimpleRabbitListenerContainerFactory可以是系统定义的,如果系统定义的,则是读取yml相关rabbit相关配置,如果是用户自定义的,则读取用户手动设置的相关信息到SimpleMessageListenerContainer容器中,虽然对象之前的关系有点繁锁,但是他们之间的职责是很明确,SimpleMessageListenerContainer则是所有的配置都配置到这个对象中,HandlerAdapter主要是处理消息和method的适配关系,所以其内部有一堆argumentResolvers(参数解析器),而MessagingMessageListenerAdapter主要是消息的转换,监听方法的调用,以及返回结果的处理,下面来看initializeContainer的内部实现。

protected void initializeContainer(SimpleMessageListenerContainer instance) {
	super.initializeContainer(instance);

	if (this.applicationContext != null) {
		instance.setApplicationContext(this.applicationContext);
	}
	if (this.taskExecutor != null) {
		instance.setTaskExecutor(this.taskExecutor);
	}
	if (this.transactionManager != null) {
		instance.setTransactionManager(this.transactionManager);
	}
	if (this.txSize != null) {
		//确认模式为AUTO时,在acks之间处理的消息数.如果大于预取,则预取将增加到此值
		instance.setTxSize(this.txSize);
	}
	if (this.concurrentConsumers != null) {
		//侦听器调用者线程的最小数量。
		instance.setConcurrentConsumers(this.concurrentConsumers);
	}
	if (this.maxConcurrentConsumers != null) {
		//调用者线程的最大数量
		instance.setMaxConcurrentConsumers(this.maxConcurrentConsumers);
	}
	if (this.startConsumerMinInterval != null) {
		//在消费者扩容时,创建新消费者的时间间隔
		//扩消费者时,两个新消费者创建时间间隔必需大于这个值 
		instance.setStartConsumerMinInterval(this.startConsumerMinInterval);
	}
	if (this.stopConsumerMinInterval != null) {
		//有消费者空闲时,需要关闭消费者,两个消费者关闭
		//的最小时间间隔
		instance.setStopConsumerMinInterval(this.stopConsumerMinInterval);
	}
	if (this.consecutiveActiveTrigger != null) {
		//创建新消费者触发器数
		instance.setConsecutiveActiveTrigger(this.consecutiveActiveTrigger);
	}
	if (this.consecutiveIdleTrigger != null) {
		//关闭空闲消费者的触发器数,这几个参数有什么作用,在后面的消费者扩容时,我们再来分析这几个参数的作用
		instance.setConsecutiveIdleTrigger(this.consecutiveIdleTrigger);
	}
	if (this.prefetchCount != null) {
		//在单个请求中处理的消息个数,他应该大于等于事务数量
		instance.setPrefetchCount(this.prefetchCount);
	}
	if (this.receiveTimeout != null) {
		//receive()方法的超时时间
		instance.setReceiveTimeout(this.receiveTimeout);
	}
	if (this.defaultRequeueRejected != null) {
		//投递失败时是否重新排队
		instance.setDefaultRequeueRejected(this.defaultRequeueRejected);
	}
	if (this.adviceChain != null) {
		instance.setAdviceChain(this.adviceChain);
	}
	if (this.recoveryBackOff != null) {
		instance.setRecoveryBackOff(this.recoveryBackOff);
	}
	if (this.mismatchedQueuesFatal != null) {
		//当mismatchedQueuesFatal为true时,容器中有且只允许有一个rabbitAdmin
		instance.setMismatchedQueuesFatal(this.mismatchedQueuesFatal);
	}
	if (this.missingQueuesFatal != null) {
		//如果容器声明的队列在代理上不可用,则是否失败;and /or如果在运行时删除一个或多个队列,是否停止容器。
		instance.setMissingQueuesFatal(this.missingQueuesFatal);
	}
	if (this.consumerTagStrategy != null) {
		//上线文的使用者标识的策略器 consumerTag: 客户端生成的用于建立上线文的使用者标识
		instance.setConsumerTagStrategy(this.consumerTagStrategy);
	}
	if (this.idleEventInterval != null) {
		//队列空闲的时间(毫秒为单位),如果空闲时间超过这个时间,则发布一个事件消息
		instance.setIdleEventInterval(this.idleEventInterval);
	}
	if (this.applicationEventPublisher != null) {
		//设置消息事件发布者
		instance.setApplicationEventPublisher(this.applicationEventPublisher);
	}
}

  其实上面一大段代码,非常繁锁,但是整个过程都是围绕着SimpleMessageListenerContainer容器转,将yml中的配置信息,或用户自定义的SimpleRabbitListenerContainerFactory中的配置信息,或者注解中定义的配置信息都设置到SimpleMessageListenerContainer容器的属性中,最终,生成的容器保存到RabbitListenerEndpointRegistry的listenerContainers中,此时可能很多小伙伴都被上面的代码绕晕了,但是我们需要明白一个对应关系,RabbitListener-RabbitListenerEndpoint-SimpleMessageListenerContainer他们之间是一对一的关系,什么意思呢?每一个被RabbitListener修饰的方法或被RabbitListener修饰的类下所有方法对应一个RabbitListenerEndpoint,而每个RabbitListenerEndpoint对应一个SimpleMessageListenerContainer容器,既然知道了这一点,下面我们来分析一个例子。

@RabbitListener(queues = "#{rabbitTestQueue.name}",containerFactory = "simpleRabbitListenerContainerFactory")
public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {

}
@RabbitListener(queues = "#{rabbitTestQueuebbbb.name}",containerFactory = "simpleRabbitListenerContainerFactory")
public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {

}

@Bean(name = "simpleRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(5);
    listenerContainerFactory.setMaxConcurrentConsumers(5);
    listenerContainerFactory.setPrefetchCount(5);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//开启消息确认机制
    return listenerContainerFactory;
}

  从上面例子中,我们知道消费者rabbitTestQueue和rabbitTestQueuebbbb,两者到底是创建两个SimpleMessageListenerContainer容器,还是创建一个呢?聪明的小伙伴肯定知道,是创建两个,SimpleRabbitListenerContainerFactory只是一个Rabbit属性配置的工厂,在initializeContainer方法中,每个RabbitListener修饰的方法或类都对应一个SimpleMessageListenerContainer容器,而每个容器都会将其对应的SimpleRabbitListenerContainerFactory对应的属性信息考呗到容器中来,既然这样,我们就不会产生误解,误认为rabbitTestQueue和rabbitTestQueuebbbb两个队列共用5个消费者。而实际上是每个队列都创建5个消费者等待消费。而SimpleRabbitListenerContainerFactory只是一个配置而已,不同的队列可以共用这个配置。
  创建好SimpleMessageListenerContainer容器后,调用了容器的afterPropertiesSet方法,接下来,我们来看看容器的afterPropertiesSet方法内部实现。

public final void afterPropertiesSet() {
	super.afterPropertiesSet();
	Assert.state(
			this.exposeListenerChannel || !getAcknowledgeMode().isManual(),
			"You cannot acknowledge messages manually if the channel is not exposed to the listener "
					+ "(please check your configuration and set exposeListenerChannel=true or acknowledgeMode!=MANUAL)");
	Assert.state(
			!(getAcknowledgeMode().isAutoAck() && isChannelTransacted()),
			"The acknowledgeMode is NONE (autoack in Rabbit terms) which is not consistent with having a "
					+ "transactional channel. Either use a different AcknowledgeMode or make sure channelTransacted=false");
	validateConfiguration();
	initialize();
}


public void initialize() {
	try {
		synchronized (this.lifecycleMonitor) {
			this.lifecycleMonitor.notifyAll();
		}
		doInitialize();
	}
	catch (Exception ex) {
		throw convertRabbitAccessException(ex);
	}
}

protected void doInitialize() throws Exception {
	//当队列不存在QueuesNotAvailableException异常时,是抛出异常还是关闭容器的设置
	checkMissingQueuesFatal();
	if (!this.isExposeListenerChannel() && this.transactionManager != null) {
		logger.warn("exposeListenerChannel=false is ignored when using a TransactionManager");
	}
	//当用户没有设置taskExecutor,并且消息监听器容器有beanName时,设置taskExecutor为SimpleAsyncTaskExecutor
	if (!this.taskExecutorSet && StringUtils.hasText(this.getBeanName())) {
		this.taskExecutor = new SimpleAsyncTaskExecutor(this.getBeanName() + "-");
		this.taskExecutorSet = true;
	}
	initializeProxy();
	if (this.transactionManager != null) {
		//如果事务管理器不为空,并且信道事务为false
		if (!isChannelTransacted()) {
			logger.debug("The 'channelTransacted' is coerced to 'true', when 'transactionManager' is provided");
			setChannelTransacted(true);
		}
	}
}

  对于容器的afterPropertiesSet方法,真正做事情的方法是doInitialize方法,不过大家有没有发现一个规律,在Spring中,所有实际做事情的代码都是以do开头的,如doScan,doScanClassPath,doLoadBeanDefinitions,而在这些方法之前都是一些校验和准备工作,而这里也是一样。但是我们需要注意有两点,第一点就是beanName是在什么时候设置的呢?因为SimpleMessageListenerContainer实现了BeanNameAware接口,在BeanNameAware接口中有一个setBeanName(String name)方法,也就是说,SimpleMessageListenerContainer是注册到容器中,并由容器启动,那肯定会调用setBeanName()方法,为bean设置名称,而如果是自己new 的SimpleMessageListenerContainer,默认情况下beanName为null,因此,不会设置taskExecutor,第二个注意点是initializeProxy方法,先来看看initializeProxy方法的源码。

private void initializeProxy() {
	if (this.adviceChain.length == 0) {
		return;
	}
	ProxyFactory factory = new ProxyFactory();
	for (Advice advice : getAdviceChain()) {
		factory.addAdvisor(new DefaultPointcutAdvisor(Pointcut.TRUE, advice));
	}
	factory.setProxyTargetClass(false);
	factory.addInterface(ContainerDelegate.class);
	factory.setTarget(this.delegate);
	this.proxy = (ContainerDelegate) factory.getProxy(ContainerDelegate.class.getClassLoader());
}
private Advice[] getAdviceChain() {
	return this.adviceChain;
}

  创不创建代理的决定因素是有没有adviceChain,而adviceChain的来源于我们手动设定,如initializeContainer方法中的setAdviceChain方法。另一方面来源于消息消费异常的重试机制的设定,如setAdviceChain,那下面,我们就来看一个例子。体会一下initializeProxy方法和作用。

  我们分析这么久,最终得到创建好的SimpleMessageListenerContainer存储于RabbitListenerEndpointRegistry的listenerContainers属性中,那这个属性什么时候用呢?

  1. 准备SimpleRabbitListenerContainerFactory
@Bean(name = "simpleRetryRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRetryRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(1);
    listenerContainerFactory.setPrefetchCount(1);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);//开启消息确认机制

    RabbitProperties.ListenerRetry retryConfig = new RabbitProperties.ListenerRetry();
    retryConfig.setEnabled(true);
    retryConfig.setMaxAttempts(3);
    retryConfig.setInitialInterval(3000);
    retryConfig.setMaxInterval(5000);

    if (retryConfig.isEnabled()) {
        RetryInterceptorBuilder<?> builder = (retryConfig.isStateless()
                ? RetryInterceptorBuilder.stateless()
                : RetryInterceptorBuilder.stateful());
        builder.maxAttempts(retryConfig.getMaxAttempts());
        builder.backOffOptions(retryConfig.getInitialInterval(),
                retryConfig.getMultiplier(), retryConfig.getMaxInterval());
        MessageRecoverer recoverer = (new RejectAndDontRequeueRecoverer());
        builder.recoverer(recoverer);
        //设置增强
        listenerContainerFactory.setAdviceChain(builder.build());
    }
    return listenerContainerFactory;
}
  1. 创建重试消费者
@Component
@Slf4j
public class RabbitRetryistener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestRestryQueue.name}",containerFactory = "simpleRetryRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        System.out.println("-------接收到消息:" + message + ",接收时间 : " + df.format(new Date()));
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int i = 0 ;
        int j = 0;
        int c = i /j;

        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    }
}
  1. 创建生产者代码
@Value("${eb.config.rabbitQueue.retry}")
public String retryQueueName;

@RequestMapping("retryQueueNameTest")
public String retryQueueName() {
    String message = "测试 " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(retryQueueName, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}

开始测试
在这里插入图片描述

  从测试结果中可以看出,因为在消费消息时,抛出了异常,因此,我们配置的重试机制起作用了,消息重试了三次,还是抛出异常,则直接抛出异常,并确认消息,这种特性,当我们发送第三方请求时,对方抛出了404异常,此时,我们就可以使用消息的重试机制来提高请求成功率。虽然使用起来这么简单,那实现原理是什么呢?
在这里插入图片描述
  第一步,我们先来看看setAdviceChain方法,到底是设置了一个怎样的增强。

public static final class StatelessRetryInterceptorBuilder extends RetryInterceptorBuilder<RetryOperationsInterceptor> {

	private final StatelessRetryOperationsInterceptorFactoryBean factoryBean =
			new StatelessRetryOperationsInterceptorFactoryBean();

	@Override
	public RetryOperationsInterceptor build() {
		//设置factoryBean的messageRecoverer和RetryOperations
		this.applyCommonSettings(this.factoryBean);
		return this.factoryBean.getObject();
	}
	private StatelessRetryInterceptorBuilder() {
	}
}

private final RetryTemplate retryTemplate = new RetryTemplate();

protected void applyCommonSettings(AbstractRetryOperationsInterceptorFactoryBean factoryBean) {
	if (this.messageRecoverer != null) {
		factoryBean.setMessageRecoverer(this.messageRecoverer);
	}
	//默认情况下retryOperations是retryTemplate
	if (this.retryOperations != null) {
		factoryBean.setRetryOperations(this.retryOperations);
	}
	else {
		factoryBean.setRetryOperations(this.retryTemplate);
	}
}

public RetryOperationsInterceptor getObject() {

	RetryOperationsInterceptor retryInterceptor = new RetryOperationsInterceptor();
	RetryOperations retryTemplate = getRetryOperations();
	if (retryTemplate == null) {
		retryTemplate = new RetryTemplate();
	}
	retryInterceptor.setRetryOperations(retryTemplate);
	
	final MessageRecoverer messageRecoverer = getMessageRecoverer();
	retryInterceptor.setRecoverer(new MethodInvocationRecoverer<Void>() {
		public Void recover(Object[] args, Throwable cause) {
			Message message = (Message) args[1];
			if (messageRecoverer == null) {
				logger.warn("Message dropped on recovery: " + message, cause);
			}
			else {
				messageRecoverer.recover(message, cause);
			}
			return null;
		}
	});
	return retryInterceptor;
}

  我们最终得到的增强器是RetryOperationsInterceptor,接下来,我们再来看这个增强器的作用。我们生产者生产一条消息,在消费者代码中,我们打一个断点。
  从而得到方法调用栈。
在这里插入图片描述
  下面这个方法是消费消息的入口。

protected void invokeListener(Channel channel, Message message) throws Exception {
	this.proxy.invokeListener(channel, message);
}

  上面这一块的逻辑,我们要在后面再来讲解了,但是为了分析Retry的使用,所以提前在这里分析一下。因为我们设置了setAdviceChain为RetryOperationsInterceptor,因此在代理最终调用了RetryOperationsInterceptor的invoke方法,这是aop这一块的逻辑,这里就不再深入。我们先看看RetryOperationsInterceptor的invoke方法 。

public Object invoke(final MethodInvocation invocation) throws Throwable {

	String name;
	if (StringUtils.hasText(label)) {
		name = label;
	} else {
		name = invocation.getMethod().toGenericString();
	}
	final String label = name;

	RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {

		public Object doWithRetry(RetryContext context) throws Exception {
			
			context.setAttribute(RetryContext.NAME, label);
			if (invocation instanceof ProxyMethodInvocation) {
				try {
					//此时,我们需要注意的是,invocation中的method是SimpleMessageListenerContainer的ContainerDelegate中的invokeListener方法
					return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
				}
				catch (Exception e) {
					throw e;
				}
				catch (Error e) {
					throw e;
				}
				catch (Throwable e) {
					throw new IllegalStateException(e);
				}
			}
			else {
				throw new IllegalStateException(
						"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +
								"so please raise an issue if you see this exception");
			}
		}

	};

	if (recoverer != null) {
		ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(
				invocation.getArguments(), recoverer);
		//retryOperations默认是RetryTemplate
		return this.retryOperations.execute(retryCallback, recoveryCallback);
	}
	return this.retryOperations.execute(retryCallback);
}

在这里插入图片描述
  其实我们之前分析过RetryTemplate的实现,在这篇 Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析 博客中,只不过是生产者发送消息的重试实现,其实现也是大同小异,不过,还是继续分析一下吧。

public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
		RecoveryCallback<T> recoveryCallback) throws E {
	return doExecute(retryCallback, recoveryCallback, null);
}

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
		RecoveryCallback<T> recoveryCallback, RetryState state)
		throws E, ExhaustedRetryException {

	RetryPolicy retryPolicy = this.retryPolicy;
	BackOffPolicy backOffPolicy = this.backOffPolicy;
	// 创建本次重试机制的一个容器,容器中存储了重试策略
	RetryContext context = open(retryPolicy, state);
	if (this.logger.isTraceEnabled()) {
		this.logger.trace("RetryContext retrieved: " + context);
	}
	RetrySynchronizationManager.register(context);
	Throwable lastException = null;
	boolean exhausted = false;
	try {
		boolean running = doOpenInterceptors(retryCallback, context);
		if (!running) {
			throw new TerminatedRetryException(
					"Retry terminated abnormally by interceptor before first attempt");
		}
		
		BackOffContext backOffContext = null;
		Object resource = context.getAttribute("backOffContext");

		if (resource instanceof BackOffContext) {
			backOffContext = (BackOffContext) resource;
		}
		if (backOffContext == null) {
			backOffContext = backOffPolicy.start(context);
			if (backOffContext != null) {
				context.setAttribute("backOffContext", backOffContext);
			}
		}
		//根据重试策略判断本次调用是否终止,人为手动终止
		while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
			try {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Retry: count=" + context.getRetryCount());
				}
				lastException = null;
				return retryCallback.doWithRetry(context);
			}
			catch (Throwable e) {
				lastException = e;
				try {
					//注册异常时,让重试次数+1
					registerThrowable(retryPolicy, state, context, e);
				}
				catch (Exception ex) {
					throw new TerminatedRetryException("Could not register throwable",
							ex);
				}
				finally {
					doOnErrorInterceptors(retryCallback, context, e);
				}
				//如果还能重试,则根据策略等待相应的时间
				if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
					try {
						backOffPolicy.backOff(backOffContext);
					}
					catch (BackOffInterruptedException ex) {
						lastException = e;
						if (this.logger.isDebugEnabled()) {
							this.logger
									.debug("Abort retry because interrupted: count="
											+ context.getRetryCount());
						}
						throw ex;
					}
				}
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(
							"Checking for rethrow: count=" + context.getRetryCount());
				}
				if (shouldRethrow(retryPolicy, context, state)) {
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Rethrow in retry for policy: count="
								+ context.getRetryCount());
					}
					throw RetryTemplate.<E>wrapIfNecessary(e);
				}
			}
			//如果容器被关掉,则终止重试
			if (state != null && context.hasAttribute(GLOBAL_STATE)) {
				break;
			}
		}
		if (state == null && this.logger.isDebugEnabled()) {
			this.logger.debug(
					"Retry failed last attempt: count=" + context.getRetryCount());
		}

		exhausted = true;
		return handleRetryExhausted(recoveryCallback, context, state);

	}
	catch (Throwable e) {
		throw RetryTemplate.<E>wrapIfNecessary(e);
	}
	finally {
		//关闭重试机制
		close(retryPolicy, context, state, lastException == null || exhausted);
		doCloseInterceptors(retryCallback, context, lastException);
		//清除context数据
		RetrySynchronizationManager.clear();
	}
}

  上面的代码,基本上讲清楚了,重试的原理,当doExecute第一次调用时,会创建一个容器,容器中存储了重试策略及初始化一些数据,当重试第一次之后,会将重试次数累加到容器中,之后,根据策略判断是否符合重试条件,如果符合,根据重试策略等待相应的时间,当人为终止或超过重试次数时,终止重试,将目标方法抛出的异常抛出去,并清除容器中的数据。上面是一个简单的重试策略,目标方法抛出运行时异常不能超过3次,并且每次重试时间大于3秒,小于5秒,但是,我们想要一个更加复杂一点的策略,如,每次重试的时间是原来的两倍,最大时间不能超过某个值,同时,如果抛出的是空指针异常时,不再重试,如果抛出其他异常,则继续重试,比刚刚的需要麻烦一点,但是,这该怎样实现呢?

  1. 重写SimpleRabbitListenerContainerFactory
@Bean(name = "complexRetryRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory complexRetryRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(1);
    listenerContainerFactory.setPrefetchCount(1);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);//开启消息确认机制

    RabbitProperties.ListenerRetry retryConfig = new RabbitProperties.ListenerRetry();
    retryConfig.setEnabled(true);
    if (retryConfig.isEnabled()) {
        RetryTemplate retryTemplate = new RetryTemplate();
        RetryPolicy retryPolicy = new SimpleRetryPolicy(8);
        retryTemplate.setRetryPolicy(retryPolicy);
        ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
        //初始等待时间
        exponentialBackOffPolicy.setInitialInterval(1000);
        //时间等待倍数
        exponentialBackOffPolicy.setMultiplier(2);
        // 每一次等待的最大等待时间,默认30秒
        exponentialBackOffPolicy.setMaxInterval(5000);
        retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);

        MyRetryOperationsInterceptor retryInterceptor = new MyRetryOperationsInterceptor();

        retryInterceptor.setRetryOperations(retryTemplate);
        final MessageRecoverer messageRecoverer = new RejectAndDontRequeueRecoverer();
        retryInterceptor.setRecoverer(new MethodInvocationRecoverer<Void>() {
            public Void recover(Object[] args, Throwable cause) {
                Message message = (Message) args[1];
                if (messageRecoverer == null) {
                    log.info("messageRecoverer == null");
                }
                else {
                    messageRecoverer.recover(message, cause);
                }
                return null;
            }
        });
        listenerContainerFactory.setAdviceChain(retryInterceptor);
    }
    return listenerContainerFactory;
}

  在retryTemplate中,我们设置了重试策略,最多重试8次,每一次等待时间1秒,以后每次重试等待时间都是前一次的两倍,当等待时间超过最大等待时间后,以最大等待时间为准。

  1. 创建消息监听器
@Component
@Slf4j
public class RabbitSimpleRetryistener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestRestryQueue.name}",containerFactory = "simpleRetryRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("------接收到消息:" + message + ",接收时间 : " + df.format(new Date()));
        int i = 0 ;
        int j = 0;
        int c = i /j;

        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    }
}
  1. 创建生产者开始测试
@Value("${eb.config.rabbitQueue.complexRetry}")
public String complexRetry;

@RequestMapping("complexRetryTest")
public String complexRetry() {
    String message = "测试 " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(complexRetry, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}

  1. 测试结果
    在这里插入图片描述
      我们看到了,第一次等待时间1秒,第二次2秒,第三次4秒,第4次之后的都是5秒,有一种情况没有测试到,如果抛出的是空指针异常时,直接终止重试,修改消费者代码
@Component
@Slf4j
public class RabbitComplexRetryistener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestComplexRestryQueue.name}",containerFactory = "complexRetryRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("---complex----接收到消息:" + message + ",接收时间 : " + df.format(new Date()));
		
        /*
        int i = 0 ;
        int j = 0;
        int c = i /j;*/
        TestUser testUser = null;
        testUser.setGmtModified(new Date());

        String bs[] = message.split(" ");
        System.out.println("complex消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    }
}
  1. 自定义MethodInterceptor
public class MyRetryOperationsInterceptor implements MethodInterceptor {
    ...
    public Object invoke(final MethodInvocation invocation) throws Throwable {

        String name;
        if (StringUtils.hasText(label)) {
            name = label;
        } else {
            name = invocation.getMethod().toGenericString();
        }
        final String label = name;

        RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {
            public Object doWithRetry(RetryContext context) throws Exception {
                context.setAttribute(RetryContext.NAME, label);
                if (invocation instanceof ProxyMethodInvocation) {
                    try {
                        return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
                    //中断循环
                    } catch (ListenerExecutionFailedException e) {
                        if(e.getCause() instanceof NullPointerException){
                            context.setExhaustedOnly();
                            throw e;
                        }
                        return null;
                    } catch (Exception e) {
                        throw e;
                    } catch (Error e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new IllegalStateException(e);
                    }
                } else {
                    throw new IllegalStateException(
                            "MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +
                                    "so please raise an issue if you see this exception");
                }
            }

        };

        if (recoverer != null) {
            ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(
                    invocation.getArguments(), recoverer);
            return this.retryOperations.execute(retryCallback, recoveryCallback);
        }

        return this.retryOperations.execute(retryCallback);

    }
    ...
}

测试结果:
在这里插入图片描述

  总觉得根据不同的异常,终止策略不够优雅,有没有更加好的办法呢?我们从canRetry方法下手。

@Bean(name = "newComplexRetryRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory newComplexRetryRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(1);
    listenerContainerFactory.setPrefetchCount(1);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);//开启消息确认机制

    RabbitProperties.ListenerRetry retryConfig = new RabbitProperties.ListenerRetry();
    retryConfig.setEnabled(true);
    if (retryConfig.isEnabled()) {
        RetryTemplate retryTemplate = new RetryTemplate();
        //使用新的策略
        MySimpleRetryPolicy retryPolicy = new MySimpleRetryPolicy();
        retryTemplate.setRetryPolicy(retryPolicy);
        ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
        //初始等待时间
        exponentialBackOffPolicy.setInitialInterval(1000);
        //时间等待倍数
        exponentialBackOffPolicy.setMultiplier(2);
        //每次等待最大等待时间
        exponentialBackOffPolicy.setMaxInterval(5000);
        retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
        // 使用原来的拦截器
        RetryOperationsInterceptor retryInterceptor = new RetryOperationsInterceptor();
        retryInterceptor.setRetryOperations(retryTemplate);
        final MessageRecoverer messageRecoverer = new RejectAndDontRequeueRecoverer();
        retryInterceptor.setRecoverer(new MethodInvocationRecoverer<Void>() {
            public Void recover(Object[] args, Throwable cause) {
                Message message = (Message) args[1];
                if (messageRecoverer == null) {
                    log.info("messageRecoverer == null");
                }
                else {
                    messageRecoverer.recover(message, cause);
                }
                return null;
            }
        });
        listenerContainerFactory.setAdviceChain(retryInterceptor);
    }
    return listenerContainerFactory;
}

  创建我们自己的重试策略

public class MySimpleRetryPolicy implements RetryPolicy {

    private int maxCount = 8;

    @Override
    public boolean canRetry(RetryContext context) {
        MySimpleRetryContext mySimpleRetryContext = (MySimpleRetryContext) context;
        if (mySimpleRetryContext.getRetryCount() > maxCount) {
            return false;
        }
        for (Throwable throwable : mySimpleRetryContext.getThrowables()) {
			if (throwable instanceof ListenerExecutionFailedException) {
                if(throwable.getCause() instanceof NullPointerException){
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public RetryContext open(RetryContext parent) {
        return new MySimpleRetryContext(parent);
    }

    @Override
    public void close(RetryContext context) {
    }

    @Override
    public void registerThrowable(RetryContext context, Throwable throwable) {
        MySimpleRetryContext mySimpleRetryContext = (MySimpleRetryContext) context;
        mySimpleRetryContext.addThrowables(throwable);
        mySimpleRetryContext.registerThrowable(throwable);
    }

    public class MySimpleRetryContext extends RetryContextSupport {
        private Set<Throwable> throwables = new HashSet<>();


        public MySimpleRetryContext(RetryContext parent) {
            super(parent);
        }

        public Set<Throwable> getThrowables() {
            return throwables;
        }

        public void addThrowables(Throwable throwable) {
            this.throwables.add(throwable);
        }
    }
}

  上面需要注意的是canRetry方法中的逻辑,其实也很简单,如果重试次数超过8次,肯定终止重试,如果抛出的异常是空指针异常,也终止重试。
在这里插入图片描述
  到目前为止,我相信大家对消费者的重试机制有了深刻的理解,但是我们还是要回到之前的逻辑,到目前为止,我们只知道了所有创建好的容器存储于RabbitListenerEndpointRegistry的listenerContainers集合中,那什么时候初始化容器呢?我们来看看RabbitListenerEndpointRegistry的类结构关系。
在这里插入图片描述
  从关系中,我们看到RabbitListenerEndpointRegistry实现了SmartLifecycle接口。而实现的目的是什么呢?再来看SimpleMessageListenerContainer也实现了SmartLifecycle接口,那他们之间肯定存在某种联系。
在这里插入图片描述
  因为SmartLifecycle中有start方法,接下来,我们在start方法中打一个断点看看。根据方法调用栈,我们看到,最开始是onRefresh方法。
在这里插入图片描述
  从方法调用栈中,我们找到其中的一个方法,这个方法是onRefresh()方法之后调用的。

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.isEmpty()) {
        List<Integer> keys = new ArrayList<Integer>(phases.keySet());
        Collections.sort(keys);
        for (Integer key : keys) {
            phases.get(key).start();
        }
    }
}

  我们看上面的加粗的代码,RabbitListenerEndpointRegistry肯定实现了SmartLifecycle类,而RabbitListenerEndpointRegistry的isAutoStartup方法默认为true,因此RabbitListenerEndpointRegistry的start方法肯定会被调用。经过一系列的调用,最终到达了RabbitListenerEndpointRegistry的start方法

public void start() {
    for (MessageListenerContainer listenerContainer : getListenerContainers()) {
        startIfNecessary(listenerContainer);
    }
}
public Collection<MessageListenerContainer> getListenerContainers() {
    return Collections.unmodifiableCollection(this.listenerContainers.values());
}

  根据RabbitListenerEndpointRegistry中的start方法的逻辑,即遍历所有的listenerContainer,调用startIfNecessary方法。那startIfNecessary方法的逻辑是什么呢?

private void startIfNecessary(MessageListenerContainer listenerContainer) {
    if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
        listenerContainer.start();
    }
}

  最终调用了listenerContainer的start方法,而SimpleMessageListenerContainer也是实现了SmartLifecycle接口的。因此也需要实现start方法,那start方法又做了什么事情呢?

public void start() {
    if (isRunning()) {
        return;
    }
    //如果容器没有被初始化,则执行初始化逻辑
    if (!this.initialized) {
        synchronized (this.lifecycleMonitor) {
            if (!this.initialized) {
               	afterPropertiesSet();
                this.initialized = true;
            }
        }
    }
    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Starting Rabbit listener container.");
        }
        doStart();
    }
    catch (Exception ex) {
        throw convertRabbitAccessException(ex);
    }
}

  上面的代码很简单,无非是容器如果没有被初始化,先初始化,再调用doStart方法。接下来,来看看doStart方法的实现。

protected void doStart() throws Exception {
	// 如果messageListener实现了ListenerContainerAware
	// 而我们之前也分析过,默认messageListener是MessagingMessageListenerAdapter对象
    if (getMessageListener() instanceof ListenerContainerAware) {
        Collection<String> expectedQueueNames = ((ListenerContainerAware) getMessageListener()).expectedQueueNames();
        if (expectedQueueNames != null) {
            String[] queueNames = getQueueNames();
            Assert.state(expectedQueueNames.size() == queueNames.length,
                    "Listener expects us to be listening on '" + expectedQueueNames + "'; our queues: "
                            + Arrays.asList(queueNames));
            boolean found = false;
            for (String queueName : queueNames) {
                if (expectedQueueNames.contains(queueName)) {
                    found = true;
                }
                else {
                    found = false;
                    break;
                }
            }
            Assert.state(found, "Listener expects us to be listening on '" + expectedQueueNames + "'; our queues: "
                    + Arrays.asList(queueNames));
        }
    }
    //取 rabbitAdmin,默认情况下只允许一个rabbitAdmin 
    if (this.rabbitAdmin == null && this.getApplicationContext() != null) {
        Map<String, RabbitAdmin> admins = this.getApplicationContext().getBeansOfType(RabbitAdmin.class);
        if (admins.size() == 1) {
            this.rabbitAdmin = admins.values().iterator().next();
        }
        else {
            if (this.autoDeclare || this.mismatchedQueuesFatal) {
                if (logger.isDebugEnabled()) {
                    logger.debug("For 'autoDeclare' and 'mismatchedQueuesFatal' to work, there must be exactly one "
                            + "RabbitAdmin in the context or you must inject one into this container; found: "
                            + admins.size() + " for container " + this.toString());
                }
            }
            if (this.mismatchedQueuesFatal) {
                throw new IllegalStateException("When 'mismatchedQueuesFatal' is 'true', there must be exactly "
                        + "one RabbitAdmin in the context or you must inject one into this container; found: "
                        + admins.size() + " for container " + this.toString());
            }
        }
    }
    //初始化队列,交换器,绑定关系
    checkMismatchedQueues();
    super.doStart();
    synchronized (this.consumersMonitor) {
        if (this.consumers != null) {
            throw new IllegalStateException("A stopped container should not have consumers");
        }
        //初始化消费者
        int newConsumers = initializeConsumers();
        if (this.consumers == null) {
            logger.info("Consumers were initialized and then cleared " +
                    "(presumably the container was stopped concurrently)");
            return;
        }
        if (newConsumers <= 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Consumers are already running");
            }
            return;
        }
        Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
        for (BlockingQueueConsumer consumer : this.consumers) {
            AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
            processors.add(processor);
            //taskExecutor默认是SimpleAsyncTaskExecutor
            this.taskExecutor.execute(processor);
            if (this.applicationEventPublisher != null) {
            	//发送消费者启动事件
                this.applicationEventPublisher.publishEvent(new AsyncConsumerStartedEvent(this, consumer));
            }
        }
        for (AsyncMessageProcessingConsumer processor : processors) {
        	//如果消费者启动异常,则抛出异常
            FatalListenerStartupException startupException = processor.getStartupException();
            if (startupException != null) {
                throw new AmqpIllegalStateException("Fatal exception on listener startup", startupException);
            }
        }
    }
}

  上述代码的逻辑,真正重要的只有三点。

  1. 初始化队列,交换器,绑定关系
  2. 初始化消费者
  3. 启动消费者

  接下来,我们来看如何初始化队列及绑定关系。

private void checkMismatchedQueues() {
    if (this.mismatchedQueuesFatal && this.rabbitAdmin != null) {
        try {
        	//调用rabbitAdmin初始化逻辑
            this.rabbitAdmin.initialize();
        }
        catch (AmqpConnectException e) {
            logger.info("Broker not available; cannot check queue declarations");
        }
        catch (AmqpIOException e) {
            if (RabbitUtils.isMismatchedQueueArgs(e)) {
                throw new FatalListenerStartupException("Mismatched queues", e);
            }
            else {
                logger.info("Failed to get connection during start(): " + e);
            }
        }
    }
}

public void initialize() {
    if (this.applicationContext == null) {
        this.logger.debug("no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
        return;
    }

    this.logger.debug("Initializing declarations");
    //获取到Spring容器中所有的交换器
    Collection<Exchange> contextExchanges = new LinkedList<Exchange>(
            this.applicationContext.getBeansOfType(Exchange.class).values());
    //获取到Spring容器中所有的队列
    Collection<Queue> contextQueues = new LinkedList<Queue>(
            this.applicationContext.getBeansOfType(Queue.class).values());
    //获取到Spring容器中所有的绑定关系
    Collection<Binding> contextBindings = new LinkedList<Binding>(
            this.applicationContext.getBeansOfType(Binding.class).values());

	//从容器中获取所有的Collection集合,collection集合内容中有交换器,队列,绑定关系的添加到相应的集合中
    @SuppressWarnings("rawtypes")
    Collection<Collection> collections = this.declareCollections
        ? this.applicationContext.getBeansOfType(Collection.class, false, false).values()
        : Collections.<Collection>emptyList();
    for (Collection<?> collection : collections) {
        if (collection.size() > 0 && collection.iterator().next() instanceof Declarable) {
            for (Object declarable : collection) {
                if (declarable instanceof Exchange) {
                    contextExchanges.add((Exchange) declarable);
                }
                else if (declarable instanceof Queue) {
                    contextQueues.add((Queue) declarable);
                }
                else if (declarable instanceof Binding) {
                    contextBindings.add((Binding) declarable);
                }
            }
        }
    }
    
    //删除不应由该管理员声明的任何实例。
    final Collection<Exchange> exchanges = filterDeclarables(contextExchanges);
    final Collection<Queue> queues = filterDeclarables(contextQueues);
    final Collection<Binding> bindings = filterDeclarables(contextBindings);
	// 可能会存在消息丢失的交换器,打印日志
    for (Exchange exchange : exchanges) {
        if ((!exchange.isDurable() || exchange.isAutoDelete())  && this.logger.isInfoEnabled()) {
            this.logger.info("Auto-declaring a non-durable or auto-delete Exchange ("
                    + exchange.getName()
                    + ") durable:" + exchange.isDurable() + ", auto-delete:" + exchange.isAutoDelete() + ". "
                    + "It will be deleted by the broker if it shuts down, and can be redeclared by closing and "
                    + "reopening the connection.");
        }
    }
	// 感觉不安全的队列打印日志
    for (Queue queue : queues) {
        if ((!queue.isDurable() || queue.isAutoDelete() || queue.isExclusive()) && this.logger.isInfoEnabled()) {
            this.logger.info("Auto-declaring a non-durable, auto-delete, or exclusive Queue ("
                    + queue.getName()
                    + ") durable:" + queue.isDurable() + ", auto-delete:" + queue.isAutoDelete() + ", exclusive:"
                    + queue.isExclusive() + ". "
                    + "It will be redeclared if the broker stops and is restarted while the connection factory is "
                    + "alive, but all messages will be lost.");
        }
    }
	//如果交换器,队列,或绑定关系为空,则直接返回
    if (exchanges.size() == 0 && queues.size() == 0 && bindings.size() == 0) {
        this.logger.debug("Nothing to declare");
        return;
    }
    this.rabbitTemplate.execute(new ChannelCallback<Object>() {
        @Override
        public Object doInRabbit(Channel channel) throws Exception {
        	//申明交换器
            declareExchanges(channel, exchanges.toArray(new Exchange[exchanges.size()]));
            //申明队列 
            declareQueues(channel, queues.toArray(new Queue[queues.size()]));
            //申明绑定关系
            declareBindings(channel, bindings.toArray(new Binding[bindings.size()]));
            return null;
        }
    });
    this.logger.debug("Declarations finished");
}

  上面中,前期,都是做一些准备操作,只有execute方法,才是核心逻辑,而之前在Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析这篇博客中,对内部的代码做了深入分析,包括连接的获取,信道的获取,而信道和连接的获取方法又有不同的模式,一个连接多个信道,还是多个连接,多个信道,信道的复用,创建等,方法内部的实现也是非常巧妙,有兴趣可以去研究一下,但是今天我们重点看declareExchanges,declareQueues和declareBindings方法,接下来,我们看看三个方法的实现。

private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException {
    for (final Exchange exchange : exchanges) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("declaring Exchange '" + exchange.getName() + "'");
        }
        if (!isDeclaringDefaultExchange(exchange)) {
            try {
                if (exchange.isDelayed()) {
                    Map<String, Object> arguments = exchange.getArguments();
                    if (arguments == null) {
                        arguments = new HashMap<String, Object>();
                    }
                    else {
                        arguments = new HashMap<String, Object>(arguments);
                    }
                    arguments.put("x-delayed-type", exchange.getType());
                    channel.exchangeDeclare(exchange.getName(), x-delayed-message, exchange.isDurable(),
                            exchange.isAutoDelete(), exchange.isInternal(), arguments);
                }
                else {
                    channel.exchangeDeclare(exchange.getName(), exchange.getType(), exchange.isDurable(),
                            exchange.isAutoDelete(), exchange.isInternal(), exchange.getArguments());
                }
            }
            catch (IOException e) {
                logOrRethrowDeclarationException(exchange, "exchange", e);
            }
        }
    }
}

private DeclareOk[] declareQueues(final Channel channel, final Queue... queues) throws IOException {
    List<DeclareOk> declareOks = new ArrayList<DeclareOk>(queues.length);
    for (int i = 0; i < queues.length; i++) {
        Queue queue = queues[i];
        if (!queue.getName().startsWith("amq.")) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("declaring Queue '" + queue.getName() + "'");
            }
            try {
                try {
                    DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
                            queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());
                    declareOks.add(declareOk);
                }
                catch (IllegalArgumentException e) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.error("Exception while declaring queue: '" + queue.getName() + "'");
                    }
                    try {
                        if (channel instanceof ChannelProxy) {
                            ((ChannelProxy) channel).getTargetChannel().close();
                        }
                    }
                    catch (TimeoutException e1) {
                    }
                    throw new IOException(e);
                }
            }
            catch (IOException e) {
                logOrRethrowDeclarationException(queue, "queue", e);
            }
        }
        else if (this.logger.isDebugEnabled()) {
            this.logger.debug(queue.getName() + ": Queue with name that starts with 'amq.' cannot be declared.");
        }
    }
    return declareOks.toArray(new DeclareOk[declareOks.size()]);
}


private void declareBindings(final Channel channel, final Binding... bindings) throws IOException {
    for (Binding binding : bindings) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Binding destination [" + binding.getDestination() + " (" + binding.getDestinationType()
                    + ")] to exchange [" + binding.getExchange() + "] with routing key [" + binding.getRoutingKey()
                    + "]");
        }

        try {
            if (binding.isDestinationQueue()) {
                if (!isDeclaringImplicitQueueBinding(binding)) {
                    channel.queueBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
                            binding.getArguments());
                }
            }
            else {
                channel.exchangeBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
                        binding.getArguments());
            }
        }
        catch (IOException e) {
            logOrRethrowDeclarationException(binding, "binding", e);
        }
    }
}

  上面的代码就是RabbitMQ的基本操作,申明队列,申明交换器,申明绑定关系,如果这一块不是很明白的小伙伴,可以去看我的RabbitMq初识(一)这篇博客。
  接下来,我们来看第二步,初始化消费者。

protected int initializeConsumers() {
    int count = 0;
    synchronized (this.consumersMonitor) {
    	//如果消费者集合为空
        if (this.consumers == null) {
            this.cancellationLock.reset();
            this.consumers = new HashSet<BlockingQueueConsumer>(this.concurrentConsumers);
            //循环遍历初始化消费者
            for (int i = 0; i < this.concurrentConsumers; i++) {
                BlockingQueueConsumer consumer = createBlockingQueueConsumer();
                this.consumers.add(consumer);
                count++;
            }
        }
    }
    return count;
}

  上面代码,我们需要注意的一点是concurrentConsumers这个数量由哪个控制,我们之前分析过SimpleRabbitListenerContainerFactory工厂,有创建工厂时可以设置setConcurrentConsumers当前队列数,因此,上面的concurrentConsumers值来源于SimpleRabbitListenerContainerFactory,而在创建容器工厂时,我们还设置了一个参数maxConcurrentConsumers,那这个参数有什么用呢?如果是用来扩容的限制最大消费者数量,那扩容的逻辑是什么呢?这些问题,我们留在后面再来分析 。
  经过上面方法,我们已经得到初始化的消费者集合,接下来,我们来看taskExecutor的execute方法。

public void execute(Runnable task) {
    execute(task, TIMEOUT_INDEFINITE);
}


public void execute(Runnable task, long startTimeout) {
    Assert.notNull(task, "Runnable must not be null");
    Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
    if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
        this.concurrencyThrottle.beforeAccess();
        doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
    }
    else {
        doExecute(taskToUse);
    }
}

protected void doExecute(Runnable task) {
    Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
    thread.start();
}

  上面真正做做事情的代码doExecute方法,而方法的内部最重要的代码就是start()方法,而AsyncMessageProcessingConsumer实现了Runnable接口,java 初学者都知道,线程启动后,其实现逻辑肯定是run方法中,接下来,我们进入AsyncMessageProcessingConsumer的run方法 。

private final class AsyncMessageProcessingConsumer implements Runnable {

    private final BlockingQueueConsumer consumer;

    private final CountDownLatch start;

    private volatile FatalListenerStartupException startupException;

    private AsyncMessageProcessingConsumer(BlockingQueueConsumer consumer) {
        this.consumer = consumer;
        this.start = new CountDownLatch(1);
    }

    @Override
    public void run() {
        if (!isActive()) {
            return;
        }
        boolean aborted = false;
        int consecutiveIdles = 0;
        int consecutiveMessages = 0;
        this.consumer.setLocallyTransacted(isChannelLocallyTransacted(null));
        String routingLookupKey = getRoutingLookupKey();
        if (routingLookupKey != null) {
            SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey);
        }
        if (this.consumer.getQueueCount() < 1) {
            if (logger.isDebugEnabled()) {
                logger.debug("Consumer stopping; no queues for " + this.consumer);
            }
       	    SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer);
            if (SimpleMessageListenerContainer.this.applicationEventPublisher != null) {
                SimpleMessageListenerContainer.this.applicationEventPublisher.publishEvent(
                        new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer));
            }
            this.start.countDown();
            return;
        }
        try {
            try {
                if (SimpleMessageListenerContainer.this.autoDeclare) {
                    SimpleMessageListenerContainer.this.redeclareElementsIfNecessary();
                }
                this.consumer.start();
                this.start.countDown();
            }
            catch (QueuesNotAvailableException e) {
                	... 略
            }

            if (SimpleMessageListenerContainer.this.transactionManager != null) {
                ConsumerChannelRegistry.registerConsumerChannel(this.consumer.getChannel(), getConnectionFactory());
            }

            while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {
                try {
                    boolean receivedOk = receiveAndExecute(this.consumer); // At least one message received
                    if (SimpleMessageListenerContainer.this.maxConcurrentConsumers != null) {
                        if (receivedOk) {
                            if (isActive(this.consumer)) {
                                consecutiveIdles = 0;
                                if (consecutiveMessages++ > SimpleMessageListenerContainer.this.consecutiveActiveTrigger) {
                                	//扩容消费者
                                    considerAddingAConsumer();
                                    consecutiveMessages = 0;
                                }
                            }
                        }
                        else {
                            consecutiveMessages = 0;
                            if (consecutiveIdles++ > SimpleMessageListenerContainer.this.consecutiveIdleTrigger) {
                            	//减少消费者
                                considerStoppingAConsumer(this.consumer);
                                consecutiveIdles = 0;
                            }
                        }
                    }
                    if (SimpleMessageListenerContainer.this.idleEventInterval != null) {
                        if (receivedOk) {
                            SimpleMessageListenerContainer.this.lastReceive = System.currentTimeMillis();
                        }
                        else {
                            long now = System.currentTimeMillis();
                            long lastAlertAt = SimpleMessageListenerContainer.this.lastNoMessageAlert.get();
                            long lastReceive = SimpleMessageListenerContainer.this.lastReceive;
                            if (now > lastReceive + SimpleMessageListenerContainer.this.idleEventInterval
                                    && now > lastAlertAt + SimpleMessageListenerContainer.this.idleEventInterval
                                    && SimpleMessageListenerContainer.this.lastNoMessageAlert
                                    .compareAndSet(lastAlertAt, now)) {
                                publishIdleContainerEvent(now - lastReceive);
                            }
                        }
                    }
                }
                catch (ListenerExecutionFailedException ex) {
                    if (ex.getCause() instanceof NoSuchMethodException) {
                        throw new FatalListenerExecutionException("Invalid listener", ex);
                    }
                }
                catch (AmqpRejectAndDontRequeueException rejectEx) {

                }
            }

        }
        catch (InterruptedException e) {
			.... 略
		}
    }
}

  上面代码的逻辑,非常细腻,尤其是异常处理这一块,但是为了不影响主逻辑的处理,在这里,我将异常处理这一块代码逻辑给去掉了,我们着重讲正常处理逻辑,先来看消费者的启动。

public void start() throws AmqpException {
    if (logger.isDebugEnabled()) {
        logger.debug("Starting consumer " + this);
    }
    this.thread = Thread.currentThread();
    try {
        this.resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(this.connectionFactory,
                this.transactional);
        this.channel = this.resourceHolder.getChannel();
        addRecoveryListener();
    }
    catch (AmqpAuthenticationException e) {
        throw new FatalListenerStartupException("Authentication failure", e);
    }
    // 替换掉原来的consumer 
    this.consumer = new InternalConsumer(this.channel);
    this.deliveryTags.clear();
    this.activeObjectCounter.add(this);
	// declarationRetries默认为3
    int passiveDeclareRetries = this.declarationRetries;
    this.declaring = true;
    do {
        if (cancelled()) {
            break;
        }
        try {
        	//默认等待3次看队列是否存在
            attemptPassiveDeclarations();
            if (passiveDeclareRetries < this.declarationRetries && logger.isInfoEnabled()) {
                logger.info("Queue declaration succeeded after retrying");
            }
            passiveDeclareRetries = 0;
        }
        catch (DeclarationException e) {
            if (passiveDeclareRetries > 0 && this.channel.isOpen()) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Queue declaration failed; retries left=" + (passiveDeclareRetries), e);
                    try {
                        Thread.sleep(this.failedDeclarationRetryInterval);
                    }
                    catch (InterruptedException e1) {
                        this.declaring = false;
                        Thread.currentThread().interrupt();
                        this.activeObjectCounter.release(this);
                        throw RabbitExceptionTranslator.convertRabbitAccessException(e1);
                    }
                }
            }
            else if (e.getFailedQueues().size() < this.queues.length) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Not all queues are available; only listening on those that are - configured: "
                            + Arrays.asList(this.queues) + "; not available: " + e.getFailedQueues());
                }
                this.missingQueues.addAll(e.getFailedQueues());
                this.lastRetryDeclaration = System.currentTimeMillis();
            }
            else {
                this.declaring = false;
                this.activeObjectCounter.release(this);
                throw new QueuesNotAvailableException("Cannot prepare queue for listener. "
                        + "Either the queue doesn't exist or the broker will not allow us to use it.", e);
            }
        }
    }
    while (passiveDeclareRetries-- > 0 && !cancelled());
    this.declaring = false;

    if (!this.acknowledgeMode.isAutoAck() && !cancelled()) {
        try {
        	//设置客户端最多接收未被ack的消息个数 
            this.channel.basicQos(this.prefetchCount);
        }
        catch (IOException e) {
            this.activeObjectCounter.release(this);
            throw new AmqpIOException(e);
        }
    }
    try {
        if (!cancelled()) {
            for (String queueName : this.queues) {
                if (!this.missingQueues.contains(queueName)) {
                    consumeFromQueue(queueName);
                }
            }
        }
    }
    catch (IOException e) {
        throw RabbitExceptionTranslator.convertRabbitAccessException(e);
    }
}

  上面需要注意的一点是,当运行start方法之后,consumer被替换成InternalConsumer,而调用consumeFromQueue方法从信道中订阅消息。

private void consumeFromQueue(String queue) throws IOException {
    String consumerTag = this.channel.basicConsume(queue, this.acknowledgeMode.isAutoAck(),
            (this.tagStrategy != null ? this.tagStrategy.createConsumerTag(queue) : ""), this.noLocal,
            this.exclusive, this.consumerArgs,
			new ConsumerDecorator(queue, this.consumer, this.applicationEventPublisher));
    if (consumerTag != null) {
        this.consumerTags.put(consumerTag, queue);
        if (logger.isDebugEnabled()) {
            logger.debug("Started on queue '" + queue + "' with tag " + consumerTag + ": " + this);
        }
    }
    else {
        logger.error("Null consumer tag received for queue " + queue);
    }
}

  上面basicConsume实际上是调用了channel的如下方法。
  String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;
  看到没有,ConsumerDecorator消费者订阅消息类,再来看ConsumerDecorator的内部实现。

private static final class ConsumerDecorator implements Consumer {
  	... 略
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
            byte[] body) throws IOException {

        this.delegate.handleDelivery(consumerTag, envelope, properties, body);
    }
}

  看到handleDelivery()方法,我顿时哭的心都有了,终于看到一个我想看到的方法了,当服务器上有消息时,rabbit会推送消息到这个方法,可能小伙伴也被绕晕了,此时delegate是什么都不知道了,但是细心的小伙伴还是会发现,delegate就是我们之前被替换之后的consumer InternalConsumer,那InternalConsumer的handleDelivery方法实现了哪些逻辑呢?

public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
        throws IOException {
    if (logger.isDebugEnabled()) {
        logger.debug("Storing delivery for " + BlockingQueueConsumer.this);
    }
    try {
        if (BlockingQueueConsumer.this.abortStarted > 0) {
            if (!BlockingQueueConsumer.this.queue.offer(new Delivery(consumerTag, envelope, properties, body),
                    BlockingQueueConsumer.this.shutdownTimeout, TimeUnit.MILLISECONDS)) {
                RabbitUtils.setPhysicalCloseRequired(getChannel(), true);
                BlockingQueueConsumer.this.queue.clear();
                getChannel().basicNack(envelope.getDeliveryTag(), true, true);
                getChannel().basicCancel(consumerTag);
                try {
                    getChannel().close();
                }
                catch (TimeoutException e) {	
                	
                }
            }
        }
        else {
        	//默认情况下,都会将消息保存到queue中
            BlockingQueueConsumer.this.queue.put(new Delivery(consumerTag, envelope, properties, body));
        }
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

  从上面的一系列代码,我们分析得到,如果有消息订阅,消息会被存储到BlockingQueueConsumer的queue属性中,而这个特性有什么用呢?我们再回头来看AsyncMessageProcessingConsumer的run方法中的hasDelivery

protected boolean hasDelivery() {
    return !this.queue.isEmpty();
}

  当启动消费者后,系统会写一个while()死循环,每次判断消费者的队列是否为空,如果不为空,证明有消息,此时就会调用我们自定义方法了。接下来,我们继续来看在调用我们自定义方法,帮我们做了哪些事情 。

private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Throwable {
	//如果事务管理器不为空
    if (this.transactionManager != null) {
        try {
            if (this.transactionTemplate == null) {
                this.transactionTemplate =
                        new TransactionTemplate(this.transactionManager, this.transactionAttribute);
            }
            return this.transactionTemplate
                    .execute(new TransactionCallback<Boolean>() {

                        @Override
                        public Boolean doInTransaction(TransactionStatus status) {
                            RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.bindResourceToTransaction(
                                    new RabbitResourceHolder(consumer.getChannel(), false),
                                    getConnectionFactory(), true);
                            try {
                                return doReceiveAndExecute(consumer);
                            }
                            catch (RuntimeException e) {
                                prepareHolderForRollback(resourceHolder, e);
                                throw e;
                            }
                            catch (Throwable e) { //NOSONAR
                                throw new WrappedTransactionException(e);
                            }
                        }
                    });
        }
        catch (WrappedTransactionException e) {
            throw e.getCause();
        }
    }
    return doReceiveAndExecute(consumer);
}

  上面代码,分为两种情况,有事务和没有事务的情况,我们先来分析有事务的情况。

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            result = action.doInTransaction(status);
        }
        catch (RuntimeException ex) {
            rollbackOnException(status, ex);
            throw ex;
        }
        catch (Error err) {
            rollbackOnException(status, err);
            throw err;
        }
        catch (Throwable ex) {
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        //提交事务
        this.transactionManager.commit(status);
        return result;
    }
}

  对于正常情况,我们不考虑,我们来看看抛出异常的情况。

private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
    logger.debug("Initiating transaction rollback on application exception", ex);
    try {
        this.transactionManager.rollback(status);
    }
    catch (TransactionSystemException ex2) {
        logger.error("Application exception overridden by rollback exception", ex);
        ex2.initApplicationException(ex);
        throw ex2;
    }
    catch (RuntimeException ex2) {
        logger.error("Application exception overridden by rollback exception", ex);
        throw ex2;
    }
    catch (Error err) {
        logger.error("Application exception overridden by rollback error", ex);
        throw err;
    }
}


public final void rollback(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
        throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
    }

    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    processRollback(defStatus);
}

  上面代码没有什么复杂的东西,就是发现异常,需要处理回滚。

private void processRollback(DefaultTransactionStatus status) {
    try {
        try {
            triggerBeforeCompletion(status);
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
            }
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                doRollback(status);
            }
            else if (status.hasTransaction()) {
                if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                    if (status.isDebug()) {
                        logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                    }
                    doSetRollbackOnly(status);
                }
                else {
                    if (status.isDebug()) {
                        logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                    }
                }
            }
            else {
                logger.debug("Should roll back transaction but cannot - no transaction available");
            }
        }
        catch (RuntimeException ex) {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw ex;
        }
        catch (Error err) {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw err;
        }
        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    }
    finally {
        cleanupAfterCompletion(status);
    }
}

  上面代码有三种情况,有保存点,是新事务,就直接回滚,如果是嵌套事务,则设置需要回滚标识,最后再统一回滚,

protected void doRollback(DefaultTransactionStatus status) {
    RabbitTransactionObject txObject = (RabbitTransactionObject) status.getTransaction();
    RabbitResourceHolder resourceHolder = txObject.getResourceHolder();
    resourceHolder.rollbackAll();
}

public void rollbackAll() {
    for (Channel channel : this.channels) {
        if (logger.isDebugEnabled()) {
            logger.debug("Rolling back messages to channel: " + channel);
        }
        //如果是生产者发布消息,执行下面代码就够 了
        RabbitUtils.rollbackIfNecessary(channel);
        //deliveryTags包含此信道,证明是消费者回滚
        if (this.deliveryTags.containsKey(channel)) {
            for (Long deliveryTag : this.deliveryTags.get(channel)) {
                try {
                    channel.basicReject(deliveryTag, this.requeueOnRollback);
                }
                catch (IOException ex) {
                    throw new AmqpIOException(ex);
                }
            }
            RabbitUtils.commitIfNecessary(channel);
        }
    }
}

  basicReject方法中有一个参数需要注意,而这个参数由prepareHolderForRollback方法来确定

private void prepareHolderForRollback(RabbitResourceHolder resourceHolder, RuntimeException exception) {
    if (resourceHolder != null) {
        resourceHolder.setRequeueOnRollback(this.alwaysRequeueWithTxManagerRollback ||
                RabbitUtils.shouldRequeue(this.defaultRequeueRejected, exception, logger));
    }
}
public static boolean shouldRequeue(boolean defaultRequeueRejected, Throwable throwable, Log logger) {
    boolean shouldRequeue = defaultRequeueRejected ||
            throwable instanceof MessageRejectedWhileStoppingException;
    Throwable t = throwable;
    while (shouldRequeue && t != null) {
        if (t instanceof AmqpRejectAndDontRequeueException) {
            shouldRequeue = false;
        }
        t = t.getCause();
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Rejecting messages (requeue=" + shouldRequeue + ")");
    }
    return shouldRequeue;
}

  上面方法,我们需要注意的是alwaysRequeueWithTxManagerRollback默认为true,也就是说,默认情况下,对于抛出异常的消息,消费者会拒绝掉,让其他消费者消费,而当我们手动设置alwaysRequeueWithTxManagerRollback为false时,当抛出AmqpRejectAndDontRequeueException异常时,消息直接丢弃掉,不会再次被消费,其实事务这一块还是很复杂的,涉及到切面,事务嵌套,等,有兴趣的小伙伴可以去看我之前的切面分析,和事务分析的博客。我们接着继续看

private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable { //NOSONAR
    Channel channel = consumer.getChannel();
    // txSize默认为1,消费者可以一次性确认或者回滚多条消息
    for (int i = 0; i < this.txSize; i++) {
        logger.trace("Waiting for message from consumer.");
        Message message = consumer.nextMessage(this.receiveTimeout);
        if (message == null) {
            break;
        }
        
        try {
            executeListener(channel, message);
        }
        catch (ImmediateAcknowledgeAmqpException e) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("User requested ack for failed delivery: "
                        + message.getMessageProperties().getDeliveryTag());
            }
            break;
        }
        catch (Throwable ex) { 
            if (causeChainHasImmediateAcknowledgeAmqpException(ex)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("User requested ack for failed delivery: "
                            + message.getMessageProperties().getDeliveryTag());
                }
                break;
            }
            if (this.transactionManager != null) {
                if (this.transactionAttribute.rollbackOn(ex)) {
                	//如果有事务时,则直接清除掉consumer中的deliveryTags
                    RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
                            .getResource(getConnectionFactory());
                    if (resourceHolder != null) {
                        consumer.clearDeliveryTags();
                    }
                    else {
                    	//如果是本地事务
                        consumer.rollbackOnExceptionIfNecessary(ex);
                    }
                    throw ex; 
                }
                else {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("No rollback for " + ex);
                    }
                    break;
                }
            }
            else {
	            //如果有本地事务情况处理
                consumer.rollbackOnExceptionIfNecessary(ex);
                throw ex;
            }
        }
    }
    return consumer.commitIfNecessary(isChannelLocallyTransacted(channel));
}

  上面代码,我们先看其他的处理逻辑,最后再来看executeListener方法的内部实现,第一步,就是看消息的获取,有人会说,这个有什么好看的,不就是getMessage嘛,这就大错特错了,如果不分析这个方法,之前的一些结论解释不清楚。

public Message nextMessage(long timeout) throws InterruptedException, ShutdownSignalException {
    if (logger.isDebugEnabled()) {
        logger.debug("Retrieving delivery for " + this);
    }
    //检查消费者是否被关闭
    checkShutdown();
    if (this.missingQueues.size() > 0) {
    	//检查丢失的队列现在是否可用
        checkMissingQueues();
    }
    //从本地队列中弹出消息,使用handle方法处理
    Message message = handle(this.queue.poll(timeout, TimeUnit.MILLISECONDS));
    if (message == null && this.cancelled.get()) {
        throw new ConsumerCancelledException();
    }
    return message;
}
private Message handle(Delivery delivery) throws InterruptedException {
    if ((delivery == null && this.shutdown != null)) {
        throw this.shutdown;
    }
    if (delivery == null) {
        return null;
    }
    //获取消息的body信息
    byte[] body = delivery.getBody();
    Envelope envelope = delivery.getEnvelope();

    MessageProperties messageProperties = this.messagePropertiesConverter.toMessageProperties(
            delivery.getProperties(), envelope, "UTF-8");
    messageProperties.setMessageCount(0);
    messageProperties.setConsumerTag(delivery.getConsumerTag());
    messageProperties.setConsumerQueue(this.consumerTags.get(delivery.getConsumerTag()));
    //构建Message对象
    Message message = new Message(body, messageProperties);
    if (logger.isDebugEnabled()) {
        logger.debug("Received message: " + message);
    }
    
    this.deliveryTags.add(messageProperties.getDeliveryTag());
    
    if (this.transactional && !this.locallyTransacted) {
        ConnectionFactoryUtils.registerDeliveryTag(this.connectionFactory, this.channel,
                delivery.getEnvelope().getDeliveryTag());
    }
    return message;
}

  上面方法中,主要是将消息的body及属性信息封装成Message对象,但是后面将DeliveryTag加入到BlockingQueueConsumer的deliveryTags中,如果有事务的话,会注册事务的DeliveryTag,下面我们来看看,如果有事务DeliveryTag是如何处理的。

public static void registerDeliveryTag(ConnectionFactory connectionFactory, Channel channel, Long tag) {

    Assert.notNull(connectionFactory, "ConnectionFactory must not be null");

    RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
            .getResource(connectionFactory);
    if (resourceHolder != null) {
        resourceHolder.addDeliveryTag(channel, tag);
    }
}

public void addDeliveryTag(Channel channel, long deliveryTag) {
    this.deliveryTags.add(channel, deliveryTag);
}

  现在大家终于知道了在事务回滚时deliveryTags,属性的由来了。在消费消息时,会将channel加入到事务的deliveryTags属性中,回滚消息时,需要用到deliveryTags这个属性。
  下面来看本地事务的处理情况。

public void rollbackOnExceptionIfNecessary(Throwable ex) throws Exception {
	// 如果消息的确认方式不是NONE,也不是MANUAL,那就是AUTO
    boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
    try {
    	//如果是channel事务
        if (this.transactional) {
            if (logger.isDebugEnabled()) {
                logger.debug("Initiating transaction rollback on application exception: " + ex);
            }
            //回滚代码 
            RabbitUtils.rollbackIfNecessary(this.channel);
        }
        
        // 如果需要系统自动确认
        if (ackRequired) {
            boolean shouldRequeue = RabbitUtils.shouldRequeue(this.defaultRequeuRejected, ex, logger);
            for (Long deliveryTag : this.deliveryTags) {
            	//对AmqpRejectAndDontRequeueException异常处理
                this.channel.basicReject(deliveryTag, shouldRequeue);
            }
            //如果有channel事务,提交代码
            if (this.transactional) {
                RabbitUtils.commitIfNecessary(this.channel);
            }
        }
    }
    catch (Exception e) {
        logger.error("Application exception overridden by rollback exception", ex);
        throw e;
    }
    finally {
        this.deliveryTags.clear();
    }
}

public static void rollbackIfNecessary(Channel channel) {
    Assert.notNull(channel, "Channel must not be null");
    try {
        channel.txRollback();
    }
    catch (IOException ex) {
        throw new AmqpIOException(ex);
    }
}

public static void commitIfNecessary(Channel channel) {
    Assert.notNull(channel, "Channel must not be null");
    try {
        channel.txCommit();
    }
    catch (IOException ex) {
        throw new AmqpIOException(ex);
    }
}

  可能大家对channel事务这个概念又有点模糊了,什么是channel事务呢?

<!-- spring template声明-->
<rabbit:template id="rabbitTemplate" exchange="test-mq-exchange" connection-factory="connectionFactory"
                 message-converter="jsonMessageConverter" channel-transacted="true" />

  当我们在声明rabbitTemplate时,这个时候,我们可以指定channel-transacted=“true”,当调用channel.txRollback即可回滚消息。
  接下来,我们再来看看,当消息处理完之后的commitIfNecessary方法,看这个方法中做了哪些事情。

public boolean commitIfNecessary(boolean locallyTransacted) throws IOException {
    if (this.deliveryTags.isEmpty()) {
        return false;
    }
    boolean isLocallyTransacted = locallyTransacted
            || (this.transactional
            && TransactionSynchronizationManager.getResource(this.connectionFactory) == null);
    try {
    	//和之前的消息确认模式一样,如果不等于NONE,也不等于MANUAL,且等于AUTO
        boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
        if (ackRequired) {
            if (!this.transactional || isLocallyTransacted) {
                long deliveryTag = new ArrayList(this.deliveryTags).get(this.deliveryTags.size() - 1);
                this.channel.basicAck(deliveryTag, true);
            }
        }
        //如果有channel事务
        if (isLocallyTransacted) {
            RabbitUtils.commitIfNecessary(this.channel);
        }
    }
    finally {
        this.deliveryTags.clear();
    }
    return true;
}

  从上面代码中,事务这一块,我们分析过很多遍了,这是在没有抛出异常时的处理,如果配置了消息自动确认机制,是需要ack,如果配置了channel事务,则需要channel.txCommit(),但是BlockingQueueConsumer的deliveryTags属性如影随形,而还控制着commitIfNecessary方法返回true或false。到底起什么作用呢?

while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {
try {
    boolean receivedOk = receiveAndExecute(this.consumer); 
    if (SimpleMessageListenerContainer.this.maxConcurrentConsumers != null) {
        if (receivedOk) {
            if (isActive(this.consumer)) {
                consecutiveIdles = 0;
                //consecutiveActiveTrigger的默认值为10
                if (consecutiveMessages++ > SimpleMessageListenerContainer.this.consecutiveActiveTrigger) {
                	// 增加消费者
                    considerAddingAConsumer();
                    consecutiveMessages = 0;
                }
            }
        }
        else {
            consecutiveMessages = 0;
            //consecutiveIdleTrigger的默认值为10
            if (consecutiveIdles++ > SimpleMessageListenerContainer.this.consecutiveIdleTrigger) {
            	//减少消费者
                considerStoppingAConsumer(this.consumer);
                consecutiveIdles = 0;
            }
        }
    }
}
... 略
private void considerAddingAConsumer() {
    synchronized (this.consumersMonitor) {
        if (this.consumers != null
                && this.maxConcurrentConsumers != null && this.consumers.size() < this.maxConcurrentConsumers) {
            long now = System.currentTimeMillis();
            // startConsumerMinInterval的默认值为10秒
            if (this.lastConsumerStarted + this.startConsumerMinInterval < now) {
                this.addAndStartConsumers(1);
                this.lastConsumerStarted = now;
            }
        }
    }
}

  上面代码是添加消费者代码,而下面代码是减少消费者代码。

private void considerStoppingAConsumer(BlockingQueueConsumer consumer) {
    synchronized (this.consumersMonitor) {
        if (this.consumers != null && this.consumers.size() > this.concurrentConsumers) {
            long now = System.currentTimeMillis();
            // stopConsumerMinInterval的默认值为10秒
            if (this.lastConsumerStopped + this.stopConsumerMinInterval < now) {
                consumer.basicCancel(true);
                this.consumers.remove(consumer);
                if (logger.isDebugEnabled()) {
                    logger.debug("Idle consumer terminating: " + consumer);
                }
                this.lastConsumerStopped = now;
            }
        }
    }
}

  我们先来理一下增加消费者代码逻辑,默认情况下,如果receiveAndExecute方法连续10次返回true,则增加一个消费者,并且新增消费者的时间和上一次新增消费者的时间间隔要大于10秒,只要中途任意一次返回false,则consecutiveMessages变为0,需要重新计数,也就是说receiveAndExecute方法需要重新10次连续返回true,消费者才会累加1,但是需要注意的是,本次消费者增加时的时间和上一次消费者的时间间隔默认情况下要大于10秒,不然需要等待。所以consecutiveMessages计数器的最大值不一定为10,当消费者需要增加的时间间隔小于10秒时,consecutiveMessages会继续增加,但是消费者数量不能大于设置的最大值,当receiveAndExecute方法返回false时,则consecutiveMessages设置为0,当连续10次(10次是代码中写死地默认值,可以修改)都返回false时,此时,需要移除消费者,直到达到设定的最小值,同样,两次移除消费者的时间间隔不能小于10秒,原理就是这样,但是实际情况可能又是另外一种现象了。过一会来分析 。
  那receiveAndExecute方法什么时候返回true呢?我们之前不就分析过了嘛,由commitIfNecessary这个方法来控制,而这个方法中,又是由deliveryTags是否有值来控制,那我们回顾之前的代码,在nextMessage方法中有一段这样的代码,

//默认等待时间为1秒
Message message = handle(this.queue.poll(timeout, TimeUnit.MILLISECONDS));
if (message == null && this.cancelled.get()) {
    throw new ConsumerCancelledException();
}

  这段代码,看上去只有这么一点,但设计得却是非常巧妙。结合外面的代码来看。

while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {
    ...
}

  外面写了一个死循环,但是为什么我们的CPU的使用没有飙升起来呢?就是因为去队列中拉取消息,如果没有消息需要等待1秒时间,this.queue.poll(timeout, TimeUnit.MILLISECONDS),因为time默认为1秒,所以,当队列空闲时,系统会每隔1秒的时间去看看队列中是否有消息,如果没有,则等待一秒,相当于写了个死循环,1秒钟循环一次,当队列中有消息时,此时能从队列中立即获取消息进行消费,如果业务处理的时间不超过1秒,则在1秒内,会继续从queue中poll消息,如果业务处理超过1秒时,此时while徨的时间就是业务处理的时间。 这样,都感觉代码变活了,像人一样,如果有事情做,做完继续来做,如果没有事情呢?就隔固定时间,来询问一下,有没有事情做。太巧妙了。
  而 handle方法中,有一行代码。this.deliveryTags.add(messageProperties.getDeliveryTag()); 如果消息不为空,则将消息加入到deliveryTags中,而当业务处理完时,在commitIfNecessary方法中会将deliveryTags移除掉,并返回true,我相信现来理解消费者扩容的逻辑,就简单了,也就是说,如果队列只要消费掉一个消息,就会返回true,如果队列循环一次,没有消费掉消息,deliveryTags为空,则会返回false,而整个逻辑串联起来就是,队列连续消费10个消息,当前消费者并没有达到设定的最大值时,同时和上一次扩容消费者的时间间隔大于10秒是,则增加一个消费者。但是这个时候,我们需要考虑另外一种情况,假如,我们在SimpleMessageListenerContainer容器中初始化了5消费者,而5个消费者只需要每个消费者连续消费两次,此时consecutiveMessages就累加到10了,此时就可以扩容了。
  感兴趣的小伙伴,可以自己去测试一下,自己体验一下消费者的扩容机制。
  接下来,我们继续之前的分析。
在这里插入图片描述

protected void executeListener(Channel channel, Message messageIn) throws Throwable {
	//如果容器没有运行,直接终止掉
    if (!isRunning()) {
        if (logger.isWarnEnabled()) {
            logger.warn("Rejecting received message because the listener container has been stopped: " + messageIn);
        }
        throw new MessageRejectedWhileStoppingException();
    }
    try {
        Message message = messageIn;
        //消息的后处理器
        if (this.afterReceivePostProcessors != null) {
            for (MessagePostProcessor processor : this.afterReceivePostProcessors) {
                message = processor.postProcessMessage(message);
            }
        }
        Object batchFormat = message.getMessageProperties().getHeaders().get("springBatchFormat");
        if ("lengthHeader4".equals(batchFormat) && this.deBatchingEnabled) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBody());
            MessageProperties messageProperties = message.getMessageProperties();
            messageProperties.getHeaders().remove(MessageProperties.SPRING_BATCH_FORMAT);
            while (byteBuffer.hasRemaining()) {
                int length = byteBuffer.getInt();
                if (length < 0 || length > byteBuffer.remaining()) {
                    throw new ListenerExecutionFailedException("Bad batched message received",
                            new MessageConversionException("Insufficient batch data at offset " + byteBuffer.position()),
                                    message);
                }
                byte[] body = new byte[length];
                byteBuffer.get(body);
                messageProperties.setContentLength(length);
                Message fragment = new Message(body, messageProperties);
                invokeListener(channel, fragment);
            }
        }
        else {
            invokeListener(channel, message);
        }
    }
    catch (Exception ex) {
    	//errorHandler处理
        handleListenerException(ex);
        throw ex;
    }
}

  上面方法对于headers中有springBatchFormat这个,我觉得应用场景不是很多,就不分析了,先来看看消息的后处理器,这个怎样应用呢?我们先来看一个例子。

  1. 创建消息后处理器。
public class MyMessagePostProcessor implements MessagePostProcessor {
    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        System.out.println("消息后处理器");
        String s = new String(message.getBody());
        s = " 经过消息后处理器" + s ;
        return new Message(s.getBytes(), message.getMessageProperties());
    }
}

  消息后处理器的功能很简单,就是在每条消息前面加上字符串"经过消息后处理器".

@Slf4j
@Component
public class ContainerInitListener implements ApplicationContextAware {

    public ApplicationContext applicationContext;

    @EventListener(value = {EmbeddedServletContainerInitializedEvent.class})
    public void init() {
        RabbitListenerEndpointRegistry registry = applicationContext.getBean(RabbitListenerEndpointRegistry.class);
        Collection<MessageListenerContainer> messageListenerContainers = registry.getListenerContainers();
        for(MessageListenerContainer messageListenerContainer: messageListenerContainers){
            if(messageListenerContainer instanceof SimpleMessageListenerContainer){
                MyMessagePostProcessor myMessagePostProcessor = new MyMessagePostProcessor();
                ((SimpleMessageListenerContainer) messageListenerContainer).setAfterReceivePostProcessors(myMessagePostProcessor);
            }
        }
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

  我们注册一个事件监听器,当项目启动完成,接收到项目启动完成事件。在这个事件中,获取到RabbitListenerEndpointRegistry注册器,取出所有的消息监听器容器,设置其MessagePostProcessor。
  在正常情况下,启动项目后,发送消息,是没有问题的,但是如果队列中还有没有消费完的消息,此时重启项目。
测试结果如下:
在这里插入图片描述
  为什么会出现上面的情况呢?因为容器正在启动的时候,消费者就已经在等待消费消息了,此时Rabbit服务器将推送未被消费的消息给消费者,而此时容器可能还没有启动完成,当容器启动完成时,接收到启动完成的事件,此时再取出RabbitListenerEndpointRegistry中的listenerContainers属性中的所有消息监听器容器,为每个消息监听容器设置MessagePostProcessor处理器,而在这之后,消费者再消费消息时,发现有MessagePostProcessor,则调用MessagePostProcessor的postProcessMessage方法,在此之后,所有的消息body才会在前面加上 “经过消息后处理器”,显然,这种bug比较隐避,只有当重启项目,并且服务器队列中的消息未被消费完,才会出现上面的现象。显然,这种方式是不能用于项目的,如果需要所有的消息在消费之前被处理,那有什么更好的办法呢?
  我相信,如果认真看博客看到这里的小伙伴,再来看这个问题,觉得肯定不难,我提供一下我的实现方案,既然不能秋后算账,那我们就在之前解决这个问题。

  1. 重新创建SimpleRabbitListenerContainerFactory
public class MySimpleRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {

    @Override
    protected MySimpleMessageListenerContainer createContainerInstance() {
        return new MySimpleMessageListenerContainer();
    }   
}
  1. 创建消息监听器容器
public class MySimpleMessageListenerContainer extends SimpleMessageListenerContainer {
    @Override
    protected void doInitialize() throws Exception {
        super.doInitialize();
        setAfterReceivePostProcessors(new MyMessagePostProcessor());
    }
}

可能有小伙伴说为什么我不在SimpleMessageListenerContainer的afterPropertiesSet方法做处理,因为SimpleMessageListenerContainer的afterPropertiesSet是final的,不能被继承。

  1. 创建新的SimpleRabbitListenerContainerFactory工厂
@Bean(name = "mySimpleRabbitListenerContainerFactory")
public MySimpleRabbitListenerContainerFactory mySimpleRabbitListenerContainerFactory() {
    MySimpleRabbitListenerContainerFactory listenerContainerFactory = new MySimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(10);
    listenerContainerFactory.setPrefetchCount(5);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//开启消息确认机制
    return listenerContainerFactory;
}
  1. 修改containerFactory
@Component
@Slf4j
public class RabbitBussListener {

    @RabbitHandler
    @RabbitListener(queues = "#{rabbitTestQueue.name}",containerFactory = "mySimpleRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            System.out.println("-------接收到消息:" + message);
            Thread.sleep(3000);

            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String bs[] = message.split(" ");
            System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
        } catch (Exception e) {
            log.error("处理异常", e);
        } finally {
            try {
                    channel.basicAck(delivertTag,true);
            } catch (Exception e) {
                log.error("确实消息失败",e);
            }
        }
    }

}

开始测试:
在这里插入图片描述
  接下来,我们来看,如果调用目标方法,抛出异常,会怎么办?

protected void handleListenerException(Throwable ex) {
    if (isActive()) {
        invokeErrorHandler(ex);
    }
    else {
        logger.debug("Listener exception after container shutdown", ex);
    }
}

protected void invokeErrorHandler(Throwable ex) {
    if (this.errorHandler != null) {
    	//handleError的默认值是ConditionalRejectingErrorHandler
        this.errorHandler.handleError(ex);
    }
    else if (logger.isWarnEnabled()) {
        logger.warn("Execution of Rabbit message listener failed, and no ErrorHandler has been set.", ex);
    }
}

  而errorHandler的默认值是ConditionalRejectingErrorHandler,如果业务有需要,我们也可以重写errorHandler,从而实现业务需求。因为例子原理和上面的MessagePostProcessor一样,我就不写了,感兴趣的小伙伴可以自己去实现一下。

protected void invokeListener(Channel channel, Message message) throws Exception {
    Object listener = getMessageListener();
    // SimpleMessageListenerContainer肯定是实现了ChannelAwareMessageListener接口
    if (listener instanceof ChannelAwareMessageListener) {
        doInvokeListener((ChannelAwareMessageListener) listener, channel, message);
    }
    else if (listener instanceof MessageListener) {
        boolean bindChannel = isExposeListenerChannel() && isChannelLocallyTransacted(channel);
        if (bindChannel) {
            RabbitResourceHolder resourceHolder = new RabbitResourceHolder(channel, false);
            resourceHolder.setSynchronizedWithTransaction(true);
            TransactionSynchronizationManager.bindResource(this.getConnectionFactory(),
                    resourceHolder);
        }
        try {
            doInvokeListener((MessageListener) listener, message);
        }
        finally {
            if (bindChannel) {
                TransactionSynchronizationManager.unbindResource(this.getConnectionFactory());
            }
        }
    }
    else if (listener != null) {
        throw new FatalListenerExecutionException("Only MessageListener and SessionAwareMessageListener supported: "
                + listener);
    }
    else {
        throw new FatalListenerExecutionException("No message listener specified - see property 'messageListener'");
    }
}

  上面无论哪种情况,都最终调用了doInvokeListener方法,下面,我们看看doInvokeListener的内部实现。

protected void  doInvokeListener(MessageListener listener, Message message) throws Exception {
    try {
        listener.onMessage(message);
    }
    catch (Exception e) {
        throw wrapToListenerExecutionFailedExceptionIfNeeded(e, message);
    }
}

protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Message message)
            throws Exception {
    RabbitResourceHolder resourceHolder = null;
    Channel channelToUse = channel;
    boolean boundHere = false;
    try {
        if (!isExposeListenerChannel()) {
        	// 我们需要暴露一个单独的 Channel。
            resourceHolder = getTransactionalResourceHolder();
            channelToUse = resourceHolder.getChannel();
            // 如果有真实交易,资源就会被绑定;
            // 否则我们需要在这里临时绑定它。在此通道上完成的任何工作都将在 finally 块中提交。
            if (isChannelLocallyTransacted(channelToUse) &&
                        !TransactionSynchronizationManager.isActualTransactionActive()) {
                    resourceHolder.setSynchronizedWithTransaction(true);
                    TransactionSynchronizationManager.bindResource(this.getConnectionFactory(),
                            resourceHolder);
                boundHere = true;
            }
        }
        else {
            // 如果是本地事务,则绑定当前通道以使其可用于 RabbitTemplate
            if (isChannelLocallyTransacted(channel)) {
                RabbitResourceHolder localResourceHolder = new RabbitResourceHolder(channelToUse, false);
                localResourceHolder.setSynchronizedWithTransaction(true);
                TransactionSynchronizationManager.bindResource(this.getConnectionFactory(),
                        localResourceHolder);
                boundHere = true;
            }
        }
        // 实际调用消息监听器...
        try {
            listener.onMessage(message, channelToUse);
        }
        catch (Exception e) {
            throw wrapToListenerExecutionFailedExceptionIfNeeded(e, message);
        }
    }
    finally {
        if (resourceHolder != null && boundHere) {
            //所以暴露的通道(因为exposeListenerChannel为false)将被关闭
            resourceHolder.setSynchronizedWithTransaction(false);
        }
        ConnectionFactoryUtils.releaseResources(resourceHolder);
        if (boundHere) {
            TransactionSynchronizationManager.unbindResource(this.getConnectionFactory());
            if (!isExposeListenerChannel() && isChannelLocallyTransacted(channelToUse)) {
               // 提交我们暴露的临时通道;消费者的频道将在稍后提交。
               //请注意,当公开不同的通道时当没有事务管理器时,公开的通道将在每条消息上提交,而不是基于 txSize。
                RabbitUtils.commitIfNecessary(channelToUse);
            }
        }
    }
}

  上面的大部分代码都是关于事务的,我们可能懂得在Spring的xml配置文件中使用事务,在Spring boot中消费者中又如何使用事务呢?我们来看个例子。

  1. 创建消息监听器容器,设置channel事务
@Bean(name = "txSimpleRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory txSimpleRabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(10);
    listenerContainerFactory.setPrefetchCount(5);//预处理消息个数
    listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);//自动确认消息
    listenerContainerFactory.setChannelTransacted(true);//设置有channel事务
    return listenerContainerFactory;
}
  1. 设置消费者
@Component
@Slf4j
public class RabbitChannelTxListener {

    @RabbitHandler
    @RabbitListener(queues = "#{channelTxQueueName.name}",containerFactory = "txSimpleRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        System.out.println("-------接收到消息:" + message);
        int i = 0 ;
        int j  = i / 0 ;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    }

}

  上面代码有意思的是,在消费者代码中,我们创建了一个空指针异常。

  1. 创建生产者代码
@Value("${eb.config.rabbitQueue.channelTxQueueName}")
public String channelTxQueueName;

@RequestMapping("channelTxQueueNameTest")
public String channelTxQueueNameTest() {
    String message = "测试 " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(channelTxQueueName, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}

测试结果:
在这里插入图片描述
在这里插入图片描述
  看到没有,上面【-------接收到消息:测试 1625368750044】这条消息被多次重复消费,因为调用消费者代码抛出异常,最终消息被rollbackOnExceptionIfNecessary方法中的 RabbitUtils.rollbackIfNecessary(this.channel); 代码回滚了。所以,在消费者代码中使用事务,务必小心,不然很容易出现死循环代码。
  那有小伙伴肯定会想,channel事务是上面这个例子实现了,如何使用RabbitTransactionManager来管理事务呢?

  1. 创建消息监听器工厂
@Bean(name = "rabbitManagerSimpleRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory rabbitManagerSimpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(10);
    listenerContainerFactory.setPrefetchCount(5);//预处理消息个数
    listenerContainerFactory.setTransactionManager(rabbitTransactionManager(connectionFactory));
    return listenerContainerFactory;
}
  1. 创建消费者
@Component
@Slf4j
public class RabbitTransactionManagerListener {
    @RabbitHandler
    @RabbitListener(queues = "#{transactionManagerQueueName.name}",containerFactory = "rabbitManagerSimpleRabbitListenerContainerFactory")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        System.out.println("-------接收到消息:" + message);
        int i = 0 ;
        int j  = i / 0 ;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String bs[] = message.split(" ");
        System.out.println("消费掉消息:" +   message  +       "  发送时间:"+df.format(new Date(Long.parseLong(bs[bs.length-1]))) + "     消费完成时间:"+ df.format(new Date()));
    }
}
  1. 生产者生产消息
@Value("${eb.config.rabbitQueue.transactionManagerQueueName}")
public String transactionManagerQueueName;

@RequestMapping("transactionManagerQueueNameTest")
public String transactionManagerQueueNameTest() {
    String message = "测试 " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(transactionManagerQueueName, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}

测试结果如下
在这里插入图片描述
在这里插入图片描述
  难道RabbitMQ的事务真的如此简单吗?我们再来看一个例子,注释掉RabbitTransactionManager
在这里插入图片描述
  再来测试。
在这里插入图片描述
  大家发现一个规率没有,无论有没有设置事务,只要业务消费者代码抛出异常,消息将不断的被重复消费。这是为什么呢?
在这里插入图片描述
  从上图中,我们看到,SimpleMessageListenerContainer的默认消息确认模式是AUTO,那这一行代码对消息的处理有何影响呢?
在这里插入图片描述
  因为acknowledgeMode为AUTO,只有抛出AmqpRejectAndDontRequeueException异常时,shouldRequeue才为true,因此最终调用了channel.basicReject(deliveryTag,true)方法,消息被拒,才导致消息不断的被重复消费。因此,即使我们看到的都是消息重复消费的死循环,但是实现的原理不一样,这个还是需要大家注意的,如果没有什么特殊情况,最好,在消费者业务逻辑中,我们手动捕获异常,根据自己的业务对异常相应的处理,不然很容易出现死循环的。
  我相信此时此刻,大家对Rabbit事务这一块有了深刻理解了,但是在开发代码时还有一个小点需要注意,如下图所示。
在这里插入图片描述
  我们设置了AcknowledgeMode为NONE模式,同时又设置了事务,此时抛出异常,为什么呢?我们调用channel.basicReject,或channel.basicAck方法之后,需要commit才会生效,而rollback和commit就是针对ack而言的,如果都不需要ack了,那要rollback和commit还有什么意义呢?因此,不能设置ack为NONE,但是又有事务。
  我相信大家对事务这一块有了深刻理解了,我们继续来看消息如何处理的。

public void onMessage(org.springframework.amqp.core.Message amqpMessage, Channel channel) throws Exception {
    Message<?> message = toMessagingMessage(amqpMessage);
    if (logger.isDebugEnabled()) {
        logger.debug("Processing [" + message + "]");
    }
    // 调用具体目标方法
    Object result = invokeHandler(amqpMessage, channel, message);
    if (result != null) {
        handleResult(result, amqpMessage, channel, message);
    }
    else {
        logger.trace("No result object given - no result to handle");
    }
}

protected Message<?> toMessagingMessage(org.springframework.amqp.core.Message amqpMessage) {
	return (Message<?>) getMessagingMessageConverter().fromMessage(amqpMessage);
}

protected final MessagingMessageConverter getMessagingMessageConverter() {
	return this.messagingMessageConverter;
}

  关于messagingMessageConverter,我们在生产中还是用得蛮多的,我们来看看生产中如何使用。

@Bean(name = "messageConvertSimpleRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory messageConvertSimpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
    listenerContainerFactory.setConnectionFactory(connectionFactory);
    listenerContainerFactory.setConcurrentConsumers(1);
    listenerContainerFactory.setMaxConcurrentConsumers(10);
    listenerContainerFactory.setPrefetchCount(5);//预处理消息个数
    listenerContainerFactory.setMessageConverter(new Jackson2JsonMessageConverter());
    return listenerContainerFactory;
}

  我们只需要在SimpleRabbitListenerContainerFactory设置messageConverter即可 。
  具体有什么用途,我也不知道,从代码中看到应该是这样修改,一般情况下用默认的即可,具体怎样用,可能需要根据具体的业务需求来定了。接下来,我们继续看invokeHandler方法。

private Object invokeHandler(org.springframework.amqp.core.Message amqpMessage, Channel channel,
        Message<?> message) {
    try {
        return this.handlerMethod.invoke(message, amqpMessage, channel);
    }
    catch (org.springframework.messaging.converter.MessageConversionException ex) {
        throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
                "be invoked with the incoming message", message.getPayload()),
                new MessageConversionException("Cannot handle message", ex),
                amqpMessage);
    }
    catch (MessagingException ex) {
        throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
                "be invoked with the incoming message", message.getPayload()), ex, amqpMessage);
    }
    catch (Exception ex) {
        throw new ListenerExecutionFailedException("Listener method '" +
                this.handlerMethod.getMethodAsString(message.getPayload()) + "' threw exception", ex, amqpMessage);
    }
}

  我们之前分析过,在handlerMethod 保存着消费者的目标类及方法信息。我们继续看invoke方法

public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
	if (this.invokerHandlerMethod != null) {
		//方法上配置了RabbitListener注解的处理
		return this.invokerHandlerMethod.invoke(message, providedArgs);
	}
	else {
		//类上配置了RabbitListener注解的处理
		return this.delegatingHandler.invoke(message, providedArgs);
	}
}

  上面两种情况,即我们文章最开头分析的,如果方法上配置了RabbitListener注解的,此时invokerHandlerMethod不为空,而类上配置了RabbitListener注解的,此时应该调用代理对象delegatingHandler来处理消息。而内部实现大同小异。
  我们先来看方法配置了RabbitListener注解的情况 。

public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
	//获取方法的参数值
    Object[] args = getMethodArgumentValues(message, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                "' with arguments " + Arrays.toString(args));
    }
    // 通过反射调用我们业务的消费者代码
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                "] returned [" + returnValue + "]");
    }
    return returnValue;
}

  上面代码中,真正值得我们研究的是getMethodArgumentValues方法,这个方法的内部实现也非常有代表性。

private Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                args[i] = this.argumentResolvers.resolveArgument(parameter, message);
                continue;
            }
            catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                }
                throw ex;
            }
        }
        if (args[i] == null) {
            throw new MethodArgumentResolutionException(message, parameter,
                    getArgumentResolutionErrorMessage("No suitable resolver for", i));
        }
    }
    return args;
}

  看到上面的代码,我相信此时此刻,如果看过我之前Spring MVC博客的小伙伴,肯定很熟悉了,这不就是Spring MVC 将request请求参数封装到Controller方法中参数的逻辑嘛,非常典型的代码,获取方法中每一个参数属性对象,获取所有方法参数解析器,调用每个解析器的resolveArgument方法,看此解析器对该方法参数是否有解析能力,如果有,则调用参数解析器的argumentResolvers方法,解析得到方法参数值,依此类推,得到方法的所有参数值,再通过反射调用目标方法,我们先来看这些方法解析器是何时初始化到argumentResolvers属性的。在代码中寻寻觅觅,最终在DefaultMessageHandlerMethodFactory的afterPropertiesSet方法中初始化方法解析器的。
在这里插入图片描述
  接下来,我们来看看初始化了哪些方法参数解析器。

protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
    ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory ?
            (ConfigurableBeanFactory) this.beanFactory : null);
    resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, cbf));
    resolvers.add(new HeadersMethodArgumentResolver());

 
    resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));

    if (this.customArgumentResolvers != null) {
        resolvers.addAll(this.customArgumentResolvers);
    }
    resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));
    return resolvers;
}

  从上面代码上,我们默认看到了4个方法解析器。我们随便挑选一个来看看。如MessageMethodArgumentResolver。

public class MessageMethodArgumentResolver implements HandlerMethodArgumentResolver {
    ... 略

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Message.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
        Class<?> targetMessageType = parameter.getParameterType();
        Class<?> targetPayloadType = getPayloadType(parameter);

        if (!targetMessageType.isAssignableFrom(message.getClass())) {
            throw new MethodArgumentTypeMismatchException(message, parameter, "Actual message type '" +
                    ClassUtils.getDescriptiveType(message) + "' does not match expected type '" +
                    ClassUtils.getQualifiedName(targetMessageType) + "'");
        }

        Object payload = message.getPayload();
        if (payload == null || targetPayloadType.isInstance(payload)) {
            return message;
        }

        if (isEmptyPayload(payload)) {
            throw new MessageConversionException(message, "Cannot convert from actual payload type '" +
                    ClassUtils.getDescriptiveType(payload) + "' to expected payload type '" +
                    ClassUtils.getQualifiedName(targetPayloadType) + "' when payload is empty");
        }

        payload = convertPayload(message, parameter, targetPayloadType);
        return MessageBuilder.createMessage(payload, message.getHeaders());
    }
    
    public static <T> Message<T> createMessage(T payload, MessageHeaders messageHeaders) {
        Assert.notNull(payload, "Payload must not be null");
        Assert.notNull(messageHeaders, "MessageHeaders must not be null");
        if (payload instanceof Throwable) {
            return (Message<T>) new ErrorMessage((Throwable) payload, messageHeaders);
        }
        else {
            return new GenericMessage<T>(payload, messageHeaders);
        }
    }
    ... 略
}

  我们着重看supportsParameter和resolveArgument方法,这两个方法的业务逻辑可能有点复杂,但是实现原理却是非常简单,只要传入的参数supportsParameter方法返true,resolveArgument方法则返回相应的对象即可,你看上面的方法,supportsParameter方法说,我支持方法参数是Message对象的参数,resolveArgument方法,则从rabbit中的header,body 等地方取出属性信息,封装成Message对象返回即可,是不是很简单,有人肯定觉得,你说得比唱得还好听。那我们自己又来写一个例子看看。
  大家还记得之前我们写过的MyRabbitListenerConfigurer类实例吗?现在需要用到这个实例,下面,我们在之前的例子上加深一下。

  1. 添加我自己手写的参数解析器
@Component
public class MyRabbitListenerConfigurer implements RabbitListenerConfigurer {
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        MessageHandlerMethodFactory messageHandlerMethodFactory = createDefaultMessageHandlerMethodFactory(registrar);
        registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
    }


	private MessageHandlerMethodFactory createDefaultMessageHandlerMethodFactory(RabbitListenerEndpointRegistrar registrar ) {
	    DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory();
	    defaultFactory.setBeanFactory((BeanFactory) getFieldValueByFieldName("beanFactory", registrar));
	    //defaultFactory.setMessageConverter(new MyMessageConverter());
	    // 会覆盖掉之前的属性
	    //List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>();
	    //customArgumentResolvers.add(new UserParamterRelover());
	    //defaultFactory.setArgumentResolvers(customArgumentResolvers);
	    defaultFactory.afterPropertiesSet();
	    org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite handlerMethodArgumentResolverComposite =
	            (org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite)getFieldValueByFieldName("argumentResolvers",defaultFactory);
	    List<HandlerMethodArgumentResolver> argumentResolvers = (List<HandlerMethodArgumentResolver>)getFieldValueByFieldName("argumentResolvers",handlerMethodArgumentResolverComposite);
	    argumentResolvers.add(0,new UserParamterRelover());
	    return defaultFactory;
	}
    private Object getFieldValueByFieldName(String fieldName, Object object) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            //设置对象的访问权限,保证对private的属性的访问
            field.setAccessible(true);
            return  field.get(object);
        } catch (Exception e) {

            return null;
        }
    }
}
  1. 创建方法参数解析器
public class UserParamterRelover implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return User.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
        System.out.println("----------");
        String object =  message.getPayload().toString();
        String [] as = object.split(" ");
        User user =  new User();
        user.setContent(as[0]);
        user.setSendTime(as[1]);
        return user;
    }
}

  方法参数解析器的原理很简单,如果方法参数是User对象,则由我们创建的参数解析器来处理,参数解析器的原理也很简单,就是获取消息,以空格分割,第一个参数是消息内容,第二个参数是消息发送时间。

  1. 创建消费者
@Component
@Slf4j
public class RabbitCustomArgumentListener {

    @RabbitHandler
    @RabbitListener(queues = "#{customArgumentName.name}")
    public void consumeMessage(User user) {
        System.out.println(JSON.toJSONString(user));
    }
}

方法参数是User对象
在这里插入图片描述
5. 创建生产者

@Value("${eb.config.rabbitQueue.customArgumentName}")
public String customArgumentName;

@RequestMapping("customArgumentNameTest")
public String customArgumentNameTest() {
    String message = "测试xxx " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(customArgumentName, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}

  创建一个生产者,生产一个消息,并加上生产时间,以空格隔开。
在这里插入图片描述
  从测试结果中,我们看出,消息被我们自定义的方法参数解析器解析处理了。
  到这里,我们对Spring Boot 集成RabbitMQ 这一块的源码基本解析完了,剩下可能是一些边边角角的东西,有兴趣的同学可以继续深入研究。这篇博客,我也写了几个星期了,业务需求也比较忙,因为就告一段落。
  如果有坚持看到这里的小伙伴,我觉得你肯定对Spring Boot和RabbitMQ 的源码有了一定的理解,那我们来考虑一个问题。每一次创建一个消费者时,都需要在配置文件中声明一个队列,如下图。
在这里插入图片描述
  我觉得这个队列声明是"多余"的,给开发者带来麻烦,如果只写生产者代码,再写消费者代码,而省略队列的声明,那该多好啊。当然,可能在RabbitListener的QueueBinding属性中声明也可以的,但是现在我就不想这样声明,我想让系统来帮我声明队列。而我只需要在RabbitListener写上队列的名称即可。那有什么好的办法呢?

  1. 创建生产者代码
@Value("${eb.config.rabbitQueue.autoCreateQueue}")
public String autoCreateQueue;

@RequestMapping("autoCreateQueueTest")
public String autoCreateQueueTest() {
    String message = "测试xxx " + System.currentTimeMillis();
    rabbitTemplate.convertAndSend(autoCreateQueue, message);
    System.out.println("发送消息为:" + message);
    return "Sucess";
}
  1. 创建消费者代码
@Component
@Slf4j
public class RabbitAutoCreateQueueListener {

    @RabbitHandler
    @RabbitListener(queues = "#{autoCreateQueue.name}")
    public void consumeMessage(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long delivertTag, Channel channel) {
        try {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("------接收到消息:" + message + ",接收时间 : " + df.format(new Date()));
            String bs[] = message.split(" ");
            System.out.println("消费掉消息:" + message + "  发送时间:" + df.format(new Date(Long.parseLong(bs[bs.length - 1]))) + "     消费完成时间:" + df.format(new Date()));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }
}
  1. 配置文件中配置队列名称
    在这里插入图片描述

  有经验的小伙伴肯定会知道,如果上面这样写,肯定启动都为报错,第一,队列没有声明。第二#{autoCreateQueue.name},找不到autoCreateQueue的值(需要注意的是autoCreateQueue就是配置文件中rabbitMQ队列名称的key,什么意思呢?#{autoCreateQueue.name}中的autoCreateQueue一定和eb.config.rabbitQueue.autoCreateQueue=xxx。 中的加粗名称相等)。
  我们先来解决#{autoCreateQueue.name}问题.

  1. 创建ResolverBeanPostProcessor处理器。修改RabbitListenerAnnotationBeanPostProcessor的BeanExpressionResolver。
@Component
public class ResolverBeanPostProcessor implements BeanPostProcessor , BeanFactoryAware , EnvironmentAware {

    private BeanFactory beanFactory;

    public static boolean flag = true;

    public Environment environment;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //System.out.println("------before--------------------");
        if(flag){
            synchronized (ResolverBeanPostProcessor.class){
                if(flag){
                    if(beanFactory instanceof DefaultListableBeanFactory){
                        RabbitListenerAnnotationBeanPostProcessor beanPostProcessor =  beanFactory.getBean(RabbitListenerAnnotationBeanPostProcessor.class);
                        BeanExpressionResolver resolver = (BeanExpressionResolver) MyRabbitListenerConfigurer.getFieldValueByFieldName("resolver", beanPostProcessor);
                        String active = environment.getProperty("spring.profiles.active");
                        MyRabbitListenerConfigurer.setFieldValueByFieldName("resolver",beanPostProcessor,
                                new MyStandardBeanExpressionResolver(((DefaultListableBeanFactory) beanFactory).getBeanClassLoader(),active));

                        System.out.println("---------------");
                    }
                    flag = false;
                }
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //System.out.println("--------------after------------");
        if(bean instanceof RabbitListenerEndpointRegistry){
          //  System.out.println("--------我成功了-------");
        }
        return bean;
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("-------------beanfactory-------");
        this.beanFactory = beanFactory;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment =environment;
    }
}
  1. 创建自定义的ResolverBeanPostProcessor
@Slf4j
public class MyStandardBeanExpressionResolver extends StandardBeanExpressionResolver {

    public String env;
    public MyStandardBeanExpressionResolver() {

    }

    public MyStandardBeanExpressionResolver(ClassLoader beanClassLoader,String env) {
        super(beanClassLoader);
        this.env = env;
    }

    @Override
    public Object evaluate(String value, BeanExpressionContext evalContext) throws BeansException {
        try {
            System.out.println("--------valuexxx--------------" + value);
            Object object = super.evaluate(value, evalContext);
            System.out.println("---" + object);
            return object;
        } catch (Exception e) {
            //        e.printStackTrace();
            log.info(" value = " + value + " 并没有申明队列,抛出异常,系统来帮忙申明队列  ");
        } catch (Throwable t) {
            //    t.printStackTrace();
            log.info(" value = " + value + " 并没有申明队列,系统来帮忙申明队列  ");
        }
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("application-"+env+".yml");
        Processor processor = new Processor(resource, null);
        Map<String, String> notExitQueue = new HashMap<>();
        Map<String, Object> source = processor.process();
        // 从配置文件中获取队列名
        for (Map.Entry<String, Object> map : source.entrySet()) {
            if (map.getKey().startsWith("eb.config.rabbitQueue")) {
                System.out.println("key : " + map.getKey() + " value : " + map.getValue());
                String queueName = map.getKey().replaceAll("eb.config.rabbitQueue.", "");
                notExitQueue.put(queueName, map.getValue().toString());
            }
        }
        System.out.println("=======notExitQueue=====" + JSON.toJSONString(notExitQueue));
        String a = value;
        a = a.substring(2);
        a = a.substring(0, a.length() - 1);
        String bs[] = a.split("\\.");
        String queueName = bs[0];
        String queue = notExitQueue.get(queueName);
        System.out.println(queue);
        return queue;
    }

    public static void main(String[] args) {
        String a = "#{autoCreateQueue.name}";
        a = a.substring(2);
        a = a.substring(0, a.length() - 1);
        String bs[] = a.split("\\.");
        System.out.println(bs[0]);
    }
}

MyStandardBeanExpressionResolver的实现原理也很简单,如果解析表达式抛出异常,则从我们yml配置文件中取。

  1. 重写RabbitAdmin
@Bean
public MyRabbitAdmin myRabbitAdmin(ConnectionFactory connectionFactory, ApplicationContext applicationContext) {
    return new MyRabbitAdmin(connectionFactory,applicationContext);
}

public class MyRabbitAdmin extends RabbitAdmin   implements BeanFactoryAware {

    private ApplicationContext applicationContext;

    private BeanFactory beanFactory;

    private boolean declareCollections = true;

    public MyRabbitAdmin(ConnectionFactory connectionFactory, ApplicationContext applicationContext) {
        super(connectionFactory);
        this.applicationContext = applicationContext;
    }

    @Override
    public void initialize() {
        super.initialize();
        RabbitListenerEndpointRegistry registry = SpringContextUtils.getBean(RabbitListenerEndpointRegistry.class);
        Collection<MessageListenerContainer> messageListenerContainers = registry.getListenerContainers();
        Collection<Queue> contextQueues = new LinkedList<Queue>(
                this.applicationContext.getBeansOfType(Queue.class).values());
        Collection<Collection> collections = this.declareCollections
                ? this.applicationContext.getBeansOfType(Collection.class, false, false).values()
                : Collections.<Collection>emptyList();
        for (Collection<?> collection : collections) {
            if (collection.size() > 0 && collection.iterator().next() instanceof Declarable) {
                for (Object declarable : collection) {
                    if (declarable instanceof Queue) {
                        contextQueues.add((Queue) declarable);
                    }
                }
            }
        }
        final Collection<Queue> exitQueues = filterDeclarables(contextQueues);
        List<String> exitsQueueNames = new ArrayList<>();
        final Collection<Queue> queues = new ArrayList<>();
        for (Queue exitQueue : exitQueues) {
            exitsQueueNames.add(exitQueue.getName());
            System.out.println("exit queueName " + exitQueue.getName());
        }

        for (MessageListenerContainer messageListenerContainer : messageListenerContainers) {
            if (messageListenerContainer instanceof SimpleMessageListenerContainer) {
                String[] queueNames = ((SimpleMessageListenerContainer) messageListenerContainer).getQueueNames();
                for (String queueName : queueNames) {
                    System.out.println("=======queueName====" + queueName);
                }
            }
        }
        System.out.println("========MyRabbitAdmin 被 ===========");
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("application-" + SpringContextUtils.getActiveProfile() + ".yml");
        Processor processor = new Processor(resource, null);
        Map<String,String> notExitQueue = new HashMap<>();
        Map<String, Object> source = processor.process();
        // 获取容器中所有没有声明的队列
        for(Map.Entry<String,Object> map : source.entrySet()){
            if(map.getKey().startsWith("eb.config.rabbitQueue")){
                System.out.println("key : " + map.getKey() + " value : " + map.getValue());
                if(!exitsQueueNames.contains(map.getValue())){
                    String queueName = map.getKey().replaceAll("eb.config.rabbitQueue.","");
                    System.out.println("向容器中注册队列: " + queueName);
                    notExitQueue.put(queueName,map.getValue().toString());
                }
            }
        }
        System.out.println("=======notExitQueue=====" + JSON.toJSONString(notExitQueue));
        if(!notExitQueue.isEmpty()){
           for(Map.Entry<String,String> notExitQue:notExitQueue.entrySet() ){
               Queue queue = new Queue(notExitQue.getValue());
               queues.add(queue);
               if(beanFactory instanceof DefaultListableBeanFactory){
                   ((DefaultListableBeanFactory) beanFactory).registerSingleton(notExitQue.getKey(),queue);
               }
           }
        }
        if (!CollectionUtils.isEmpty(queues)) {
            getRabbitTemplate().execute(new ChannelCallback<Object>() {
                @Override
                public Object doInRabbit(Channel channel) throws Exception {
                    //声明队列
                    declareQueues(channel, queues.toArray(new Queue[queues.size()]));
                    return null;
                }
            });
        }
    }
    private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables) {
        Collection<T> filtered = new ArrayList<T>();
        for (T declarable : declarables) {
            Collection<?> adminsWithWhichToDeclare = declarable.getDeclaringAdmins();
            if (declarable.shouldDeclare() &&
                    (adminsWithWhichToDeclare.isEmpty() || adminsWithWhichToDeclare.contains(this))) {
                filtered.add(declarable);
            }
        }
        return filtered;
    }

    private AMQP.Queue.DeclareOk[] declareQueues(final Channel channel, final Queue... queues) throws IOException {
        List<AMQP.Queue.DeclareOk> declareOks = new ArrayList<AMQP.Queue.DeclareOk>(queues.length);
        for (int i = 0; i < queues.length; i++) {
            Queue queue = queues[i];
            if (!queue.getName().startsWith("amq.")) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("declaring Queue '" + queue.getName() + "'");
                }
                try {
                    try {
                        AMQP.Queue.DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
                                queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());
                        declareOks.add(declareOk);
                    } catch (IllegalArgumentException e) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.error("Exception while declaring queue: '" + queue.getName() + "'");
                        }
                        try {
                            if (channel instanceof ChannelProxy) {
                                ((ChannelProxy) channel).getTargetChannel().close();
                            }
                        } catch (TimeoutException e1) {
                        }
                        throw new IOException(e);
                    }
                } catch (IOException e) {
                    logger.error("队列失败", e);
                }
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug(queue.getName() + ": Queue with name that starts with 'amq.' cannot be declared.");
            }
        }
        return declareOks.toArray(new AMQP.Queue.DeclareOk[declareOks.size()]);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

  有小伙伴可能会觉得好奇,为什么我们重写的RabbitAdmin,就会替换掉系统的RabbitAdmin。我们来看看RabbitAdmin的声明。

@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
@ConditionalOnMissingBean(AmqpAdmin.class)
public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
}

  看到ConditionalOnMissingBean注解没有,当用户自定义RabbitAdmin后,系统就不会再帮我们声明RabbitAdmin了。
  MyRabbitAdmin的实现功能也很简单,如果我们没有手动声明队列,系统帮我们声明。
  看一下效果。
在这里插入图片描述
  不相信的小伙伴,可以注释掉MyRabbitAdmin的declareQueues方法,为了测试效果,修改autoCreateQueue为一个没有声明的队列,因为声明一次之后,Rabbit服务器就有存在这个队列,即使你注释掉声明队列的代码,也不会报错,如果创建一个新的队列名称,RabbitMQ服务器没有这个队列,在启动时就会报错。

注释掉声明队列代码
在这里插入图片描述
配置一个从来没有使用过的队列名称
在这里插入图片描述
项目启动报错。
在这里插入图片描述

改回去,项目启动成功。

在这里插入图片描述
  到这里,我相信大家对RabbitMQ的源码及灵活运用都有了深刻理解,如果坚持看到这里的小伙伴,我相信你也有你的收获,因为业务需求急,就到这里告一段落了,有疑问或者有问题的小伙伴给我留言,有问题,我们共同进步。

参考文章
SpringBoot整合高级消息队列RabbitMQ及原理

springboot + rabbitmq 如何实现消息确认机制(踩坑经验)

SpringBoot整合RabbitMQ, 实现生产者与消费者的功能

Springboot集成Quartz和RabbitMQ时设置为不自动运行

本文代码在github地址
https://github.com/quyixiao/spring-boot-study

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Spring BootRabbitMQ整合可以通过使用Spring AMQP实现。下面是一个简单的步骤: 1. 添加依赖:在`pom.xml`文件中添加以下依赖关系: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> ``` 2. 配置RabbitMQ连接:在`application.properties`或`application.yml`文件中配置RabbitMQ的连接信息,例如: ```properties spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest ``` 3. 创建生产者:创建一个简单的生产者,用于向RabbitMQ发送消息。你可以使用`RabbitTemplate`类来发送消息,例如: ```java import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MessageProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String message) { rabbitTemplate.convertAndSend("exchangeName", "routingKey", message); } } ``` 4. 创建消费者:创建一个简单的消费者,用于接收RabbitMQ发送的消息。你可以使用`@RabbitListener`注解来定义一个消息监听器,例如: ```java import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class MessageConsumer { @RabbitListener(queues = "queueName") public void receiveMessage(String message) { System.out.println("Received message: " + message); } } ``` 5. 启用RabbitMQ:通过在Spring Boot应用程序的主类上添加`@EnableRabbit`注解来启用RabbitMQ功能,例如: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.amqp.rabbit.annotation.EnableRabbit; @SpringBootApplication @EnableRabbit public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 这就是整合Spring BootRabbitMQ的基本步骤。你可以根据自己的需求进行更多的高级配置和定制。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值