SpringBoot 整合MQTT 消息推送

本文详细介绍了如何在Spring项目中集成MQTT,包括在pom.xml中添加依赖,配置yml文件,创建消息实体和MQTT配置类,以及生产者和消费者的实现。还展示了MQTT网关接口和消息处理工具的编写,提供了测试示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一: pom文件添加依赖

        <!-- mqtt -->
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>

二: yml配置文件添加MQTT配置信息

###################### MQTT #################################
mqtt:
  # 服务器地址
  host: tcp://127.0.0.1:1883
  # ID唯一
  clientId: MQTT_WK
  # 主题 多个主题用逗号(,)分割 #表示这个主题下面所有,topic1,topic2,topic2/topic22/#(默认会取第一个主题)
  topics: topic1,topic2,topic2/topic22/#
  # 用户名
  username: admin
  # 密码
  password: 1q2w3e4r.
  # 连接超时
  timeout: 30
  # 心跳检测
  keepalive: 60
  # 对消息处理的几种机制。
  # 0 表示的是订阅者没收到消息不会再次发送,消息会丢失
  # 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息
  # 2 多了一次去重的动作,确保订阅者收到的消息有一次
  qos: 1
  # false为建立持久会话
  cleanSession: false
  # 断开后重新连接
  automaticReconnect: true

三: 新建一个接收MQTT消息的实体用于接收参数(@RequestBody), 也可以直接参数字段(@RequestParam)这一步就不需要了

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @Description 消息实体对象
 * @Author WangKun
 * @Date 2023/4/19 14:59
 * @Version
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MQTTMessage implements Serializable {
    /**
     * MQTT主题
     */
    private String topic;

    /**
     * qos
     */
    private Integer qos = 1;

    /**
     * MQTT内容
     */
    private String content;

}

 四: 新建一个MQTT配置类(这可以改造两个配置类,一个生产者一个消费者,yml配置文件也是一个生产者一个消费者)

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


/**
 * @Description MQTT的配置类
 * @Author WangKun
 * @Date 2023/4/19 10:38
 * @Version
 */
@Component
@Data
public class MQTTConfig {

    /**
     * host 服务器地址配置
     */
    @Value("${mqtt.host}")
    private String[] host;

    /**
     * clientId
     */
    @Value("${mqtt.clientId}")
    private String clientId;

    /**
     * 主题
     */
    @Value("${mqtt.topics}")
    private String[] topics;

    /**
     * 用户名
     */
    @Value("${mqtt.username}")
    private String username;

    /**
     * 密码
     */
    @Value("${mqtt.password}")
    private String password;

    /**
     * 连接超时时长
     */
    @Value("${mqtt.timeout}")
    private Integer timeout;

    /**
     * keep Alive时间(心跳检测)
     */
    @Value("${mqtt.keepalive}")
    private Integer keepalive;

    /**
     * 遗嘱消息 QoS
     */
    @Value("${mqtt.qos}")
    private Integer qos;

    /**
     * false为建立持久会话
     */
    @Value("${mqtt.cleanSession}")
    private Boolean cleanSession;

    /**
     * 断开后重新连接
     */
    @Value("${mqtt.automaticReconnect}")
    private Boolean automaticReconnect;
}

五: 新建生产者与消费者

import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

import javax.annotation.Resource;

/**
 * @Description MQTT生产端配置
 * @Author WangKun
 * @Date 2023/4/19 14:58
 * @Version
 */
@Configuration
public class MQTTProduceConfig {


    @Resource
    private MQTTConfig mqttConfig;

    // 客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息
    private static final byte[] WILL_DATA;

    static {
        WILL_DATA = "offline".getBytes();
    }

    /**
     * @param
     * @Description 出站直连通道
     * @Throws
     * @Return org.springframework.messaging.MessageChannel
     * @Date 2023-04-20 15:15:42
     * @Author WangKun
     */
    @Bean("mqttOut")
    public MessageChannel mqttOutBoundChannel() {
        return new DirectChannel();
    }


    /**
     * @param
     * @Description 创建MqttPahoClientFactory 设置MQTT的broker的连接属性
     * @Throws
     * @Return org.springframework.integration.mqtt.core.MqttPahoClientFactory
     * @Date 2023-04-20 15:15:52
     * @Author WangKun
     */
    @Bean
    public MqttPahoClientFactory outClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        String[] hosts = mqttConfig.getHost();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(hosts);
        options.setUserName(mqttConfig.getUsername());
        options.setPassword(mqttConfig.getPassword().toCharArray());
        options.setConnectionTimeout(mqttConfig.getTimeout());
        options.setKeepAliveInterval(mqttConfig.getKeepalive());
        options.setCleanSession(mqttConfig.getCleanSession());
        options.setAutomaticReconnect(mqttConfig.getAutomaticReconnect());
        options.setWill("willTopic", WILL_DATA, 2, false);
        factory.setConnectionOptions(options);
        return factory;
    }

    /**
     * @param
     * @Description 出站
     * @Throws
     * @Return org.springframework.messaging.MessageHandler
     * @Date 2023-04-20 15:16:05
     * @Author WangKun
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttOut")
    public MessageHandler mqttOutbound() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(mqttConfig.getClientId() + "_producer", outClientFactory());
        //如果设置成true,即异步,发送消息时将不会阻塞。
        messageHandler.setAsync(true);
        //设置默认QoS
        messageHandler.setDefaultQos(mqttConfig.getQos());
        // 设置默认主题,取第一个
        messageHandler.setDefaultTopic(mqttConfig.getTopics()[0]);
        DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();
        //发送默认按字节类型发送消息
//        defaultPahoMessageConverter.setPayloadAsBytes(true);
        messageHandler.setConverter(defaultPahoMessageConverter);
        return messageHandler;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import javax.annotation.Resource;

/**
 * @Description MQTT 消费端的配置
 * @Author WangKun
 * @Date 2023/4/19 14:56
 * @Version
 */
@Configuration
@Slf4j
public class MQTTConsumeConfig {

    @Resource
    private MQTTConfig mqttConfig;

    @Resource
    private MQTTMessageHandler mqttMessageHandler;

    // 客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息
    private static final byte[] WILL_DATA;

    static {
        WILL_DATA = "offline".getBytes();
    }

    /**
     * @param
     * @Description 入站直连通道
     * @Throws
     * @Return org.springframework.messaging.MessageChannel
     * @Date 2023-04-20 15:10:25
     * @Author WangKun
     */
    @Bean("mqttInput")
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }


    /**
     * @param
     * @Description 创建MqttPahoClientFactory 设置MQTT的broker的连接属性
     * @Throws
     * @Return org.springframework.integration.mqtt.core.MqttPahoClientFactory
     * @Date 2023-04-20 15:11:33
     * @Author WangKun
     */
    @Bean
    public MqttPahoClientFactory inClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(mqttConfig.getHost());
        options.setUserName(mqttConfig.getUsername());
        options.setPassword(mqttConfig.getPassword().toCharArray());
        options.setConnectionTimeout(mqttConfig.getTimeout());
        options.setKeepAliveInterval(mqttConfig.getKeepalive());
        options.setCleanSession(mqttConfig.getCleanSession());
        options.setAutomaticReconnect(mqttConfig.getAutomaticReconnect());
        options.setWill("willTopic", WILL_DATA, 2, false);
        factory.setConnectionOptions(options);
        return factory;
    }


    /**
     * @param
     * @Description 入站
     * @Throws
     * @Return org.springframework.integration.core.MessageProducer
     * @Date 2023-04-20 15:12:06
     * @Author WangKun
     */
    @Bean
    public MessageProducer producer() {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(mqttConfig.getClientId() + "_customer", inClientFactory(), mqttConfig.getTopics());
        adapter.setCompletionTimeout(5000);
        DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();
        // 按字节接收消息
        // defaultPahoMessageConverter.setPayloadAsBytes(true);
        adapter.setConverter(defaultPahoMessageConverter);
        adapter.setQos(mqttConfig.getQos());
        adapter.setOutputChannel(mqttInputChannel());

        return adapter;
    }

    /**
     * @param
     * @Description 通过通道获取数据
     * ServiceActivator注解表明:当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。
     * tips:
     * 异步处理
     * @Throws
     * @Return org.springframework.messaging.MessageHandler
     * @Date 2023-04-20 15:12:55
     * @Author WangKun
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttInput")
    public MessageHandler handler() {
        return this.mqttMessageHandler;
    }
}

六: 重写MQTT网关接口

import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;

/**
 * @Description 网关接口MqttGateway
 * @Author WangKun
 * @Date 2023/4/19 15:01
 * @Version
 */
@MessagingGateway(defaultRequestChannel = "mqttOut")
public interface MQTTGateway {

    /**
     * @param message 消息
     * @Description 定义重载方法,用于消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:53:22
     * @Author WangKun
     */
    void sendToMqtt(String message);

    /**
     * @param topic   主题
     * @param message 消息
     * @Description 指定topic进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:03
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String message);

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:33
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String message);

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息(字节数字类型)
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:56
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, byte[] message);
}

七: 消费端消息拦截提取

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;

/**
 * @Description 消费端消息处理工具(消费端消息处理)
 * @Author WangKun
 * @Date 2023/4/20 15:31
 * @Version
 */

@AllArgsConstructor
@Component
@Slf4j
public class MQTTMessageHandler implements MessageHandler {


    /**
     * @Description 消息处理(在这实现接收消息处理业务, 资源注入不进来,可以手动注入)
      * @param message
     * @Throws
     * @Return void
     * @Date 2023-04-20 15:38:22
     * @Author WangKun
     */
    @Override
    public void handleMessage(Message<?> message) throws MessagingException {
        log.info("收到的完整消息为--->{}", message);
        log.info("----------------------");
        log.info("message:" + message.getPayload());
        log.info("Id:" + message.getHeaders().getId());
        log.info("receivedQos:" + message.getHeaders().get(MqttHeaders.RECEIVED_QOS));
        String topic = (String) message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC);
        log.info("topic:" + topic);
        log.info("----------------------");
    }
}

八: 为了方便使用,再封装发送工具


import org.springframework.stereotype.Component;


/**
 * @Description MQTT 消息发送工具类
 * @Author WangKun
 * @Date 2023/4/20 16:13
 * @Version
 */
@Component
public class MQTTUtils {

    /**
     * 资源手动注入
     */
    private static final MQTTGateway mqttGateway = SpringUtils.getBean(MQTTGateway.class);

    /**
     * @param message 消息
     * @Description 定义重载方法,用于消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:53:22
     * @Author WangKun
     */
    public static void send(String message) {
        mqttGateway.sendToMqtt(message);
    }

    /**
     * @param topic   主题
     * @param message 消息
     * @Description 指定topic进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:03
     * @Author WangKun
     */
    public static void send(String topic, String message) {
        mqttGateway.sendToMqtt(topic, message);
    }

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:33
     * @Author WangKun
     */
    public static void send(String topic, int qos, String message) {
        mqttGateway.sendToMqtt(topic, qos, message);
    }

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息(字节数字类型)
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:56
     * @Author WangKun
     */
    public static void send(String topic, int qos, byte[] message) {
        mqttGateway.sendToMqtt(topic, qos, message);
    }

}

九: 测试类

/**
 * @Description TODO
 * @Author WangKun
 * @Date 2023/4/20 16:58
 * @Version
 */
@RestController
public class MQTTController {

    @PostMapping("/send")
    public String send(@RequestBody MQTTMessage message) {
        // 发送消息到指定主题
//        mqttGateWay.sendToMqtt(myMessage.getTopic(), 1, myMessage.getContent());
        MQTTUtils.send(message.getTopic(), message.getQos(), message.getContent());
        return "send topic: " + message.getTopic() + ", message : " + message.getContent();
    }

}

整体代码结构

 测试结果:

EMQX服务监控:

 Apifox: 

MQTTX订阅该主题同时也收到消息

 MQTTX发送消息到该主题

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Description spring工具类 方便在非spring管理环境中获取bean
 * @Author WangKun
 * @Date 2023/4/19 15:01
 * @Version
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    // Spring应用上下文环境
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * @param name
     * @Description 获取bean对象
     * @Throws
     * @Return T 注册的bean的实例
     * @Date 2023-04-20 16:50:47
     * @Author WangKun
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * @param clz
     * @Description 获取类型为requiredType的对象
     * @Throws
     * @Return T
     * @Date 2023-04-20 16:51:18
     * @Author WangKun
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T) beanFactory.getBean(clz);
    }

    /**
     * @param name
     * @Description 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     * @Throws
     * @Return boolean
     * @Date 2023-04-20 16:51:26
     * @Author WangKun
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /**
     * @param name
     * @Description 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * @Throws
     * @Return boolean
     * @Date 2023-04-20 16:51:39
     * @Author WangKun
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @Description 注册对象的类型
     * @Throws
     * @Return java.lang.Class<?>
     * @Date 2023-04-20 16:51:58
     * @Author WangKun
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /**
     * @param name
     * @Description 如果给定的bean名字在bean定义中有别名,则返回这些别名
     * @Throws
     * @Return java.lang.String[]
     * @Date 2023-04-20 16:52:08
     * @Author WangKun
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

    /**
     * @param invoker
     * @Description 获取aop代理对象
     * @Throws
     * @Return T
     * @Date 2023-04-20 16:52:17
     * @Author WangKun
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return (T) AopContext.currentProxy();
    }

    /**
     * @Description 获取配置文件值
      * @param key
     * @Throws
     * @Return java.lang.String
     * @Date 2023-04-25 11:30:50
     * @Author WangKun
     */
    public static String getRequiredProperty(String key) {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

### Spring Boot 整合 MQTT 发送消息到 MQTT 服务器 为了在Spring Boot项目中集成MQTT协议并实现向MQTT服务器发送消息的功能,需先配置项目的依赖项。对于仅涉及订阅的情况,在`pom.xml`文件中加入如下依赖[^2]: ```xml <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version> </dependency> ``` 除了上述依赖外,还需引入用于简化与Spring框架交互的Paho库版本。这可以通过添加额外的starter来完成。 接着定义一个Java类作为MQTT客户端服务组件,负责处理连接至MQTT代理以及执行消息收发操作。下面是一个简单的例子展示如何创建此类,并通过它向指定主题发布一条测试消息: ```java import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; @Service public class MqttService { private static final String BROKER_URL = "tcp://broker.hivemq.com:1883"; // 替换成实际使用的Broker地址 private static final String CLIENT_ID = "spring-boot-mqtt-client"; @PostConstruct public void init() throws MqttException { try (MqttClient client = new MqttClient(BROKER_URL, CLIENT_ID)) { client.connect(); MqttMessage message = new MqttMessage("Hello from Spring Boot!".getBytes()); message.setQos(1); client.publish("/test/topic", message); System.out.println("Published message to topic /test/topic"); client.disconnect(); } } } ``` 这段代码展示了基本的消息发布流程:建立到MQTT Broker的TCP连接;构建待发布的消息对象;设置服务质量等级(QoS),这里设为1表示至少一次传递保证;最后调用publish方法将消息推送到特定的主题上。 值得注意的是,以上示例中的`BROKER_URL`应替换为所选MQTT服务器的实际URL。如果是在本地环境中运行,则可以选择使用EMQX这样的企业级MQTT平台来进行实验和学习开发工作[^1]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值