Spring Boot:集成Mqtt实现消息的发送和接收

使用<>文章中创建的Mqtt Docker容器结合SpringBoot模拟实现消息的发送和接收。在物联网实际应用场景中,Mqtt模块可集成在任何软硬件设备,如Pc电脑、手持终端、车载模块以及一些定制的电路板中,即有通信功能的“物”中。下面主要记录SpringBoot项目中集成Mqtt的步骤。

Springboot版本:2.2.6.RELEASE

1 添加依赖包

build.gradle文件中添加如下依赖:

implementation "org.springframework.integration:spring-integration-mqtt"

2 添加properties配置

开发使用dev环境,在application-dev.properties文件中添加如下配置:

#自定义应用开关
mqtt.enabled=true
#mqtt服务器地址 使用本机中docker容器提供的mqtt服务
mqtt.host=tcp://localhost:1883
#mqtt服务用户名 当前未配置
mqtt.username=
#mqtt服务用户密码 当前未配置
mqtt.pwd=

3 添加配置类

为了使结构更清晰,将配置分为3部分,分别放在3个类中。

  • MqttBaseConfig:配置Mqtt公用部分,主要是提供Mqtt客户端Bean
  • MqttInConfig:配置入站消息通道及相关处理Bean
  • MqttOutConfig:配置出站消息通道及相关处理Bean

3.1 MqttBaseConfig

类完整代码如下:


/**
 * mqtt消息处理配置
 */
@Configuration
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
public class MqttBaseConfig {
    @Value("${mqtt.host}")
    private String mqttHost;

    @Value("${mqtt.username:}")
    private String mqttUserName;

    @Value("${mqtt.pwd:}")
    private String mqttPwd;

    /**
     * 构造一个默认的mqtt客户端操作bean
     *
     * @return
     */
    @Bean
    public MqttPahoClientFactory factory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        //如果配置了用户密码这里需要对应设置
        //options.setUserName();
        //options.setPassword();
        options.setServerURIs(new String[]{mqttHost});
        factory.setConnectionOptions(options);
        return factory;
    }

}

3.2 MqttInConfig

类完整代码如下:


/**
 * mqtt入站消息处理配置
 */
@Configuration
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
public class MqttInConfig {

    private final MqttMessageReceiver mqttMessageReceiver;

    public MqttInConfig(MqttMessageReceiver mqttMessageReceiver) {
        this.mqttMessageReceiver = mqttMessageReceiver;
    }

    /**
     * mqtt消息入站通道,订阅消息后消息进入的通道。
     * 可创建多个入站通道,对应多个不同的消息生产者。
     *
     * @return
     */
    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }

    /**
     * 对于当前应用来讲,接收的mqtt消息的生产者。将生产者绑定到mqtt入站通道,即通过入站通道进入的消息为生产者生产的消息。
     * 可创建多个消息生产者,对应多个不同的消息入站通道,同时生产者监听不同的topic
     *
     * @return
     */
    @Bean
    public MessageProducer channelInbound(MessageChannel mqttInputChannel, MqttPahoClientFactory factory) {
        String clientId = "h-backend-mqtt-in-" + System.currentTimeMillis();
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientId, factory, "/crane/mqtt/demo/#");
        adapter.setCompletionTimeout(5000);
        adapter.setConverter(new DefaultPahoMessageConverter());
        adapter.setQos(2);
        adapter.setOutputChannel(mqttInputChannel);
        return adapter;
    }

    /**
     * mqtt入站消息处理工具,对于指定消息入站通道接收到生产者生产的消息后处理消息的工具。
     *
     * @return
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttInputChannel")
    public MessageHandler mqttMessageHandler() {
        return this.mqttMessageReceiver;
    }

}

3.3 MqttOutConfig

类完整代码如下:


/**
 * mqtt出站消息处理配置
 */
@Configuration
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
public class MqttOutConfig {

    /**
     * mqtt消息出站通道,用于发送出站消息
     *
     * @return
     */
    @Bean
    public MessageChannel mqttOutputChannel() {
        return new DirectChannel();
    }

    /**
     * mqtt消息出站通道默认配置,用于向外发出mqtt消息,当前案例中发到了同一个mqtt服务器
     *
     * @return
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttOutputChannel")
    public MessageHandler mqttOutbound(MqttPahoClientFactory factory) {
        String clientId = "h-backend-mqtt-out-" + System.currentTimeMillis();
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientId, factory);
        messageHandler.setAsync(true);
        messageHandler.setDefaultQos(2);
        messageHandler.setDefaultTopic("/crane/mqtt/demo/foo");
        return messageHandler;
    }
}

4 入站消息处理

当前应用中订阅的topic消息入站时的处理类,这里只在控制台打印消息内容,具体根据业务场景而定,完整代码如下:


/**
 * mqtt订阅消息处理
 */
@Component
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
public class MqttMessageReceiver implements MessageHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void handleMessage(Message<?> message) throws MessagingException {
        String topic = String.valueOf(message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
        String payload = String.valueOf(message.getPayload());
        logger.info("接收到 mqtt消息,主题:{} 消息:{}", topic, payload);
    }
}

5 出站消息发送接口

用于发送Mqtt消息的接口,包含3个重载方法:

  • 第一个:向默认topic发送消息,当前应用设置的默认topic为/crane/mqtt/demo/foo
  • 第二个:指定topic发送消息
  • 第三个:指定topic和QOS发送消息

完整代码如下:


/**
 * mqtt消息发送工具,指定通过某个通道发出消息
 */
@Component
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
@MessagingGateway(defaultRequestChannel = "mqttOutputChannel")
public interface MqttMessageSender {
    /**
     * 发送信息
     *
     * @param data 发送的消息
     */
    void sendToMqtt(String data);

    /**
     * 指定主题发送消息
     *
     * @param topic   主题
     * @param payload 消息主体
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);

    /**
     * 指定主题、qos发送消息
     *
     * @param topic   主题
     * @param qos     对消息处理的几种机制。
     *                0 表示的是订阅者没收到消息不会再次发送,消息会丢失。
     *                1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。
     *                2 多了一次去重的动作,确保订阅者收到的消息有一次。
     * @param payload 消息主体
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);

}

6 消息测试

6.1 消息测试控制器

用于接收http请求,模拟发送消息。


@RestController
@ConditionalOnProperty(value = "mqtt.enabled", havingValue = "true")
@RequestMapping("/public/mqtt")
public class MqttController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private final MqttMessageSender mqttMessageSender;

    public MqttController(MqttMessageSender mqttMessageSender) {
        this.mqttMessageSender = mqttMessageSender;
    }

    /**
     * 发送mqtt消息
     *
     * @param topic 消息主题
     * @param data  消息内容
     * @return
     */
    @PostMapping("/send")
    public HResponse send(String topic, String data) {
        this.logger.info("开始发送mqtt消息,主题:{},消息:{}", topic, data);
        if (StringUtils.isNotBlank(topic)) {
            this.mqttMessageSender.sendToMqtt(topic, data);
        } else {
            this.mqttMessageSender.sendToMqtt(data);
        }
        this.logger.info("发送mqtt消息完成,主题:{},消息:{}", topic, data);
        return HResponse.success();
    }
}

6.2 模拟MQTT消息

6.2.1 入站消息测试

# 在Mqtt消息主题中代表多层通配符,位于主题字符串末尾,可匹配任意多的主题层级内容

在上面步骤中,Springboot应用订阅了主题:/crane/mqtt/demo/#,这里我们用MQTT Explorer工具向/crane/mqtt/demo/foo主题发送消息,则会被Springboot应用接收,如下图:

Spring Boot:集成Mqtt实现消息的发送和接收_第1张图片
Spring Boot:集成Mqtt实现消息的发送和接收_第2张图片
向另外一个topic /crane/mqtt/demo/foo2发送消息,则也会被Springboot应用接收,如下图:
Spring Boot:集成Mqtt实现消息的发送和接收_第3张图片
Spring Boot:集成Mqtt实现消息的发送和接收_第4张图片

6.2.2 出站消息测试

上面的MqttMessageSender接口定义了发送消息的方法,并在MqttController控制器中进行了使用。可通过Postman向控制器发送请求模拟发送Mqtt消息,消息发送成功后MQTT Explorer能接收到消息详细内容。如果使用相同的主题重复发送不同消息,可在MQTT Explorer中查看历史记录,如下图:

Spring Boot:集成Mqtt实现消息的发送和接收_第5张图片

Spring Boot:集成Mqtt实现消息的发送和接收_第6张图片

Spring Boot:集成Mqtt实现消息的发送和接收_第7张图片

7 总结

通过上面的模拟可发现,只要有一个MQTT消息服务处理中心(上文中Mqtt Docker容器提供了此功能),一个或多个可实现Mqtt通信的设备(上文中的Springboot应用和MQTT Explorer),那么设备间即可方便的进行通信,实现“物物相连”。

你可能感兴趣的:(SpringBoot)