
SpringBoot整合MQTT:后端订阅设备数据、解析传感器报文
作者:黒漂技术佬
前面几篇文章都是在讲 MQTT 协议本身和硬件端的事情。现在该轮到后端程序员出场了------设备把数据发到了 MQTT Broker,咱们的后端服务怎么把它接住?
这篇就手把手带你用 SpringBoot 整合 MQTT,实现订阅传感器数据、解析报文、存入库的全流程。
一、方案选型:用哪个 MQTT 客户端库?
Java 生态里搞 MQTT 主要有两个选项:
| 方案 | 说明 | 推荐度 |
|---|---|---|
| org.eclipse.paho.client.mqttv3 | Eclipse Paho 原生客户端,功能完整但偏底层 | ⭐⭐⭐ |
| spring-integration-mqtt | Spring 官方集成方案,基于 Paho 封装,与 Spring 生态无缝对接 | ⭐⭐⭐⭐⭐ |
强烈推荐 spring-integration-mqtt。 理由很简单:你已经在用 SpringBoot 了,何必再从底层写一堆连接管理、线程池、异常处理的代码?Spring Integration 把 MQTT 客户端包装成了 Spring 的 MessageChannel 和 MessageHandler,和你写 Controller 一个味儿。
二、引入依赖和配置
2.1 pom.xml
xml
<!-- MQTT 核心依赖 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- JSON 处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
SpringBoot 的 spring-boot-starter-integration 会自动拉取 Integration 核心,所以不需要额外引入。
2.2 application.yml
yaml
mqtt:
broker-url: tcp://192.168.1.100:1883
client-id: ${spring.application.name}-${random.value}
username: admin
password: admin123
# 订阅的Topic列表
topics:
- agriculture/+/+/sensor/#
# QoS级别
qos: 1
# 超时和心跳配置
completion-timeout: 3000
keep-alive-interval: 60
# 是否异步发送
async: true
client-id 里加了随机值是为了支持多实例部署------两个相同 client-id 的连接会互相踢下线,你肯定不想这样。
三、MQTT 配置类
下面是一个可直接用于生产的 MQTT 配置类(40行左右):
java
@Configuration
@IntegrationComponentScan
public class MqttConfig {
@Value("${mqtt.broker-url}")
private String brokerUrl;
@Value("${mqtt.client-id}")
private String clientId;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.completion-timeout}")
private int completionTimeout;
@Value("${mqtt.keep-alive-interval}")
private int keepAliveInterval;
@Value("#{'${mqtt.topics}'.split(',')}")
private List<String> topics;
@Value("${mqtt.qos}")
private int qos;
// ① 连接配置
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{brokerUrl});
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setCleanSession(false); // 持久会话
options.setAutomaticReconnect(true); // 自动重连
options.setKeepAliveInterval(keepAliveInterval);
options.setConnectionTimeout(10);
return options;
}
// ② 客户端工厂
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(mqttConnectOptions());
return factory;
}
// ③ 入站通道(MQTT Broker → 应用)
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
// ④ 入站适配器(订阅Topic)
@Bean
public MessageProducer inbound() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
clientId, mqttClientFactory(),
topics.toArray(new String[0]));
adapter.setCompletionTimeout(completionTimeout);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(qos);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
}
来逐段解读一下:
- ①
MqttConnectOptions:就像你上网时的连接设置。setAutomaticReconnect(true)告诉 Paho:「断了就自己连回来,别烦我」。 - ②
MqttPahoClientFactory:工厂模式,负责生产 MQTT 客户端实例。Spring Integration 内部会用它来创建连接。 - ③
DirectChannel:Spring Integration 的消息通道,简单理解就是一个「管道」,消息从这里流进来。 - ④
MqttPahoMessageDrivenChannelAdapter:入站适配器,负责订阅 Topic,把收到的消息灌入mqttInputChannel。topics.toArray(new String[0])支持多 Topic 订阅,比如同时订阅温湿度 Topic 和光照 Topic。
四、消息接收处理器
配置写好了,现在接收消息:
java
@Component
public class SensorDataHandler {
@ServiceActivator(inputChannel = "mqttInputChannel")
public void handleMessage(Message<?> message) {
// 获取 Topic
String topic = (String) message.getHeaders()
.get(MqttHeaders.RECEIVED_TOPIC);
// 获取 Payload(消息体)
String payload = (String) message.getPayload();
System.out.printf("[收到消息] Topic: %s%n", topic);
System.out.printf("[消息内容] %s%n", payload);
// 解析JSON
try {
SensorData data = parseSensorData(topic, payload);
processSensorData(data);
} catch (Exception e) {
System.err.println("消息解析失败: " + e.getMessage());
}
}
/**
* 根据Topic路由到不同的解析逻辑
*/
private SensorData parseSensorData(String topic, String payload)
throws Exception {
ObjectMapper mapper = new ObjectMapper();
if (topic.contains("/temperature")) {
return mapper.readValue(payload, TemperatureData.class);
} else if (topic.contains("/humidity")) {
return mapper.readValue(payload, HumidityData.class);
} else if (topic.contains("/light")) {
return mapper.readValue(payload, LightData.class);
}
throw new IllegalArgumentException("Unknown topic: " + topic);
}
private void processSensorData(SensorData data) {
// 1. 数据校验
if (!data.isValid()) {
log.warn("数据异常,已丢弃: {}", data);
return;
}
// 2. 业务处理(存库、告警、转发等)
sensorDataService.save(data);
// 3. 实时推送(WebSocket通知前端大屏)
webSocketService.push(data);
}
}
@ServiceActivator(inputChannel = "mqttInputChannel") 这行是核心。它告诉 Spring:「MQTT 来的消息从 mqttInputChannel 这个管道流过来,交给 handleMessage 方法处理」。
Spring Integration 的 Message<?> 对象封装了消息的 Header(元数据)和 Payload(消息体)。从 Header 里可以拿到 Topic、QoS、是否 Retained 等信息。
五、消息发布:后端向设备发指令
光收不发怎么行?咱们还得给设备下发控制指令(开风机、关水泵之类的):
java
@Service
public class MqttCommandService {
@Autowired
private MqttPahoClientFactory mqttClientFactory;
@Value("${mqtt.client-id}-outbound")
private String outboundClientId;
/**
* 发送控制指令到指定设备
*/
public void sendCommand(String deviceId, String command, Map<String, Object> params) {
String topic = String.format("agriculture/%s/command", deviceId);
CommandMessage msg = new CommandMessage();
msg.setCommand(command);
msg.setParams(params);
msg.setTimestamp(System.currentTimeMillis());
String payload = new ObjectMapper().writeValueAsString(msg);
// 创建出站处理器
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(
outboundClientId, mqttClientFactory);
handler.setDefaultTopic(topic);
handler.setDefaultQos(1); // 至少一次送达
// 发送
handler.handleMessage(
MessageBuilder.withPayload(payload).build());
}
}
注意出站适配器的 clientId 和入站的不能一样,否则会冲突。这里加了 -outbound 后缀来区分。
六、连接异常处理
农业生产环境不如机房稳定,MQTT 连接偶尔会断开。我们需要感知并处理这种状况:
java
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = mqttConnectOptions();
// 方式一:Paho 自带的自动重连(推荐)
options.setAutomaticReconnect(true);
// 重连间隔从 1 秒开始,最大 30 秒
options.setMaxReconnectDelay(30000);
factory.setConnectionOptions(options);
return factory;
}
/**
* 方式二:自定义回调监听连接状态(更灵活)
*/
@Component
public class MqttConnectionListener implements MqttCallbackExtended {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if (reconnect) {
// 重连成功,处理离线期间积压的业务
log.info("MQTT 重连成功: {}", serverURI);
sensorService.syncOfflineData();
} else {
log.info("MQTT 首次连接成功: {}", serverURI);
}
}
@Override
public void connectionLost(Throwable cause) {
log.error("MQTT 连接断开: {}", cause.getMessage());
// 可以在这里触发告警通知
}
@Override
public void messageArrived(String topic, MqttMessage message) {
// 这个回调由 Paho 原生 API 触发
// 用 Spring Integration 的话,消息走 @ServiceActivator,这里不需要处理
}
}
七、多 Topic 订阅与消息分发
智慧农业场景下,后端通常要同时订阅十几个 Topic。如果全堆在一个 Handler 里,代码必然变成一锅粥。这时候策略模式就派上用场了:
java
// 定义处理器接口
public interface TopicHandler {
boolean supports(String topic);
void handle(String topic, String payload);
}
// 温度处理器
@Component
public class TemperatureHandler implements TopicHandler {
public boolean supports(String topic) {
return topic.contains("/temperature");
}
public void handle(String topic, String payload) {
// 温度相关处理
}
}
// 湿度处理器
@Component
public class HumidityHandler implements TopicHandler {
public boolean supports(String topic) {
return topic.contains("/humidity");
}
public void handle(String topic, String payload) {
// 湿度相关处理
}
}
// 统一分发器
@Component
public class MessageDispatcher {
private final List<TopicHandler> handlers;
public MessageDispatcher(List<TopicHandler> handlers) {
this.handlers = handlers;
}
public void dispatch(String topic, String payload) {
for (TopicHandler handler : handlers) {
if (handler.supports(topic)) {
handler.handle(topic, payload);
return;
}
}
log.warn("未找到匹配的处理器: {}", topic);
}
}
Spring 会自动扫描所有实现了 TopicHandler 的 Bean,注入到 MessageDispatcher。后续新增传感器类型只需新增一个 Handler 类,完全符合开闭原则------对扩展开放,对修改关闭。
总结
SpringBoot 整合 MQTT 的关键步骤就三步:配置连接参数 → 定义消息通道 → 绑定处理器。Spring Integration 帮你屏蔽了连接管理、线程调度、异常重试这些脏活累活,你就可以专心写业务逻辑。
记住几个容易踩的坑:
- 多实例部署时
clientId必须唯一 - 入站和出站不能用同一个
clientId setCleanSession(false)配合setAutomaticReconnect(true)才是弱网环境的正确打开方式