RabbitMQ与SpringBoot简单结合(二)结合使用简单介绍

在之前的博客中,我们介绍了如何搭建RabbitMQ环境,在这篇博客中,会介绍SpringBoot和RabbitMQ的简单的综合使用。


目录

RabbitMQ对比ActiveMQ结合SpringBoot的优点

结合使用的简单介绍

POM.xml添加依赖

修改application.properties

创建RabbitMQ相关类

创建消息提供者

创建消息消费者

创建Controller调用模拟发送消息

测试结果

补充

AmqpTemplate与RabbitTemplate

SpringBoot与RabbitMQ使用手动Ack


RabbitMQ对比ActiveMQ结合SpringBoot的优点

这里所说的优点,其实是我个人的理解,主要目的仅仅是为了帮助我更好的理解与使用RabbitMQ。

在之前博客中,我们提到了SpringBoot结合ActiveMQ,消费者默认只接收queue的消息,而如果将spring.jms.pub-sub-domain=true之后,我们将只能接收topic消息,如果要让其既消费queue消息又消费topic消息是要进行不少的改动的。

另外一点就是,RabbitMQ使用Erlang语言编写,所以并发能力很强,性能极其好,延时很低。

结合使用的简单介绍

POM.xml添加依赖

只需要在pom.xml文件添加如下:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

修改application.properties

spring.applicaiton.name=spring-boot-rabbitmq-sender
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=lei

根据自己的配置设置RabbitMQ的配置信息,这里设置了virtual-host,如果使用默认的virtual-host(即 /)则可以不设置此项。

创建RabbitMQ相关类

我们知道RabbitMQ 有queue和exchange,消息经由exchange被发送到需要发送到的与此exchange绑定queue(或者其他绑定的exchange),因而我们需要在使用之前定义 queue和exchange,并绑定在一起。

@Configuration
public class RabbitConf {
    @Bean
    public Queue queue() {
        return new Queue("queue");
    }
    @Bean(name="message")
    public Queue queueMessage() {
        return new Queue("topic.message");
    }
    @Bean(name="messages")
    public Queue queueMessages() {
        return new Queue("topic.messages");
    }
    @Bean(name="Amessage")
    public Queue AMessage() {
        return new Queue("fanout.A");
    }
    @Bean(name="Bmessage")
    public Queue BMessage() {
        return new Queue("fanout.B");
    }
    @Bean(name="Cmessage")
    public Queue CMessage() {
        return new Queue("fanout.C");
    }
    @Bean
    public TopicExchange exchange() {
        return new TopicExchange("exchange");
    }
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");//配置广播路由器
    }
    @Bean
    Binding bindingExchangeMessage(@Qualifier("message") Queue queueMessage, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
    }
    @Bean
    Binding bindingExchangeMessages(@Qualifier("messages") Queue queueMessages, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
    }
    @Bean
    Binding bindingExchangeA(@Qualifier("Amessage") Queue AMessage,FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(AMessage).to(fanoutExchange);
    }
    @Bean
    Binding bindingExchangeB(@Qualifier("Bmessage") Queue BMessage, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(BMessage).to(fanoutExchange);
    }
    @Bean
    Binding bindingExchangeC(@Qualifier("Cmessage") Queue CMessage, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(CMessage).to(fanoutExchange);
    }
}

从上面代码我们 可以看到 我们定义了一个名为exchange的Topic Exchange(也就是会通过队列与其绑定时候的binding key与发送到此exchange的消息的routing key进行模糊匹配,如果binding key能匹配routing key则发送消息到此队列)和一个名为fanoutExchange的Fanout Exchange(发送到此exchange的消息 将直接发送到与这个exchange绑定的队列中)。并且分别定义了名为queue,topic.message,topic.messages,fanout.A,fanout.B,fanout.C 等6个queue,而且 queue与默认队列exchange绑定(这里没有显示调用绑定,所以就是使用默认的绑定),topic.message 以topic.message 为binding key与exchange绑定,topic.messages 以topic.# 为binding key与exchange绑定,其余3个 fanout.A,fanout.B,fanout.C与fanoutExchange绑定(由于是Fanout Exchang 所以bindingKey无效,这里没设置)。

创建消息提供者

@Component
public class HelloSender {
	@Autowired
	private AmqpTemplate template;
	
	public void send() {
		User user = new User();
		user.setUsername("username_lei");
		user.setPassword("password_lei");
		template.convertAndSend("queue",user);
	}
	public void sendTopicMessage() {
		template.convertAndSend("exchange","topic.message","hello,rabbit topic.message!");
	}
	public void sendTopicMessages() {
		template.convertAndSend("exchange","topic.messages","hello,rabbit topic.messages!");
	}
	public void sendFanoutMessage() {
		template.convertAndSend("fanoutExchange","","hello,rabbit fanoutmessage");
	}
}

上面代码我看可以看出,我们主要使用注入的AmqpTemplate来进行消息的发送。更多有关的操作 可以通过注入RabbitTemplate来使用。RabbitTemplate继承于AmqpTemplate,可以实现更强大的功能。

我们写了4个消息发送相关的方法。

send方法 里面调用了 template.convertAndSend(routingKey,message) 只指定了消息的routingKey,也就是这个消息发送到默认exchange,然后将消息发送到 指定的队列中(根据queue与exchange绑定的bindingKey 与routingKey匹配)。

sendTopicMessage 则 以routingKey为topic.message,发送一条消息体为("hello,rabbit topic.message!")的消息 到名为exchange的交换器。

sendTopicMessages 是以routingKey为topic.messages,发送一条消息体为("hello,rabbit topic.messages!")的消息 到名为exchange的交换器。

sendFanoutMessage 以routingKey为 “”,发送一条消息体为"hello,rabbit fanoutmessage"的消息到名为fanoutExchange的消息。

创建消息消费者

@Component
public class HelloReceive {

    @RabbitListener(queues="queue")    //监听器监听指定的Queue
    public void processC(User user) {
        System.out.println("Receive:"+user);
    }
    @RabbitListener(queues="fanout.A")
    public void processA(String str1) {
        System.out.println("ReceiveA:"+str1);
    }
    @RabbitListener(queues="fanout.B")
    public void processB(String str) {
        System.out.println("ReceiveB:"+str);
    }
    @RabbitListener(queues="fanout.C")
    public void processC(String str) {
        System.out.println("ReceiveC:"+str);
    }
    @RabbitListener(queues="topic.message")    //监听器监听指定的Queue
    public void process1(String str) {    
        System.out.println("message:"+str);
    }
    @RabbitListener(queues="topic.messages")    //监听器监听指定的Queue
    public void process2(String str) {
        System.out.println("messages:"+str);
    }
}

这里看到我们创建了不同方法监听了 不同消息队列的消息(这里queues可以是个数组类型的,也就是方法消费多个消息队列的消息),跟ActiveMQ类似 我们使用@RabbitListener(queues)就能把一个方法定义为可以消费 消息队列中消息的方法。

创建Controller调用模拟发送消息

@RestController
public class HelloController {
    @Autowired
    private HelloSender helloSender;
	
    @RequestMapping("/sendMessage")
    public void sendMessage() {
        helloSender.send();
        helloSender.sendTopicMessage();
        helloSender.sendTopicMessages();
        helloSender.sendFanoutMessage();
    }
}

这里我们仅仅是注入HelloSender,然后在访问localhost:8080/sendMessage 的时候发送消息。当然也可以使用单元测试类来测试结果。

测试结果

最后我们运行程序,然后调用localhost:8080/sendMessage观察后台打印:

从结果中我们可以看到,消息打印符合 FanoutExchange 将消息发送到所有与之绑定的队列中,TopicExchange根据消息发送设置的RoutingKey 与 所有与之绑定的队列的BindingKey相匹配,发送消息到匹配成功的队列。(进行通配符匹配) DirectExchange 是RoutingKey和BindingKey完全匹配。

补充

AmqpTemplate与RabbitTemplate

Spring AMQP提供了一个发送和接收消息的操作模板类AmqpTemplate。 AmqpTemplate它定义包含了发送和接收消息等的一些基本的操作功能。RabbitTemplate是AmqpTemplate的一个实现。

RabbitTemplate支持消息的确认与返回,为了返回消息,RabbitTemplate 需要设置mandatory 属性为true,并且CachingConnectionFactory 的publisherReturns属性也需要设置为true。返回的消息会根据它注册的RabbitTemplate.ReturnCallback setReturnCallback 回调发送到给客户端,

一个RabbitTemplate仅能支持一个ReturnCallback 。

为了确认Confirms消息, CachingConnectionFactory 的publisherConfirms 属性也需要设置为true,确认的消息会根据它注册的RabbitTemplate.ConfirmCallback setConfirmCallback回调发送到给客户端。一个RabbitTemplate也仅能支持一个ConfirmCallback。

SpringBoot与RabbitMQ使用手动Ack

首先需要在application.properties设置如下,取消自动Ack

#开启发送确认 默认为false

spring.rabbitmq.publisher-confirms=true

#开启发送失败退回 默认为false

spring.rabbitmq.publisher-returns=true

spring.rabbitmq.listener.direct.acknowledge-mode=manual

 spring.rabbitmq.listener.simple.acknowledge-mode= manual

然后要给注入的RabbitTemplate 添加一个确认回调函数如下,注意这里只能对一个rabbitTemplate 设置一个confirmCallback设置第二个会报错:

rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
    if (!ack) {
        System.out.println("HelloSender消息处理失败" + cause + correlationData.toString());
    } else {
        System.out.println("HelloSender 消息处理成功 ");
    }
});

这里仅仅判断了ack,并没有进行多的操作,其实发送ack过来可以得到其他多余的信息,然后做别的操作,这里只是简单示例。

在消息消费端 也要在处理完消息之后发送ack回来如下方法:

@Component
public class HelloReceive {
    @RabbitListener(queues = "queue")
    public void process(String msg, Channel channel, Message message) throws IOException{
        System.out.println("helloReceive:"+msg);
        try{
            //根据消息进行操作
            //告诉服务器收到这条消息 已经被消费 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会继续发
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            System.out.println("receiver success");
        }catch(IOException e){
            e.printStackTrace();
            //丢弃这条消息 第三个参数为是否重新入栈,如果为false则这条消息被丢弃,如果为true则直接重新入栈(慎用,如果处理一直错误可能会发生死循环)
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true);
            System.out.println("receiver fail");
        }
    }
}

可以看到我们在处理try catch中 如果成功执行就发送ack  如果抛出异常就不发送ack,而是调用channel.basicNack() 发送nack,注意第三个参数 为true的时候重新放回队列(慎用设置为true,因为如果消费者一直处理错误,会发生死循环),如果为false,直接丢弃。这样就实现了如果客户端处理出错,消息被回退。

当然我们也可以直接获取ConnectionFactory,然后自己创建Connection、Channel等来操作RabbitMQ,具体操作可以参照我RabbitMQ系列博客。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值