1.前言
上一篇文章写了spring-boot使用普通方式集成了mqtt,但是网上很多的帖子都是使用工厂模式实现,经过研究终于对工厂模式有些突破,不废话,直接上代码。
2.建一个spring-boot项目
2.1配置文件内容如下(本案例使用的是emqx作为服务端,大家在练习时自己下载,下载地址在上一篇普通方式集成mqtt的内容中有,地址:CSDN)
server.port=8085
#MQTT配置
#MQTT-服务端用户名
spring.mqtt.username=admin
#MQTT-服务端密码
spring.mqtt.password=public
#MQTT-服务端地址
spring.mqtt.url=tcp://localhost:1883
#MQTT-客户端clientid
spring.mqtt.clientid=clientid
#MQTT-默认主题
spring.mqtt.default_topic=test
2.2加入mqtt相关的maven支持
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mqtt-factory-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mqtt-factory-demo</name>
<description>mqtt-factory-demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3 创建类MqttSenderConfig
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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.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.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
@IntegrationComponentScan
@Slf4j
public class MqttSenderConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MqttSenderConfig.class);
@Value("${spring.mqtt.username}")
private String username;
@Value("${spring.mqtt.password}")
private String password;
@Value("${spring.mqtt.url}")
private String hostUrl;
@Value("${spring.mqtt.clientid}")
private String clientid;
@Value("${spring.mqtt.default_topic}")
private String defaultTopic;
/**
* 订阅的bean名称
*/
public static final String CHANNEL_NAME_IN = "mqttInboundChannel";
/**
* 发布的bean名称
*/
public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";
/**
* MQTT连接器选项
*/
@Bean
public <hostUrl> MqttConnectOptions getMqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
// 这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(false);
// 设置连接的用户名
options.setUserName(username);
// 设置连接的密码
options.setPassword(password.toCharArray());
//可以是多个服务端
options.setServerURIs(new String[]{hostUrl});
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
// 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
//options.setWill("willTopic", WILL_DATA, 2, false);
return options;
}
/**
* MQTT客户端
*/
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
}
/**
* MQTT信息通道(生产者)
*/
@Bean(name = CHANNEL_NAME_OUT)
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* MQTT消息处理器(生产者)
*/
@Bean
@ServiceActivator(inputChannel = CHANNEL_NAME_OUT)
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
clientid+"_publish", mqttClientFactory());
messageHandler.setAsync(true);
//设置生产者的消息级别
messageHandler.setDefaultQos(2);
//这个地方还没理解透,生产消息的时候怎么自由的指定主题?
messageHandler.setDefaultTopic(defaultTopic);
return messageHandler;
}
/**
* MQTT消息订阅绑定(消费者)
*/
@Bean
public MessageProducer inbound() {
// 可以同时消费(订阅)多个Topic
String []topics = new String[]{"publish", "test"};
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
clientid+"_subsribe", mqttClientFactory(), topics);
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
//设置消费者的消息级别
adapter.setQos(2);
// 设置订阅通道
adapter.setOutputChannel(mqttInboundChannel());
return adapter;
}
/**
* MQTT信息通道(消费者)
*/
@Bean(name = CHANNEL_NAME_IN)
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}
/**
* MQTT消息处理器(消费者)
* 持久化也是在这里处理
*/
@Bean
@ServiceActivator(inputChannel = CHANNEL_NAME_IN)
public MessageHandler handler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) {
try {
String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
String qos = message.getHeaders().get("mqtt_receivedQos").toString();
String payload = message.getPayload().toString();
LOGGER.info("主题:"+topic);
LOGGER.info("内容:"+payload);
LOGGER.info("级别:"+qos);
//message.get
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
};
}
}
2.4 创建MsgWriter接口
import com.example.mqttfactorydemo.comons.MqttSenderConfig;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
@Component
@MessagingGateway(defaultRequestChannel = MqttSenderConfig.CHANNEL_NAME_OUT)
public interface MsgWriter {
void sendToMqtt(String data);
void sendToMqtt(String payload,@Header(MqttHeaders.TOPIC) String topic);
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
2.5 创建测试的controller类
import com.example.mqttfactorydemo.service.MsgWriter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping(value = "/con")
public class PublishController {
@Resource
private MsgWriter msgWriter;
@RequestMapping(value = "/test")
public void test(String topic) {
/**
* 这两个发送可以
*/
//msgWriter.sendToMqtt("我是消息");
msgWriter.sendToMqtt("我是内容", topic);
/**
* 下面这个方法测试无效,qos指定了也没用,到现在还没搞清楚原因
*/
//msgWriter.sendToMqtt("我是主题", 2, "我是消息");
}
}
3 代码部分就完事儿了,接下来看看测试
先测试只有消息内容的接口,浏览器输入地址:http://localhost:8085/con/test
输出如下,因为默认订阅主题就是test,所以输出时按照默认主题推送
再测试有消息内容和主题的接口,浏览器输入地址:http://localhost:8085/con/test?topic=publish
后台输出如下内容(在MqttSenderConfig类中消费者我们订阅了两个主题:publish和test,所以topic的值只要是这两个的任意一个都可以):
4. 百度网盘代码获取地址
链接:https://pan.baidu.com/s/1SaH45ZYQrOecmImKXxml0Q
提取码:1234