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();
    }
}
相关推荐
Wang's Blog2 小时前
RabbitMQ: 基于Docker技术实施集群部署实战指南
分布式·docker·rabbitmq
gordon~92 小时前
RabbitMQ -消息可靠 的实战示例
分布式·消息队列·rabbitmq·消息可靠性
Wang's Blog2 小时前
RabbitMQ:高效消息处理与资源管理实践
分布式·rabbitmq
驱动探索者2 小时前
[缩略语大全]之[INTEL]篇
java·后端·spring·intel
Lisonseekpan2 小时前
为什么Spring 推荐使用构造器注入而非@Autowired字段注入?
java·后端·spring·log4j
北城以北888814 小时前
Spring定时任务与Spring MVC拦截器
spring boot·spring·mvc
WizLC14 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Mr.朱鹏15 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka