rabbitmq第三章【在springboot中使用(注解)】

上一章讲到了rabbitmq在普通java项目中的消费端与生产端

这一章讲解一下 在springboot中的注解方式

 

默认已经安装完成rabbitmq

 

上一章,rabbitmq在java中使用

上一章得出的使用rabbitmq顺序如下:

1:定义exchange   

2定义queue

3将queue与exchange绑定起来

 

那么在注解中我们从简到难也按照这样的顺序进行学习:

首先讲解使用rabbitadmin接入

首先在maven中引入依赖:

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

 

新建一个config类  用于配置rabbitmq相关选项

@Configuration
//本类地址
@ComponentScan({"com.rabbitmqstu.integration.config"})
public class RabbitmqConfig {

    /**
     * 获取连接工厂
     * @return
     */
    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        //rabbitmq服务的地址
        connectionFactory.setHost("192.168.106.70");
        connectionFactory.setPort(5672);
//        connectionFactory.setPassword("guest");
//        connectionFactory.setUsername("guset");
        connectionFactory.setVirtualHost("/");
        return connectionFactory;
    }
    /**
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }



}

在spring容器中注入了rabbitadmin后 接下来开始rabbitadmin的使用

我这里使用junit的测试类进行定义


@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = TestDeclare.class)
@ComponentScan
public class TestDeclare {

    @Resource
    private RabbitAdmin rabbitAdmin;


    /**
     * 定义exchange 定义queue 定义绑定
     *
     */
    @Test
    public void test1() {
        /** 1声明交换机  */
        rabbitAdmin.declareExchange(new DirectExchange("springboottest.direct", true
                , true));
        rabbitAdmin.declareExchange(new TopicExchange("springboottest.topic", true
                , true));
        rabbitAdmin.declareExchange(new FanoutExchange("springboottest.fanout", true
                , true));
        /** 1声明队列  */
        rabbitAdmin.declareQueue(new Queue("test.direct.queue", false));
        rabbitAdmin.declareQueue(new Queue("test.topic.queue", false));
        rabbitAdmin.declareQueue(new Queue("test.fanout.queue", false));
        /** 1声明绑定  */
        rabbitAdmin.declareBinding(new Binding("test.direct.queue",
                Binding.DestinationType.QUEUE, "springboottest.direct",
                "direct", null));
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.topic.queue", false))//创建队列
                .to(new TopicExchange("springboottest.topic", false, false))//创建路由
                .with("user.#"));//创建routingkey
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.fanout.queue", false))//创建队列
                .to(new FanoutExchange("springboottest.fanout", false, false))//创建路由
        );
    }

}

执行这个方法后 查看rabbitmq网页可以看到已经定义成功了

当然也可以使用另一种方式定义  下面展示方式2:

/**
 * 声明交换机、队列、绑定的方式
 */
@Configuration
public class MyRabbitConfig {
    /**
     * 声明交换机001
     * @return
     */
    @Bean
    public TopicExchange exchange001(){
        return new TopicExchange("exchange-topic-001",true,true);
    }

    /**
     * 声明队列001
     * @return
     */
    @Bean
    public Queue queue001(){
        return new Queue("queue-topic-001",true);
    }
    /**
     * 声明绑定001
     * 如果是rabbit.study.* 就路由到队列001
     * @return
     */
    @Bean
    public Binding binding001(){
        return BindingBuilder.bind(queue001()).to(exchange001()).with("annotation.#");
    }




    /**
     * 声明交换机002
     * @return
     */
    @Bean
    public DirectExchange exchange002(){
        return new DirectExchange("exchange-topic-002",true,true);
    }

    /**
     * 声明队列002
     * @return
     */
    @Bean
    public Queue queue002(){
        return new Queue("queue-topic-002",true);
    }
    /**
     * 声明绑定001
     * 如果是rabbit.study.* 就路由到队列001
     * @return
     */
    @Bean
    public Binding binding002(){
        return BindingBuilder.bind(queue002()).to(exchange002()).with("annotation.002");
    }

    //todo:
    // 这里是消息接收
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){

        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);

        container.setQueues(queue001(),queue002());
        //当前消费者数量
        container.setConcurrentConsumers(1);
        //最大消费者数量
        container.setMaxConcurrentConsumers(5);
        //重回队列
        container.setDefaultRequeueRejected(false);
        //签收模式
        container.setAcknowledgeMode(AcknowledgeMode.AUTO);
        //
        //container.setConsumerTagStrategy(queue -> queue+"_"+ UUID.randomUUID().toString());

        //默认
//         container.setMessageListener(new ChannelAwareMessageListener() {
//            @Override
//            public void onMessage(Message message, Channel channel) throws Exception {
//                String body = new String(message.getBody());
//                System.err.println("消费者接收信息:"+body);
//            }
//        });

        //使用自定义消息接收方式
        MyMessageAdapter myMessageAdapter = new MyMessageAdapter();
        //消息适配器
        container.setMessageListener(myMessageAdapter);
        return container;
    }

}

上图中可以看到使用了spring的注解@bean 这样在项目启动时  如果类上有@configuration注解 就会将这些bean放入spring容器之中  一样可以完成exchange queue的定义与绑定

而最后一个bean其实就是消息的监听 从代码中可以看到这个监听了两个队列 分别是上面定义的两个queue、其中有一个参数是重回队列也就是当消息消费失败 打回到队列的末尾  这样以后还可以继续消费、这经常会出现问题、因为你一次消费失败 可能下一次还是失败  建议对失败数据进行持久化

这里有一个关键的信息就是这一行:

//签收模式
container.setAcknowledgeMode(AcknowledgeMode.AUTO);

这个是什么意思呢?比如你收快递当你收到了快递(消息)

你可以选择自动签收

AcknowledgeMode.AUTO

手动签收:

AcknowledgeMode.MANUAL

自动签收就是当消息接收到之后 自动准备接收下一条消息、而手动签收就必须自己手动执行签收之后才能继续签收

这也可以做到防止服务器过载

 

上面的代码中最后我定义了一个我自定义的消息接收类:


/**
 * 自定义消息接收类
 *
 *
 */
public class MyMessageAdapter implements ChannelAwareMessageListener {


    /**
     * 消息接收时执行的函数
     * @param message
     * @param channel
     * @throws Exception
     */
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //接收到的消息
        String body = new String(message.getBody());
        MessageProperties messageProperties = message.getMessageProperties();
        Map<String, Object> headers = messageProperties.getHeaders();
        //如果header中有数据 就循环输出
        if (headers != null) {
            headers.entrySet().stream().forEach((e) -> System.out.println("key:" + e.getKey() + "----value:" + e.getValue()));
        }
        System.err.println("消费者接收信息:" + body);

        //---------------如果是手工ack 就需要下面的代码
        //---------------如果是手工ack 就需要下面的代码
        //---------------如果是手工ack 就需要下面的代码

        //消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
//        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认成功收到消息
        //ack返回false,并重新回到队列,api里面解释得很清楚
//        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        //拒绝消息
//        channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
    }

    @Override
    public void onMessage(Message message) {
        String body = new String(message.getBody());
        MessageProperties messageProperties = message.getMessageProperties();
        Map<String, Object> headers = messageProperties.getHeaders();
        if (headers != null) {
            Set<Map.Entry<String, Object>> entries = headers.entrySet();
            Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> next = iterator.next();
                System.out.println("key:" + next.getKey() + "----value:" + next.getValue());
            }
        }
        System.err.println("消费者接收信息:" + body);
    }

}

上面可以看到第一个方法有channel 第二个方法没有

这个channel是用于手动签收的、当签收模式为手动签收时如果你没有进行签收、则不会继续接收消息!!!

 

消费者和定义就讲完了  接下来讲消息的发送:

这里一样使用junit的测试类

package com.rabbitmqstu;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import javax.annotation.Resource;
import java.util.Map;

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = TestSend.class)
@ComponentScan
public class TestSend {

    @Resource
    private RabbitAdmin rabbitAdmin;

    @Resource
    private RabbitTemplate rabbitTemplate;


    /**
     * 发送消息
     * <p>
     * 添加额外设置
     */
    @Test
    public void testSend0() {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.getHeaders().put("desc", "信息描述");
        messageProperties.getHeaders().put("type", "定义消息类型");
        //定义发送的消息
        Message message = new Message("发送消息".getBytes(), messageProperties);
        //设置成功或失败返回 用于可靠性投递
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        rabbitTemplate.convertAndSend("springboottest.topic", "user.testtopic", message, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                System.out.println("测试额外添加设置");
                message.getMessageProperties().getHeaders().put("test", "测试额外添加消息");
                return message;
            }
        });
    }

    /**
     * 发送消息
     * <p>
     * 添加额外设置
     */
    @Test
    public void testSend1() {
        rabbitTemplate.convertAndSend("springboottest.topic", "user.testtopic", "test send message", new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().getHeaders().put("desc", "信息描述");
                message.getMessageProperties().getHeaders().put("type", "定义消息类型");
                return message;
            }
        });
    }

    /**
     * 发送消息
     */
    @Test
    public void testSend2() {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.getHeaders().put("desc", "信息描述");
        messageProperties.getHeaders().put("type", "定义消息类型");
        //定义发送的消息
        Message message = new Message("测试发送消息:我老婆是古力娜扎".getBytes(), messageProperties);
        rabbitTemplate.send("exchange-topic-001", "annotation.test", message);
    }

    final RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
        /**
         * 当没有路由成功进入这里
         *  比如我发送了消息给rabbitmq服务器  但是没有队列能够收到这条消息  就会返回回来
         * @param message
         * @param replyCode
         * @param replyText
         * @param exchange
         * @param routingKey
         */
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            System.out.println("***********return callback************");
            System.out.println("message:" + message.getBody().toString() + " replyCode:" + replyCode + " replyText:" + replyText + " exchange:" + exchange + " routingKey:" + routingKey);
            System.out.println("***********return callback************");
        }
    };

    final RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
        /**
         * 当ack成功或失败进入这里
         *
         * @param correlationData  唯一业务参数
         * @param ack   是否ack成功
         * @param cause 原因
         */
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            System.out.println("***********confirm callback************");
            System.out.println("correlationData:" + correlationData);
            //如果消费端选择    noack   不签收
            if (!ack) {
                System.err.println("ack失败!ack:" + ack + "异常处理");
            }
            System.out.println("***********confirm callback************");
        }
    };
}

我上面定义了多种消息发送    其实也就是是否多传参数 

但是得注意两个   我定义了两个对象  一个是confirmCallBack  一个是returnCallBack

注释中已经进行了解释    这里主要是为了说明:

当消息发送时我们可以针对消息签收成功与否?   消息是否被消费?   进行消息的管理  可以一定程度上的让消息发送更稳定

消息失败也有记录

上述已经完成了springboot项目中的  队列 交换机的定义、绑定、消息发送、消息接收

其实下面还有一个更简单的监听方式:

@Component
public class RabbitReceiver {


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "queueName", declare = "true"),
            exchange = @Exchange(value = "exchangeName", durable = "true", type = "topic",
                    ignoreDeclarationExceptions = "true"),
            key = "springboot.testroutingkeys"
    ))
    @RabbitHandler
    public void onMessage(Message message, Channel channel) throws IOException {
        String body = new String(message.getBody());
        MessageProperties messageProperties = message.getMessageProperties();
        Map<String, Object> headers = messageProperties.getHeaders();
        if (headers != null) {
            Set<Map.Entry<String, Object>> entries = headers.entrySet();
            entries.stream().forEach(kv -> System.out.println("key:"+kv.getKey()+"val:"+kv.getValue()));
        }
        System.err.println("消费者接收信息:" + body);
        //ack:手动ack确认成功收到消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

}

直接在注解之中定义了queue exchange 并且使用routingKey进行了绑定

消息接收

 

如此rabbitmq三部曲就完成了、其实这只是简单的入门、还有许多地方可以针对优化和设置、比如对象的转化(发送对象)、死信队列(失败的消息放到一个专门管理死亡消息的exchange)、消息限流、消息ttl

如果日后有需求 我看看是否在逐一进行讲解

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: RabbitMQ是一个基于AMQP协议的消息间件,提供了丰富的Exchange(交换机)类型来进行消息的路由和分发。其,topic类型的交换机可以根据消息的routing key来进行灵活的匹配和分发。 在Spring Boot使用RabbitMQ的topic类型交换机,首先需要在配置文件配置RabbitMQ的连接信息,如主机地址、端口号、用户名、密码等。 其次,需要创建一个RabbitMQ的配置类,使用@Configuration注解进行标识,并使用@EnableRabbit注解启用RabbitMQ相关的配置。在配置类,可以使用@Bean注解创建一个RabbitTemplate的实例,用于发送和接收消息。 接下来,在生产者,可以使用@Autowired注解注入RabbitTemplate实例,并调用其convertAndSend方法发送消息到指定的交换机和routing key。生产者可以根据具体的业务需求设置不同的routing key,用于消息的匹配和分发。 在消费者,同样可以使用@Autowired注解注入RabbitTemplate实例,并调用其receiveAndConvert方法接收消息。消费者需要在配置类使用@RabbitListener注解,指定要监听的队列,并根据需要设置不同的routing key或通配符进行匹配。 通过Spring Boot的自动配置,我们可以方便地使用RabbitMQ的topic类型交换机进行消息的发布和订阅。同时,使用Spring AMQP提供的注解和模板类,可以简化开发过程,提高代码的可读性和可维护性。 需要注意的是,使用topic类型交换机时,routing key是由一个或多个单词组成的字符串,用点号(.)进行分隔。其,单词可以包含字母、数字或通配符(#和*)。#用于匹配零个或多个单词,*用于匹配一个单词。 总之,通过使用RabbitMQ的topic类型交换机和Spring Boot提供的自动配置和注解,我们可以方便地实现消息的灵活路由和分发,满足不同业务场景的需求。 ### 回答2: RabbitMQ是一个开源的消息间件,用于实现可靠的消息传递。它使用AMQP(高级消息队列协议)作为消息传输协议,并通过交换机来路由消息。 Topic类型的交换机在RabbitMQ是一种灵活的交换机类型。它通过使用通配符的方式将消息路由到与匹配的路由键相关联的队列。使用Spring Boot框架与RabbitMQ集成,可以方便地实现Topic类型的交换机。 首先,在Spring Boot项目引入RabbitMQ的依赖,例如: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> ``` 然后,配置RabbitMQ的连接信息,例如: ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest ``` 接下来,在代码创建一个Topic类型的交换机,例如: ```java @Configuration public class RabbitMQConfig { private static final String TOPIC_EXCHANGE_NAME = "topicExchange"; @Bean public TopicExchange topicExchange() { return new TopicExchange(TOPIC_EXCHANGE_NAME); } } ``` 然后,创建一个消费者来监听队列的消息,例如: ```java @Component public class MessageListener { private static final String QUEUE_NAME = "messageQueue"; private static final String ROUTING_KEY = "message.*"; @RabbitListener(queues = QUEUE_NAME) public void handleMessage(String message) { System.out.println("Received message: " + message); } } ``` 最后,通过发送消息的方式来测试Topic类型的交换机,例如: ```java @Service public class MessageService { private static final String ROUTING_KEY = "message.test"; @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String message) { rabbitTemplate.convertAndSend(TOPIC_EXCHANGE_NAME, ROUTING_KEY, message); } } ``` 以上是使用Spring Boot与RabbitMQ实现Topic类型交换机的基本步骤。通过配置交换机、监听队列和发送消息,可以实现灵活的消息路由机制,满足不同的业务需求。 ### 回答3: RabbitMQ是一种开源的消息代理,用于在分布式系统之间传递消息。它支持多种类型的交换机,其之一就是Topic类型。 在使用Spring Boot集成RabbitMQ时,我们可以通过配置文件或代码的方式来定义Topic类型的交换机。 首先,在配置文件我们需要定义RabbitMQ的连接信息,如主机名、端口号、用户名和密码等。同时,我们也需要配置交换机的相关信息,包括交换机名称、类型和持久化等。 例如,我们可以在application.properties文件添加以下配置: spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.template.exchange=myExchange spring.rabbitmq.template.routing-key=myTopic 上述配置,我们定义了RabbitMQ的连接信息和交换机的相关信息。其,exchange为我们自定义的交换机名称,routing-key为我们自定义的Topic名称。 接下来,在代码我们可以通过使用注解方式,将RabbitTemplate对象注入到我们的业务逻辑。然后,通过调用RabbitTemplate对象的convertAndSend方法来发送消息。 例如,我们可以通过以下方式来发送消息: @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String message) { rabbitTemplate.convertAndSend("myExchange", "myTopic", message); } 上述代码,我们通过调用rabbitTemplate对象的convertAndSend方法来发送消息。其,第一个参数为交换机名称,第二个参数为Topic名称,第三个参数为要发送的消息内容。 总结一下,通过配置文件和代码的方式,我们可以在Spring Boot使用RabbitMQ的Topic类型交换机。只需要定义好RabbitMQ的连接信息和交换机的相关信息,然后调用RabbitTemplate对象的convertAndSend方法来发送消息即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昨晚的早点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值