基于 SpringBoot + Nats 完成简单的消息推送和订阅

本文基于上一篇安装好的 Nats 进行,如未安装,请参见

官方文档:https://docs.nats.io/developing-with-nats/developer

一、Nats 相关配置

1、引入相关依赖

<dependency>
            <groupId>io.nats</groupId>
            <artifactId>jnats</artifactId>
            <version>2.6.5</version>
        </dependency>

2、配置 application.yml

server:
  port: 7005

spring:
  application:
    name: nats

nats:
  # nats 地址
  nats-urls: nats://192.168.202.128:4222
  # 最大重连次数
  max-reconnect: 60
  # 重连等待时间
  reconnect-wait: 2
  # 连接超时时间
  connection-timeout: 2
  # 用户名
  user-name: nats
  # 密码
  password: nats

3、读取 Nats 的相关配置

package com.wxw.notes.nats.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 获取配置文件中 nats 相关配置
 */
@Component
@ConfigurationProperties(prefix = "nats")
@Data
public class NatsProperties {

    private String natsUrls;

    private String token = null;

    private int maxReconnect = 60;

    private int reconnectWait = 2;

    private int connectionTimeout = 2;

    private String userName;

    private String password;
    
}

4、Nats 连接监听及消息订阅

package com.wxw.notes.nats.listener;

import io.nats.client.Connection;
import io.nats.client.ConnectionListener;
import io.nats.client.Dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;

/**
 * NatsListener 连接监听
 */
public class NatsListener implements ConnectionListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    /**
     *  监听 nats 连接状态 ,并进行消息订阅
     * @param conn
     * @param type
     */
    @Override
    public void connectionEvent(Connection conn, Events type) {
        LOGGER.info(String.format("nats connection status: %s", conn.getStatus()));
        if(Connection.Status.CONNECTED.equals(conn.getStatus())){
            LOGGER.info("subscribe topic");
            // 连接成功后进行消息订阅
            subscribe(conn);
        }
    }

    /**
     * 异步订阅
     * @param conn  连接
     */
    public void subscribe(Connection conn){

        Dispatcher dispatcher = conn.createDispatcher(msg -> {
            LOGGER.info("Received message {}, on subject {}", new String(msg.getData(), StandardCharsets.UTF_8), msg.getSubject());
        });

        //订阅 subject 为 NATS_TEST 的消息
        dispatcher.subscribe("NATS_TEST");

        // 不关闭connection,方便测试多次接收消息
    }
}

5、将 Nats 注入 Spring 容器

package com.wxw.notes.nats.config;

import com.wxw.notes.nats.listener.NatsListener;
import io.nats.client.Connection;
import io.nats.client.Nats;
import io.nats.client.Options;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.time.Duration;


/**
 * nats 连接配置类
 */
@Configuration
public class NatsConfiguration {

    /**
     * 注入 nats 连接
     * @param properties nats 配置文件类
     * @return  nats 连接
     * @throws IOException
     * @throws InterruptedException
     */
    @Bean(name = "natsConnection")
    public Connection natsConnection(NatsProperties properties) throws IOException, InterruptedException {
        String[] str = properties.getNatsUrls().split(",");
        Options.Builder builder = new Options.Builder()
                // 配置 nats 服务器地址
                .servers(str)
                // 配置用户名和密码
                .userInfo(properties.getUserName().toCharArray(),properties.getPassword().toCharArray())
                // nats 监听
                .connectionListener(new NatsListener())
                // 最大重连次数
                .maxReconnects(properties.getMaxReconnect())
                // 重连等待时间
                .reconnectWait(Duration.ofSeconds(properties.getReconnectWait()))
                // 连接超时时间
                .connectionTimeout(Duration.ofSeconds(properties.getConnectionTimeout()));
        if (properties.getToken() != null) {
            builder.token(properties.getToken().toCharArray());
        }
        //连接 nats
        return Nats.connect(builder.build());

    }
}

二、Nats 消息推送

编写一个简单的 controller 进行消息推送

package com.wxw.notes.nats.controller;

import io.nats.client.Connection;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;

/**
 * @author wuxiongwei
 * @date 2021/6/16 16:32
 * @Description 测试 nats 消息推送
 */
@RestController
public class NatsController {

    @Resource(name = "natsConnection")
    private Connection natsConnection;



    @RequestMapping("/publish")
    public void publish(){
        String msg = "hello nats";
        // subject 为 NATS_TEST,消息内容为 msg
        natsConnection.publish("NATS_TEST", msg.getBytes(StandardCharsets.UTF_8));
    }
}

这里我们拿到之前注入到 spring 容器的 natsConnection 来进行消息推送

然后通过 publish 方法进行推送,
第一个参数是消息的 subject ,消息的主题,类似于 kafka 的 topic 概念;
第二个参数是消息的内容,格式为 byte[] ,我们设置编码格式为 UTF-8;

三、Nats 消息订阅

其实这个订阅在我们第一步的第4小点就已经做了,也就是这一段

/**
     * 异步订阅
     * @param conn  连接
     */
    public void subscribe(Connection conn){

        Dispatcher dispatcher = conn.createDispatcher(msg -> {
            LOGGER.info("Received message {}, on subject {}", new String(msg.getData(), StandardCharsets.UTF_8), msg.getSubject());
        });

        //订阅 subject 为 NATS_TEST 的消息
        dispatcher.subscribe("NATS_TEST");

        // 不关闭connection,方便测试多次接收消息
    }

这里我们采用异步的订阅方式来进行订阅,还有其他几种订阅方式,如同步订阅、接收N条之后取消订阅等,可以参阅官方文档自行尝试

四、 Nats 消息推送订阅测试

消息推送和订阅我们已经全部编写好了,接下来我们就可以开始看看成果了
运行我们的项目

springboot 消息总线 spring boot消息推送_java


可以看到我们 Nats 监听类的日志打印,已经连上了我们的 Nats 服务器,并且开始订阅了,接下来我们就需要发送一条消息,看是否能收到

浏览器访问第二步的消息推送接口:localhost:7005/publish

查看控制台,完美,消息收到了

springboot 消息总线 spring boot消息推送_spring_02