springboot整合rabbitMQ及手动ack确认

springboot版2.2.2

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ex</groupId>
    <artifactId>tmap2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

<!--    <dependency>-->
<!--        <groupId>io.agora.rtm</groupId>-->
<!--        <artifactId>agora-rtm-sdk</artifactId>-->
<!--        <version>1.3</version>-->
<!--    </dependency>-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>
</project>

application.yml
 

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: 123456
    virtual-host: /ems
    template:
      retry: #重试,消息发送失败会重试
        enabled: true # 开启重试
        initial-interval: 10000ms  #第一次十秒重试
        max-interval: 80000ms  #最后一次是八秒重试
        multiplier: 2  #重试翻倍率
    publisher-confirms: true #发送者开启 confirm 确认机制
    publisher-returns: true  # 发送者开启 return 确认机制




RabbitCallbackConfig.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class RabbitCallbackConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
    @Bean
    RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //rabbitTemplate发送消息json转换配置
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
        return rabbitTemplate;
    }
    /**
     * 配置接收消息json转换为对象
     * @return
     */
    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    // 下边这样写也可以
    // @Autowired
    // private RabbitTemplate rabbitTemplate;
    // @PostConstruct
    // public void init() {
    //     rabbitTemplate.setMandatory(true);
    //     rabbitTemplate.setReturnCallback(this);
    //     rabbitTemplate.setConfirmCallback(this);
    // }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (!ack) {
            log.error("confirm==>发送到broker.exchange失败\r\n" +
                            "correlationData={}\r\n" + "ack={}\r\n" + "cause={}",
                    correlationData, ack, cause);
        } else {
            log.info("confirm==>发送到broker.exchange成功\r\n" +
                            "correlationData={}\r\n" + "ack={}\r\n" + "cause={}",
                    correlationData, ack, cause);
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText,
                                String exchange, String routingKey) {
        log.info("returnedMessage==> \r\n" + "message={}\r\n" + "replyCode={}\r\n" +
                        "replyText={}\r\n" + "exchange={}\r\n" + "routingKey={}",
                message, replyCode, replyText, exchange, routingKey);
    }
}

WorkCustomer.java

import com.rabbitmq.client.Channel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;
@Slf4j
@Component
public class WorkCustomer {


    @RabbitListener(queuesToDeclare = @Queue(value = "hello",declare = "true"),ackMode ="MANUAL" )
    @SneakyThrows
    public void receive12(Student student, Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag){
        try {
            log.info("message: {}",student);
            // 处理实际业务
            // 制造异常
//             int wrongNumber = 1/0;
            // 无异常,确认消息消费成功
            channel.basicAck(deliveryTag, true);
        }catch (IOException | ArithmeticException exception) {
            log.error("处理消息发生异常", exception);
            // 有异常,将消息返回给Queue里,第三个参数requeue可以直接看出来,是否返回到Queue中
            channel.basicNack(deliveryTag, true, true);
        }

    }


    //-----------------工作模式---------------------------------------
    // 生产端没有指定交换机只有routingKey和Object。
    //消费方产生work队列,放在默认的交换机(AMQP default)上。
    //而默认的交换机有一个特点,只要你的routerKey的名字与这个
    //交换机的队列有相同的名字,他就会自动路由上。
    //生产端routingKey 叫work ,消费端生产work队列。
    //他们就路由上了
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive1(String message){
        System.out.println("work message1 = " + message);
    }


    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive2(String message){
        System.out.println("work message2 = " + message);
    }

    //-------------------------广播模式--------------------------------------------------
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, // 创建临时队列
            exchange = @Exchange(name = "logs", type = "fanout")
    ))
    public void fanout1(String message) {
        System.out.println("message1 = " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, // 创建临时队列
            exchange = @Exchange(name = "logs", type = "fanout")
    ))
    public void fanout2(String message) {
        System.out.println("message2 = " + message);
    }
//-------------------------路由模式--------------------------------------------------

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "infoQue",declare = "true"), // 创建info队列,declare默认队列持久化
                    key = {"info"}, // 路由key
                    exchange = @Exchange(type = "direct", name = "directs")
            )})
    public void receive1221(String message) {
        System.out.println("message1 = " + message);
    }



@RabbitListener(bindings = {
        @QueueBinding(
                value = @Queue(value = "info||error"), // 创建临时队列
                key = {"info", "error"}, // 路由key
                exchange = @Exchange(type = "direct", name = "directs")
        )})
public void receive11(String message) {
    System.out.println("message1 = " + message);
}

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"error"},
                    exchange = @Exchange(type = "direct", name = "directs")
            )})
    public void receive22(String message) {
        System.out.println("message2 = " + message);
    }
//-------------------------Topic 订阅模型(动态路由模型)--------------------------------------------------
@RabbitListener(bindings = {
        @QueueBinding(
                value = @Queue,
                key = {"user.*"},
                exchange = @Exchange(type = "topic",name = "topics")
        )
})
public void receive111(String message){
    System.out.println("message1 = " + message);
}

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"user.#"},
                    exchange = @Exchange(type = "topic",name = "topics")
            )
    })
    public void receive222(String message){
        System.out.println("message2 = " + message);
    }



    /**
     * 监听Queue的时候,直接获取消息体.
     * 在注解上开启手动确认, 必须是ackMode的大写.
     * 在进行消息确认的时候,要带上Rabbit MQ Server发送过来头上的tag,可以通过@Header注解获取delivery tag,
     * @param firstTopicQueueMessage 消息体
     * @param channel Broker和Consumer建立的channel
     * @param tag 消息头中的tag
     */
//    @RabbitListener(queues = "${queue.topic.first}", ackMode = "MANUAL")
//    @RabbitHandler
//    @SneakyThrows
//    public void receiveFirstTopicQueueMessage(String firstTopicQueueMessage, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
//        log.info("This is firstTopicQueue received message: {}", firstTopicQueueMessage);
//        try {
//            // 处理实际业务
//            TimeUnit.SECONDS.sleep(5);
//            // 制造异常
//            // int wrongNumber = 1/0;
//            // 无异常,确认消息消费成功
//            channel.basicAck(tag, true);
//        }catch (IOException | ArithmeticException exception) {
//            log.error("处理消息发生异常", exception);
//            // 有异常,将消息返回给Queue里,第三个参数requeue可以直接看出来,是否返回到Queue中
//            channel.basicNack(tag, true, true);
//        }
//    }

}

TTest.java


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //5.2 第一种hello world模型使用
    @Test
    public void test1() {
        Student student = new Student();
        student.setName("小明");
        student.setAge("18");
        student.setAddress("杭州");
        rabbitTemplate.convertAndSend("hello", student);
        // 生产端没有指定交换机只有routingKey和Object。
        //消费方产生hello队列,放在默认的交换机(AMQP default)上。
        //而默认的交换机有一个特点,只要你的routerKey的名字与这个
        //交换机的队列有相同的名字,他就会自动路由上。
        //生产端routingKey 叫hello ,消费端生产hello队列。
//他们就路由上了
    }

    //5.3 第二种work模型使用
    @Test
    public void test2() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("work", "hello work!");
            // 生产端没有指定交换机只有routingKey和Object。
            //消费方产生work队列,放在默认的交换机(AMQP default)上。
            //而默认的交换机有一个特点,只要你的routerKey的名字与这个
            //交换机的队列有相同的名字,他就会自动路由上。
            //生产端routingKey 叫work ,消费端生产work队列。
            //他们就路由上了
        }
    }
//    5.4 Fanout 广播模型

    @Test
    public void test3() {
        rabbitTemplate.convertAndSend("logs", "", "这是日志广播"); // 参数1为交换机,参数2为路由key,“”表示为任意路由,参数3为消息内容
    }

    //5.5 Route 路由模型
    @Test
    public void contextLoads() {
//        rabbitTemplate.convertAndSend("directs", "error", "error 的日志信息");
        rabbitTemplate.convertAndSend("directs", "info", "info 的日志信息");
    }

    //5.6 Topic 订阅模型(动态路由模型)
    @Test
    public void contextLoads1() {
        rabbitTemplate.convertAndSend("topics", "user.save.findAll", "user.save.findAll 的消息");
    }
}

 

<think>好的,我现在需要解决用户在Spring Boot整合RabbitMQ时遇到的空指针异常问题。首先,我得回忆一下可能的原因,并逐一分析。根据用户提供的引用内容,特别是引用[4],涉及到消息确认机制和手动ACK的处理,这可能是一个潜在的问题点。 首先,用户提到的空指针异常可能发生在消息监听器或者处理消息的过程中。常见的可能原因包括: 1. **依赖未正确注入**:比如RabbitTemplate或MessageConverter没有被Spring容器管理,导致注入失败。这时候在使用这些Bean的时候就会抛出空指针异常。需要检查相关的配置类是否正确,是否使用了@Autowired或者构造函数注入,并且确保没有遗漏@Component或@Service等注解。 2. **手动ACK确认时未正确获取Channel或DeliveryTag**:引用[4]中提到,手动ACK需要使用deliveryTag,如果在处理消息的时候,尝试确认或拒绝消息时,相关的参数如deliveryTag未正确获取,或者channel对象为空,就会导致空指针。需要检查消息监听器的方法参数是否正确,是否包含了Channel参数,以及是否正确调用了basicAck或basicNack方法。 3. **消息监听器配置错误**:如果监听器容器配置不当,比如没有正确设置AcknowledgeMode,可能会导致消息处理时某些对象未初始化。引用[2]提到SpringAMQP的ACK处理方式需要配置,如果配置为手动模式但代码中没有正确处理,也可能引发问题。 4. **消息处理过程中对象未正确初始化**:比如在处理消息时,调用了其他Service类的方法,但该Service类没有被注入,导致空指针。这时候需要检查业务逻辑中的依赖注入情况。 接下来,我需要根据这些可能的原因,给出具体的解决方案步骤。例如,对于依赖注入问题,可以建议用户检查相关Bean的配置;对于手动ACK的问题,提醒用户检查监听器方法的参数和确认逻辑是否正确。同时,结合用户提供的引用内容,特别是引用[4]中的代码示例,强调正确使用basicNack方法时的参数设置,比如deliveryTag的获取和是否批量处理等。 此外,还需要考虑其他可能性,比如RabbitMQ的连接配置错误导致连接工厂未能创建连接,进而导致相关对象为空。这时候检查application.properties或.yml文件中的配置是否正确,如主机名、端口、用户名、密码等。 最后,总结常见的解决步骤,并建议用户逐步排查,从依赖注入、配置检查、消息处理逻辑等方面入手,确保每个环节都正确无误。同时,可以建议用户查看日志,找到空指针发生的具体位置,以便更有针对性地解决问题。</think>在Spring Boot整合RabbitMQ时出现空指针异常(NPE),通常由以下原因导致。我们按优先级分步骤分析并提供解决方案: ### 一、消息监听器参数缺失(高频原因) **现象**:在`@RabbitListener`注解的方法中调用`channel.basicAck()`时出现NPE **原因**:未正确声明`Channel`参数或未获取`deliveryTag` **解决方案**: ```java @RabbitListener(queues = "yourQueue") public void processMessage(String message, Channel channel, // 必须添加该参数 @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) { try { // 处理业务逻辑 channel.basicAck(deliveryTag, false); // 正确确认 } catch (IOException e) { // 异常处理 } } ``` **验证要点**:检查方法参数是否包含`Channel`和`deliveryTag`[^4] ### 二、ACK模式配置冲突 **现象**:配置为自动ACK但尝试手动确认 **配置检查**: ```properties spring.rabbitmq.listener.simple.acknowledge-mode=manual # 必须为manual ``` **错误示例**: ```java // 若配置为auto,channel.basicAck()会引发异常 channel.basicAck(deliveryTag, false); // 在auto模式下channel可能为null ``` **处理建议**:保持配置与代码逻辑一致性[^2] ### 三、RabbitTemplate未正确注入 **现象**:发送消息时出现NPE **正确注入方式**: ```java @Service public class MessageService { @Autowired // 必须通过Spring注入 private RabbitTemplate rabbitTemplate; public void sendMessage(String exchange, String routingKey, Object message) { rabbitTemplate.convertAndSend(exchange, routingKey, message); } } ``` **常见错误**:手动new实例`new RabbitTemplate()` ### 四、消息转换器缺失 **现象**:处理复杂对象时出现NPE **配置方案**: ```java @Configuration public class RabbitConfig { @Bean public MessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } } ``` **调用验证**:确保消息发送/接收时使用相同转换器 ### 五、连接工厂配置错误 **检查配置**: ```properties spring.rabbitmq.host=your_host spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/ ``` **特殊场景**:使用SSL连接时需要额外配置`ssl.enabled=true` ### 六、异步初始化问题 **现象**:@PostConstruct方法中调用RabbitTemplate **解决方案**: ```java @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init() { // 错误用法:立即调用rabbitTemplate.convertAndSend() // 正确方案:延迟初始化或使用事件监听器 ApplicationStartedEvent event = ...; this.onApplicationEvent(event); } ``` ### 排查流程图 ``` NPE发生位置 → 检查对象注入状态 → 验证方法参数 → 检查ACK配置 → 查看连接日志 ↓ ↓ ↓ ↓ ↓ 消息发送端 @Autowired注解 Channel参数 acknowledge-mode 网络连通性 ↓ ↓ ↓ ↓ ↓ RabbitTemplate 组件扫描路径 deliveryTag获取 模式一致性 防火墙设置 ``` ### 典型日志分析 ```log Caused by: java.lang.NullPointerException: null at com.example.service.MessageListener.onMessage(MessageListener.java:25) // 查看第25行代码上下文,通常是channel或rabbitTemplate调用 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值