前言
在物联网开发中,MQTT 已经成为事实上的通信标准。很多朋友刚开始接触时,总觉得 MQTT 很复杂------Broker 是什么?QoS 怎么选?Spring Boot 怎么接入?
这篇文章不讲虚的,从 EMQX 服务端安装 到 Spring Boot 完整代码实现,一步不落,带你跑通全流程。
一、MQTT 是什么?
一句话概括:MQTT 是一种基于"发布/订阅"模式的轻量级消息传输协议。
你可以把它理解成一个"中间人":
-
发布者:发送消息到某个主题(Topic)
-
订阅者:订阅感兴趣的主题,接收消息
-
Broker(代理) :负责转发消息
不像 HTTP 那样"你请求我响应",MQTT 更像是"谁关心这个消息,就订阅它"。这种方式让发布者和订阅者完全解耦,非常适合物联网设备通信。
MQTT 核心概念速览
| 概念 | 说明 |
|---|---|
| Topic(主题) | 消息的"地址",用 / 分层,如 device/123/status |
| QoS(服务质量) | 0=最多一次(可能丢),1=至少一次(可能重复),2=只有一次(最可靠) |
| 通配符 | + 匹配单层,# 匹配多层(仅用于订阅) |
二、环境准备:安装 EMQX(Windows)
2.1 下载
访问 EMQX 官网下载页面,选择 Windows 版本的 ZIP 安装包。
⚠️ 注意 :下载
emqx-5.x.x-windows-amd64.zip,区分开源版和企业版,我们选开源版即可。
系统要求:
-
Windows 10/11 64位系统
-
至少 2GB 可用内存
-
磁盘空间 ≥ 200MB
2.2 解压
✅ 推荐 :解压到
D:\MQTT\emqx或C:\emqx这样的纯英文路径
解压后的目录结构:
text
emqx/
├── bin/ # 核心执行文件
├── etc/ # 配置文件
├── data/ # 运行数据
├── log/ # 日志文件
└── releases/ # 版本信息
2.3 启动 EMQX
打开 管理员权限 的 PowerShell 或 CMD,进入 emqx/bin 目录:
先安装emqx install ,后启动emqx console
2.4 验证启动
启动成功后,打开浏览器访问 http://localhost:18083,进入 EMQX Dashboard 管理控制台。
默认登录账号密码:
-
用户名:
admin -
密码:
public
登录后建议立即修改密码。

2.5 端口说明
EMQX 默认使用以下端口:
| 端口 | 用途 |
|---|---|
| 1883 | MQTT TCP 协议端口 |
| 8883 | MQTT SSL/TLS 端口 |
| 8083 | MQTT WebSocket 端口 |
| 18083 | Dashboard 管理控制台 |
三、Spring Boot 集成 MQTT
3.1 项目依赖
在 pom.xml 中添加 Spring Integration MQTT 依赖:
xml
XML
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
3.2 配置文件(application.yml)
yaml
XML
spring:
application:
name: mqtt-demo
mqtt:
brokerUrl: tcp://localhost:1883
user: root
password: 123456
clientId: bitstorm-server
topics:
- /x/+/notice
- /x/+/device
persistence: /var/mqtt/persistence
completionTimeout: 5000
keepAlive: 60
connectionTimeout: 30
defaultQos: 1
autoReconnect: true
cleanSession: true
maxInflight: 10000
3.3 配置类:MqttProperties
java
java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "mqtt")
@Data
@Component
public class MqttProperties {
private String brokerUrl;
private String user;
private String password;
private String clientId;
private String[] topics;
private String persistence;
private int completionTimeout;
private int keepAlive;
private int connectionTimeout;
private int defaultQos;
private boolean autoReconnect;
private boolean cleanSession;
private int maxInflight;
}
3.4 核心配置类:MqttIntegrationConfig
这是整个集成的核心,负责创建 MQTT 客户端工厂、消息入站适配器(接收消息)和出站适配器(发送消息)。
java
import jakarta.annotation.Resource;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import java.util.UUID;
import java.util.concurrent.*;
@Configuration
@IntegrationComponentScan(basePackages = "com.bitstorm.mqtt")
@EnableIntegration
public class MqttIntegrationConfig {
private static final Logger log = LoggerFactory.getLogger(MqttIntegrationConfig.class);
@Resource
private MqttProperties mqttProps;
/**
* MQTT 客户端工厂
*/
@Bean
public DefaultMqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = getMqttConnectOptions();
factory.setPersistence(new MqttDefaultFilePersistence(mqttProps.getPersistence()));
factory.setConnectionOptions(options);
return factory;
}
private MqttConnectOptions getMqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{mqttProps.getBrokerUrl()});
options.setUserName(mqttProps.getUser());
options.setPassword(mqttProps.getPassword().toCharArray());
options.setKeepAliveInterval(mqttProps.getKeepAlive());
options.setConnectionTimeout(mqttProps.getConnectionTimeout());
options.setAutomaticReconnect(mqttProps.isAutoReconnect());
options.setCleanSession(mqttProps.isCleanSession());
options.setMaxInflight(mqttProps.getMaxInflight());
return options;
}
/**
* 消息输入通道(带线程池,用于异步处理)
*/
@Bean
public MessageChannel mqttInputChannel() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 20, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(mqttProps.getMaxInflight()),
r -> {
Thread t = new Thread(r, "mqtt-inbound-" + Thread.currentThread().getName());
t.setDaemon(true);
return t;
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
return new ExecutorChannel(executor);
}
/**
* 消息入站适配器(订阅消息)
*/
@Bean
public MqttPahoMessageDrivenChannelAdapter inboundAdapter(
DefaultMqttPahoClientFactory clientFactory,
MessageChannel mqttInputChannel,
MessageChannel mqttErrorChannel) {
String clientId = mqttProps.getClientId() + "-consume-" + UUID.randomUUID();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(clientId, clientFactory, mqttProps.getTopics());
adapter.setCompletionTimeout(mqttProps.getCompletionTimeout());
adapter.setQos(mqttProps.getDefaultQos());
adapter.setOutputChannel(mqttInputChannel);
adapter.setErrorChannel(mqttErrorChannel);
adapter.setManualAcks(true); // 开启手动确认
return adapter;
}
/**
* 消息输出通道
*/
@Bean
public MessageChannel mqttOutputChannel() {
return new DirectChannel();
}
/**
* 消息出站处理器(发布消息)
*/
@Bean
@ServiceActivator(inputChannel = "mqttOutputChannel")
public MessageHandler mqttOutboundHandler(DefaultMqttPahoClientFactory clientFactory) {
String clientId = mqttProps.getClientId() + "-production-" + UUID.randomUUID();
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(clientId, clientFactory);
handler.setAsync(true);
handler.setDefaultRetained(false);
handler.setDefaultQos(mqttProps.getDefaultQos());
return handler;
}
/**
* 全局错误通道
*/
@Bean
public MessageChannel mqttErrorChannel() {
return new DirectChannel();
}
/**
* 错误通道处理器
*/
@Bean
@ServiceActivator(inputChannel = "mqttErrorChannel")
public MessageHandler mqttErrorHandler() {
return message -> {
Throwable cause = message.getPayload() instanceof Throwable ?
(Throwable) message.getPayload() :
new MessagingException("Unknown error", (Throwable) message.getPayload());
log.error("MQTT 错误发生,消息头:{}", message.getHeaders(), cause);
};
}
}
3.5 MQTT 网关接口(发送消息)
java
java
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
@MessagingGateway(defaultRequestChannel = "mqttOutputChannel")
public interface MqttGateway {
/**
* 发送消息到指定主题(使用默认 QoS)
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
/**
* 发送消息到指定主题,并指定 QoS
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
@Header(MqttHeaders.QOS) int qos,
String payload);
}
3.6 消息接收处理器(订阅消息)
java
java
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.acks.AcknowledgmentCallback;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
@Slf4j
public class MqttMessageHandler {
@Resource
private MqttGateway mqttGateway;
/**
* 定时发送测试消息
*/
@Scheduled(fixedDelay = 3000)
public void sendTestMessage() {
String payload = String.format("设备信息: %d, 时间: %s",
System.currentTimeMillis(),
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
mqttGateway.sendToMqtt("/x/identify/device", payload);
log.info("测试消息已发送: {}", payload);
}
/**
* 消息处理器 - 手动 ACK
*/
@ServiceActivator(inputChannel = "mqttInputChannel")
public void handleMessage(Message<?> message) {
// 获取 ACK 回调
Object ackObj = message.getHeaders().get(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK);
AcknowledgmentCallback ack = null;
if (ackObj instanceof AcknowledgmentCallback) {
ack = (AcknowledgmentCallback) ackObj;
}
try {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC, String.class);
String payload = message.getPayload().toString();
log.info("收到消息,主题:{},内容:{}", topic, payload);
// TODO: 在这里编写你的业务逻辑
if (ack != null) {
ack.acknowledge(); // 手动确认消息
log.debug("消息已确认");
}
} catch (Exception e) {
log.error("处理MQTT消息异常", e);
// 根据业务决定是否确认(不确认会触发重发)
}
}
}
四、测试验证
4.1 启动 Spring Boot 应用
启动后,控制台会输出定时发送的测试消息日志。

五、常见问题与踩坑指南
5.1 EMQX 启动失败
原因:端口 1883 被占用
解决:
bash
netstat -ano | findstr :1883
找到占用进程并关闭,或修改 EMQX 配置文件中的端口。
5.2 连接断开
MQTT 是长连接,网络波动可能导致断开。配置中已开启 autoReconnect: true,会自动重连。
5.3 Topic 设计不合理
不要随意命名 Topic,建议采用层级结构:
text
✅ device/{deviceId}/status
✅ device/{deviceId}/command
❌ /a/b/c/d/e/f/g
5.4 QoS 怎么选?
| QoS | 适用场景 |
|---|---|
| 0 | 传感器高频上报的非关键数据(丢一条无所谓) |
| 1 | 控制指令(开关灯、设备控制) |
| 2 | 金融、航空等关键数据 |
大多数场景 QoS 1 就够用了。
5.5 消息重复消费
使用 QoS 1 时可能收到重复消息。如果需要幂等处理,可以在业务层根据消息 ID 去重。
六、总结
本文从零开始,完成了:
-
✅ Windows 上安装 EMQX 并启动
-
✅ Spring Boot 集成 Spring Integration MQTT
-
✅ 消息发布(定时发送测试消息)
-
✅ 消息订阅(接收并手动 ACK)
-
✅ 使用 MQTTX 进行端到端测试
整套代码可以直接复制到项目中运行。如果遇到问题,欢迎在评论区留言交流!
📌 源码地址 :https://gitee.com/byte1026/mqtt-case.git
文中的所有代码均可直接使用,记得根据实际环境修改
application.yml中的连接配置。
