8.完成任务实现的SDK封装及插件式加载

1.设计

任务的实现目前完成了Modbus RTU、Modbus TCP、Virtule。任务实现应该是任意的,比如打印一段话,执行一句SQL等,所以系统内部的必然要做到可扩展。

要做到可扩展,首先第一步就是定义标准,所以我们首先需要封装任务实现的SDK(第一件事)。

还要考虑到,用户使用我们的框架,但是并不想修改我们框架内部的代码,而是自己建立仓库完成实现,动态加载到我们的系统中,所以我们还要提供插件式集成任务实现的能力(第二件事)。

所以下面就对这两个需求展开设计与实现。

1.1 封装任务实现的SDK

此任务较易实现,因为前面我们就对任务有了一定的封装,所以只需要将定义部分,摘抄出去形成独立的jar就可以了,说干就干

1.1.1 建立一个新的工程:dttask-protocol-sdk

dttask-protocol-sdk 就是一个简单的java jar

移动文件,注意更改import

1.1.2 建立一个新的工程:dttask-protocol-simulator

这是一个依赖dttask-protocol-sdk的任务实现工程

它没有什么特殊,和dttask-server里的Virtual里的实现基本一样,就是类名不一样。建这个类也是为了后续测试动态加载插件做准备

至此工程依赖结构如下图:

1.2 实现动态加载插件

上面已经完成了插件的编写,因为我们整体使用的spring,所以我们在实现插件时就需要完成spring的bean注册。

1.2.1 ProtocolController

提供一个flushProtocol的接口,里面完成对插件jar包的动态加载,原理就是:

  • 先使用classloader将jar包里的class都加载进来
  • 然后检测所有class有没有是spring的bean,是的话,就向spring容器注册它对应的BeanDefinition
  • 最后使用spring容器查找对应bean,完成bean的实例化以及任务的实现放进ProtocolManager中集中管理
@RestController
@Slf4j
public class ProtocolController {

    @Autowired
    private DefaultListableBeanFactory defaultListableBeanFactory;
    @Autowired
    private ProtocolManager protocolManager;

    @GetMapping("/flushProtocol")
    public void flushProtocol(@RequestParam(value = "jarAddress", required = false) String jarAddress) {
        if (jarAddress == null) {
            jarAddress = "D:\\workspaces\\dttask\\protocolJar\\dttask-protocol-simulator-1.0-SNAPSHOT.jar";
        }
        String jarPath = "file:/" + jarAddress;
        hotDeployWithSpring(jarAddress, jarPath);
    }

    /**
     * 加入jar包后 动态注册bean到spring容器,包括bean的依赖
     */
    public void hotDeployWithSpring(String jarAddress, String jarPath) {
        Set<String> classNameSet = readJarFile(jarAddress);
        try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader())) {
            for (String className : classNameSet) {
                Class<?> clazz = urlClassLoader.loadClass(className);
                if (isSpringBeanClass(clazz)) {
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                    String beanName = transformName(className);
                    defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
                }
            }
        } catch (ClassNotFoundException e) {
            throw new BusinessException("hotDeployWithSpring ClassNotFoundException", e);
        } catch (MalformedURLException e) {
            throw new BusinessException("hotDeployWithSpring MalformedURLException", e);
        } catch (IOException e) {
            throw new BusinessException("hotDeployWithSpring IOException", e);
        } finally {
            protocolManager.refreshMap();
        }


    }

    /**
     * 读取jar包中所有类文件
     */
    public static Set<String> readJarFile(String jarAddress) {
        Set<String> classNameSet = new HashSet<>();
        Enumeration<JarEntry> entries;
        try (JarFile jarFile = new JarFile(jarAddress)) {
            //遍历整个jar文件
            entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                if (name.endsWith(".class")) {
                    String className = name.replace(".class", "").replace("/", ".");
                    classNameSet.add(className);
                }
            }
        } catch (IOException e) {
            log.error("readJarFile exception:", e);
            throw new BusinessException("readJarFile exception", e);
        }
        return classNameSet;
    }

    /**
     * 方法描述 判断class对象是否带有spring的注解
     */
    public static boolean isSpringBeanClass(Class<?> cla) {
        if (cla == null) {
            return false;
        }
        // 不是抽象类 接口 且 没有以下注解
        return (cla.getAnnotation(Component.class) != null
                || cla.getAnnotation(Repository.class) != null
                || cla.getAnnotation(Service.class) != null)
                && !Modifier.isAbstract(cla.getModifiers())
                && !cla.isInterface();
    }

    /**
     * 类名首字母小写 作为spring容器beanMap的key
     */
    public static String transformName(String className) {
        String tmpstr = className.substring(className.lastIndexOf(".") + 1);
        return tmpstr.substring(0, 1).toLowerCase() + tmpstr.substring(1);
    }


    /**
     * 删除jar包时 需要在spring容器删除注入
     */
    public void delete(String jarAddress, String jarPath) {
        Set<String> classNameSet = readJarFile(jarAddress);
        try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)},
                Thread.currentThread().getContextClassLoader())) {
            for (String className : classNameSet) {
                Class<?> clazz = urlClassLoader.loadClass(className);
                if (isSpringBeanClass(clazz)) {
                    defaultListableBeanFactory.removeBeanDefinition(transformName(className));
                }
            }
        } catch (MalformedURLException e) {
            throw new BusinessException("delete MalformedURLException", e);
        } catch (IOException | ClassNotFoundException e) {
            throw new BusinessException("delete IOException or ClassNotFoundException", e);
        } 
    }
}

1.2.2 JobController

完成对Job的启动和停止

@RestController
@Slf4j
public class JobController {
    
    @Autowired
    private NetworkService networkService;
    
    @GetMapping("/stopDttaskJob")
    public void stopJobId(@RequestParam("dttaskJobId") long dttaskJobId) {
        Set<Long> dttaskJobIds = new HashSet<>();
        dttaskJobIds.add(dttaskJobId);
        networkService.stopCollect(dttaskJobIds);
    }

    @GetMapping("/startDttaskJob")
    public void startJobId(@RequestParam("dttaskJobId") long dttaskJobId) {
        Set<Long> dttaskJobIds = new HashSet<>();
        dttaskJobIds.add(dttaskJobId);
        networkService.startCollect(dttaskJobIds);
    }
    
}

2. 测试

  • 使用ApiPost建立3个请求

  • 启动三个节点,三个节点完成选举,并各自执行2个任务

  • dttask-protocol-simulator工程打一个jar包,放到 D:\workspaces\dttask\protocolJar 目录下

  • 对1号controller节点,发送停止任务请求

  • 将停止的任务的link_type字段改为-2并保存(-2是simulator实现的任务类型)

  • 发送协议实现接口调用

  • dttask-protocol-simulator加上一个断点,方便后续的查看

  • 发送任务启动接口

进入断点,表示 外部的插件已经动态加载进来了

至此 封装SDK 和 动态加载插件完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/254382.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FA1210AN (MHz范围晶体单元超小型低轮廓贴片)

FA1210AN体积小&#xff0c;高度低&#xff0c;设计人员可以在不影响性能的情况下节省板空间。这对于那些限制功能和尺寸的设备和模块来说是必不可少的。 宽MHz范围的频率服务于流行的&#xff0c;无线通信协议&#xff0c;理想的消费者和工业物联网应用。应用程序&#xff1a…

第15章 《乐趣》Page375~379定时器,定时回调,定时回调线程id, 停止后续定时

运行效果&#xff1a;在骏马窗口中&#xff0c;按下ctrl t 键&#xff0c;就可以看到定时回调&#xff0c;同时可以看出timer_callback和main()真的不在同一线程中运行 代码如下&#xff1a;仅给出main.cpp, 其他的头文件和源文件和Page355~375的代码一样&#xff0c;可参看上…

【C语言】数组(一维)详解,手把手教你,保姆级!!!

目录 数组的概念 数组的创建 数组的初始化 数组的类型 数组使用下标 数组的打印 数组的输入 数组的储存 总结 数组的概念 数组是⼀组相同类型元素的集合&#xff1b; 从这个概念中我们有3点拓展&#xff1a; 1&#xff0c;数组中存放的是1个或者多个数据&#xff0c;但…

4.raft协议及简化版raft协议

1.Raft协议介绍 这篇文章&#xff1a;百度安全验证 讲的比较详细了&#xff0c;我再以我的方式汇总一下&#xff1a; Raft协议是一种分布式一致性协议&#xff0c;比Pasox简单&#xff0c;旨在解决数据的一致性和高可用性Raft在选举中的方式&#xff1a; 所有节点相互建立连…

什么是磁钢的工作点和Pc值?如何计算Pc值?

永磁体是在开路状态下工作的&#xff0c;由于开路状态的磁体是在退磁场的作用下&#xff0c;所以工作状态下的永磁体的磁感应强度不在闭路状态的Br点上&#xff0c;而是在比Br低的退磁曲线上的某一点&#xff0c;这一点称为永磁体的工作点&#xff0c;如下图D点。 工作点与退磁…

【C语言】操作符详解(四):结构成员访问操作符

目录 结构成员访问操作符 结构体 结构体的声明 结构体变量的定义和初始化 结构成员访问操作符 结构体成员的直接访问 结构体成员的间接访问 结构成员访问操作符 结构体 ⭐C语言已经提供了内置类型&#xff0c;如: char、short、int、long、float、double等&#xff0c;但…

备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

MySQLhttps://www.mysql.com/ 将下发的ds_db01.sql数据库文件放置mysql中 12、编写Scala代码&#xff0c;使用Spark将MySQL的ds_db01库中表user_info的全量数据抽取到Hive的ods库中表user_info。字段名称、类型不变&#xff0c;同时添加静态分区&#xff0c;分区字段为etl_da…

提高软件交付速度的6种架构策略

本文向您展示如何评估软件交付性能&#xff0c;并向您介绍可用于提高软件交付性能的六种策略。 如何评估软件的交付速度 软件交付速度能够促进业务发展&#xff0c;那么我们如何评估软件的交付速度呢&#xff1f;主要有以下4个指标 一个功能从开发到上线运营使用需要多久&#…

代码随想Day39 | 62.不同路径、63. 不同路径 II

62.不同路径 每次向右或者向下走两个选择&#xff0c;定义dp数组dp[i][j] 为到达索引ij的路径和&#xff0c;状态转移公式为 dp[i][j]dp[i-1][j]dp[i][j-1]&#xff0c;初始状态的第一行和第一列为1&#xff0c;从左上到右下开始遍历即可。详细代码如下&#xff1a; class Sol…

问卷调查结果分析指南:方法与技巧解析

问卷调查是一种常见的数据收集方式&#xff0c;广泛用于市场调研、科研、员工幸福评估等各个领域。但是&#xff0c;问卷的数据收集只是第一步&#xff0c;分析这种数据至关重要。问卷调查该怎么分析结果&#xff1f;首先要进行数据清理&#xff0c;然后对数据展开叙述&#xf…

基于Java Web的“大学生艺术节”管理系统的设计与实现论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对“大学生艺术节”方面的信息管理混乱&#xff0c;出错率高&#xff…

自动化测试Selenium node 配置

查看自己chrome浏览器的版本 下载chromedriver对应版本&#xff0c;下载当前版本中最大版本。 https://npm.taobao.org/mirrors/chromedriver 安装java jdk &#xff0c;版本至少1.7, 并配置jdk环境变量 以下2个文件放在同一个目录下 Cmd地址切换到第四点目录下&#xff0c;然…

Spark基础入门

spark基础入门 环境搭建 localstandlonespark ha spark code spark corespark sqlspark streaming 环境搭建 准备工作 创建安装目录 mkdir /opt/soft cd /opt/soft下载scala wget https://downloads.lightbend.com/scala/2.13.12/scala-2.13.12.tgz -P /opt/soft解压scala…

setXxx getXxx 封装

1.封装介绍 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。 2.封装的理解和好处 (1)隐藏实现细节 方法(连接数据库)<-----调用(传入参数...) 只负责调…

【真情流露】我为什么要写一本OpenCV C++书籍

使用OpenCV契机 大家好&#xff0c;我是贾志刚&#xff0c;OpenCV学堂公众号的号主&#xff0c;从2009年开始搞图像处理到今天我已经十四年了。刚开始搞图像处理做的是生物数据分析与细胞分析&#xff0c;用的是工具跟SDK是ImageJ这个框架&#xff0c;多数算法都是我自己裸写&…

借助图形控件Aspose.Tasks,在 C# 中将 XER 转换为 SVG

Primavera P6 是一款流行的项目管理软件&#xff0c;它使用XER 文件格式来存储项目数据。 SVG&#xff08;即可缩放矢量图形&#xff09;是一种流行的矢量图像格式&#xff0c;可用于为 Web 和打印应用程序创建可缩放图形。在某些情况下&#xff0c;我们可能需要以编程方式将 P…

深度学习笔记_6经典预训练网络LeNet-18解决FashionMNIST数据集

1、 调用模型库&#xff0c;定义参数&#xff0c;做数据预处理 import numpy as np import torch from torchvision.datasets import FashionMNIST import torchvision.transforms as transforms from torch.utils.data import DataLoader import torch.nn.functional as F im…

算法模板之双链表图文详解

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;算法模板、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️使用数组模拟双链表讲解1.1 &#x1f514;为什么我们要使用数组去模拟双链表…

全国巡展“2024人工智能展·世亚智博会”3月上海·4月杭州·6月北京

近年来&#xff0c;我国积极布局人工智能产业&#xff0c;竞跑“未来赛道”。随着各行业、各领域对人工智能需求的日益增长&#xff0c;与实体经济深度融合的新模式不断涌现&#xff0c;形成了具有中国特色的研发体系和应用生态&#xff0c;引领着经济社会各领域从数字化、网络…

YOLOv3-YOLOv8的一些总结

0 写在前面 这个文档主要总结YOLO系列的创新点&#xff0c;以YOLOv3为baseline。参考(抄)了不少博客&#xff0c;就自己看看吧。有些模型的trick不感兴趣就没写进来&#xff0c;核心的都写了。 YOLO系列的网络都由四个部分组成&#xff1a;Input、Backbone、Neck、Prediction…
最新文章