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();
    }
}
相关推荐
云烟成雨TD18 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Java成神之路-18 小时前
SpringMVC 响应实战指南:页面、文本、JSON 返回全流程(Spring系列13)
java·spring·json
砍材农夫19 小时前
spring-ai 第六模型介绍-聊天模型
java·人工智能·spring
云烟成雨TD19 小时前
Spring AI Alibaba 1.x 系列【5】ReactAgent 构建器深度源码解析
java·人工智能·spring
Flittly21 小时前
【SpringAIAlibaba新手村系列】(15)MCP Client 调用本地服务
java·笔记·spring·ai·springboot
Flittly21 小时前
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
java·spring boot·笔记·spring·ai
mfxcyh21 小时前
基于xml、注解、JavaConfig实现spring的ioc
xml·java·spring
Flittly21 小时前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
xdscode21 小时前
Spring 依赖注入方式全景解析
java·后端·spring