Spring WebFlux整合reactor-rabbitmq

1.概览

Spring WebFlux整合reactor-rabbitmq。有发送和接受。并且我是走延时插件x-delay的延时消息,所以在这里记录下。

jdk17、springboot2.6.7

1. 让运维先配置队列

Routing Key: xxxname

Queue: xxxname

Exchange: xxxname

持久化,不自动删除

2. 配置类

pom依赖

java 复制代码
		<!-- rabbitmq -->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor.rabbitmq</groupId>
            <artifactId>reactor-rabbitmq</artifactId>
        </dependency>

先说整体的配置类

java 复制代码
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import javax.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import reactor.rabbitmq.RabbitFlux;
import reactor.rabbitmq.Receiver;
import reactor.rabbitmq.ReceiverOptions;
import reactor.rabbitmq.Sender;
import reactor.rabbitmq.SenderOptions;

/**
 * rabbitMq 配置
 *
 */

@Slf4j
@Configuration
@RequiredArgsConstructor
public class RabbitMqConfig {

    Mono<Connection> connectionMono;

    @Bean
    public Mono<Connection> connectionMono(@Value("${rabbit.host}") String host,
            @Value("${rabbit.port}") int port,
            @Value("${rabbit.username}") String username,
            @Value("${rabbit.password}") String password) {
        var connectionFactory = new ConnectionFactory();
        connectionFactory.useNio();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost("/");
        connectionMono = Mono.fromCallable(() -> connectionFactory.newConnection("reactor-rabbit")).cache();

        return connectionMono;
    }

    @Bean
    public Receiver rabbitReceiver(Mono<Connection> connectionMono) {
        return RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connectionMono));
    }

    @Bean
    public Sender rabbitSender(Mono<Connection> connectionMono) {
        return RabbitFlux.createSender(new SenderOptions().connectionMono(connectionMono));
    }

    @PreDestroy
    public void close() throws Exception {
        log.info("[RabbitMqConfig] close connection");
        Connection connection = connectionMono.block();
        if (connection != null) {
            connection.close();
        }
    }

}

3. 监听者

java 复制代码
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.rabbitmq.Receiver;

@Component
@Slf4j
@RequiredArgsConstructor
public class RabbitMqListener {

    private static final String QUEUE = "delayed.queue";
    private final Receiver rabbitReceiver;
    private static final String ACK = "ACK";

    /**
     * 初始化监听
     */
    public @PostConstruct void initQueue() {
        rabbitReceiver.consumeManualAck(QUEUE)
                .flatMap(delivery -> Mono.defer(() -> {
                                    String message = new String(delivery.getBody());
                                    // todo something
                                    return Mono.fromRunnable(delivery::ack)
                                            .thenReturn(ACK);
                                })
                                // 这里两行代码解决问题 :因为错误导致整个流结束
                                .onErrorResume(throwable -> {
                                    log.warn("[rabbitReceiver] RabbitMq 异常 直接确认 消息:{}", new String(delivery.getBody()), throwable);
                                    return Mono.fromRunnable(delivery::ack)
                                            .thenReturn(ACK);
                                })
                )
                .subscribe();
    }

}

4. 发送者

发送的是延时消息,借助gitlab的issue里大佬提供的代码

java 复制代码
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import reactor.rabbitmq.ExchangeSpecification;

/**
 * @date 2025/12/19 15:57
 */

public class DelayedExchangeSpecification  extends ExchangeSpecification {
    private static final String DELAYED_TYPE = "x-delayed-message";
    private static final String DELAYED_ARGUMENT_KEY = "x-delayed-type";

    public DelayedExchangeSpecification() {
        type(super.getType());
    }

    public static ExchangeSpecification exchange() {
        return new DelayedExchangeSpecification();
    }

    public static @NotNull ExchangeSpecification exchange(@NotNull String name) {
        return new DelayedExchangeSpecification().name(name);
    }

    @Override
    public @NotNull DelayedExchangeSpecification type(@NotNull String type) {
        Map<String, Object> args = getArguments();
        if (args == null) {
            args = new HashMap<>();
        }
        args.put(DELAYED_ARGUMENT_KEY, type);
        arguments(args);
        return this;
    }

    @Override
    public @NotNull String getType() {
        return DELAYED_TYPE;
    }
}

延时消息类

java 复制代码
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.BasicProperties;
import java.util.HashMap;
import java.util.Map;
import reactor.rabbitmq.OutboundMessage;
import reactor.util.annotation.Nullable;

/**
 * @date 2025/12/19 15:58
 */

public class DelayedOutboundMessage extends OutboundMessage {

    public static final String DELAYED_HEADER_KEY = "x-delay";

    /**
     * @param delayMillis Delay time in milliseconds 注意时间单位,可以用Duration改造这里统一接受,你来负责统一转换
     */
    public DelayedOutboundMessage(String exchange, String routingKey, long delayMillis, byte[] body) {
        super(exchange, routingKey, delayedProperties(null, delayMillis), body);
    }

    /**
     * @param delayMillis Delay time in milliseconds 注意时间单位,可以用Duration改造这里统一接受,你来负责统一转换
     */
    public DelayedOutboundMessage(String exchange, String routingKey, long delayMillis, BasicProperties properties, byte[] body) {
        super(exchange, routingKey, delayedProperties(properties, delayMillis), body);
    }

    private static BasicProperties delayedProperties(@Nullable AMQP.BasicProperties source, long delayMillis) {
        AMQP.BasicProperties.Builder builder;
        if (source != null) {
            builder = source.builder();
        } else {
            builder = new BasicProperties.Builder();
        }
        Map<String, Object> headers;
        if (source != null) {
            headers = source.getHeaders();
        } else {
            headers = new HashMap<>();
        }
        headers.put(DELAYED_HEADER_KEY, delayMillis);
        return builder.headers(headers).build();
    }

}

具体的发送方法

java 复制代码
import cn.hutool.json.JSONUtil;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.rabbitmq.BindingSpecification;
import reactor.rabbitmq.Sender;

/**
 * 发送消息工具
 *
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class RabbitMqSender {

    private static final String EXCHANGE = "delayed.exchange";
    private static final String ROUTING_KEY = "delayed.routing-key";
    private static final String QUEUE = "delayed.queue";
    private final Sender sender;

    public void sendDelayMessage(String method, String content, Duration duration) {
        log.debug("[RabbitMqSender] sendDelayMessage method: {} content: {} duration: {}", method, content, duration);
        sender.declare(DelayedExchangeSpecification.exchange(EXCHANGE)
                        .durable(true)// 持久化
                        .autoDelete(false)// 不自动删除
                        .type("x-delayed-message") // 类型
                )
                .then(sender.bind(BindingSpecification.binding(EXCHANGE, ROUTING_KEY, QUEUE)))
                .then(sender.send(Flux.just(new DelayedOutboundMessage(
                        EXCHANGE,
                        ROUTING_KEY,
                        duration.toMillis(),
                        content.getBytes(StandardCharsets.UTF_8)
                ))))
                .doOnError(throwable -> log.warn("[RabbitMqSender] sendDelayMessage method: {} content: {} duration: {}", method, content, duration, throwable))
                .subscribe();
    }
}
相关推荐
空中海4 小时前
Spring Cloud 专家级面试题库
spring·spring cloud·面试
直奔標竿5 小时前
SpringAI + RAG + MCP + Agent 零基础全栈实战(完结篇)| 27课完整汇总,Java开发者AI转型必看
java·开发语言·人工智能·spring boot·后端·spring
云烟成雨TD5 小时前
Spring AI 1.x 系列【31】向量数据库:进阶使用指南
java·人工智能·spring
冷小鱼6 小时前
消息队列(MQ)技术全景科普:从选型到AI+未来
人工智能·kafka·rabbitmq·rocketmq·mq·pulsar
counting money7 小时前
Spring框架基础(依赖注入-全注解形式)
java·数据库·spring
counting money7 小时前
Spring框架基础(依赖注入-半注解形式)
java·后端·spring
_F_y9 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(2)
网络·rabbitmq
sing~~10 小时前
SpringCloud的了解和使用
后端·spring·spring cloud
随风,奔跑10 小时前
Spring Cloud Alibaba(六)-链路追踪SkyWalking
java·后端·spring·skywalking
云烟成雨TD11 小时前
Spring AI 1.x 系列【30】向量数据库:核心 API 和入门案例
java·人工智能·spring