源码仓库
从零开始搭建属于自己的物联网平台(一)需求分析以及架构设计
从零开始搭建属于自己的物联网平台(二)实现基于订阅发布的消息总线
从零开始搭建属于自己的物联网平台(三)基于netty实现mqtt server网关
在物联网平台场景下,接入设备是多种多样的,这时候就不能以硬编码的形式在源码中实现,所以要实现动态的可以自由添加修改的协议解析方式
针对这一场景,以加载jar包的形式来实现,指定标准的接口,只要在jar包中实现对应的接口,做标准的协议解析方法就可以了(这里要注意,要支持设备上下行消息解析)。
/**
* 消息协议支持接口,通过实现此接口来自定义消息协议
*
* @author liuhualin
* @since 1.0.0
*/
public interface ProtocolSupport extends Serializable {
/**
* @return 协议ID
*/
String getId();
/**
* @return 协议名称
*/
String getName();
/**
* @return 说明
*/
String getDescription();
/**
* @return 获取支持的协议类型
*/
List<Transport> getSupportedTransport();
/**
* 根据指定协议取得
*
* @return 获取支持的协议类型
*/
DeviceMessageCodec getMessageCodecSupport(String transport);
}
定义协议解析包对象应具有的功能
DeviceMessageCodec这里继承了两个接口,分别用来实现上行解析以及下行解析,并且DeviceMessageCodec定义了这个解析器对应的协议类型,最终结构是下面这个样子的:
一个协议包可能有多个DeviceMessageCodec(根据协议包解析的网络协议来的,比如需要解析TCP、MQTT两种那就添加两个DeviceMessageCodec),每个DeviceMessageCodec都实现了上行消息解析、下行消息解析两个功能。
/**
* 实现自定义消息解析需要实现该类
*/
public interface ProtocolSupportProvider {
ProtocolSupport create();
}
/**
* 多传输协议支持的解析包
*/
@Data
public class CompositeProtocolSupport implements ProtocolSupport {
/**
* 协议ID
*/
private String id;
/**
* 协议名称
*/
private String name;
/**
* 协议描述
*/
private String description;
/**
* 支持的协议类型
*/
private Map<String, DeviceMessageCodec> messageCodecSupports = new ConcurrentHashMap<>();
@Override
public String getId() {
return this.id;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public List<Transport> getSupportedTransport() {
return messageCodecSupports.entrySet().stream().map(Map.Entry::getValue).map(DeviceMessageCodec::getSupportTransport).collect(Collectors.toList());
}
@Override
public DeviceMessageCodec getMessageCodecSupport(String transport) {
return messageCodecSupports.get(transport);
}
public void addMessageCodecSupport(Transport transport, DeviceMessageCodec codec) {
messageCodecSupports.put(transport.getId(), codec);
}
public void addMessageCodecSupport(DeviceMessageCodec codec) {
addMessageCodecSupport(codec.getSupportTransport(), codec);
}
}
以classloader的形式解析jar包,反射调用ProtocolSupportProvider的create
**
* 自定义协议支持包注册器(内存)
*/
@Component
@Slf4j
public class MemoryProtocolSupportRegistry {
/**
* 协议支持包
*
* K: 协议ID
* V: 协议实体
*/
private Map<String, ProtocolSupport> protocolSupportMap = new ConcurrentHashMap<>(16);
/**
* 取得解析器
*
* @param protocolId 协议ID
*/
public ProtocolSupport getProtocolSupport(String protocolId) {
return protocolSupportMap.get(protocolId);
}
/**
* 发布协议
*/
public void publishProtocol(String jarFilePath, String mainClass, String protocolId) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
decodeProtocolJar(jarFilePath, mainClass, protocolId);
}
/**
* 发布协议
*/
public void publishProtocol(ProtocolSupport protocolSupport) {
protocolSupportMap.put(protocolSupport.getId(), protocolSupport);
}
/**
* 解析协议jar包
*
* @param jarFilePath
* @throws IllegalAccessException
*/
private void decodeProtocolJar(String jarFilePath, String mainClass, String protocolId) {
File jarFile = new File(jarFilePath);
URL url;
try {
url = jarFile.toURI().toURL();
} catch (Exception e) {
throw new ServiceException("URL错误,加载不到jar");
}
if (null != url) {
try {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, this.getClass().getClassLoader());
Class<?> codec = urlClassLoader.loadClass(mainClass);
Object instance = codec.getClass().forName(mainClass, true, urlClassLoader).newInstance();
Method method = codec.getMethod("create");
// 加载协议包,创建实体对象
ProtocolSupport protocolSupport = (ProtocolSupport) method.invoke(instance);
if (!StringUtils.equals(protocolSupport.getId(), protocolId)) {
throw new ServiceException("jar包内定义的ID与协议ID不匹配");
}
protocolSupportMap.put(protocolSupport.getId(), protocolSupport);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new ServiceException("jar包解析异常");
}
}
}
}