Java-DataX 插件机制示例

示例代码

DataXPluginExample: DataX 项目的plugin 机制学习icon-default.png?t=O83Ahttps://gitee.com/wendgit/data-xplugin-example/

摘要

         DataXPluginExample 是一个我编写的专门解读DataX插件机制的示例项目,旨在深入解析和掌握DataX的插件机制。本示例通过简洁明了的实现方式,展示了如何实现插件的热插拔机制。本文将详细介绍DataXPluginExample的实现过程,旨在帮助那些对插件机制充满好奇心的学习者更快地理解和运用Java的插件机制。

项目结构

        

                图1 DataXPluginExample 项目结构图

        如图1 DataXPluginExample 项目结构图所示,DataXPluginExample项目共包含三个模块,pluginuser,pluginprovider,plugin1。

  • pluginuser:插件机制的使用者。

  • pluginprovider:插件机制的提供者。

  • plugin1:具体的插件实现模块。

插件机制实现方式详解

        图2 DataXPluginExample 插件机制实现时序图

        通过图2 DataXPluginExample 插件机制实现时序图的描述,我们可以很清晰的看到插件机制是如何实现的总体流程。接下来,我将遵从上述过程的描述,并结合示例代码详细讲述插件机制的实现。

1. 新建一个提供者,获取具体的插件

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        PluginProvider provider = new PluginProvider();
        AbstractJobPlugin abstractJobPlugin = provider.getUsePlugin("plugin1");
        abstractJobPlugin.getPluginName();
    }

}

        该方法在pluginUser模块,新建了一个插件提供者,并尝试根据插件名称获取具体的插件。

2. 寻找插件Jar包,加载插件Jar包

public class PluginProvider {

    public AbstractJobPlugin getUsePlugin(String pluginName) {
        LoadUtil loadUtil = new LoadUtil(pluginName);
        // 寻找插件Jar包集合
        JarLoader jarLoader = loadUtil.getJarLoader();
        try {
            Class<? extends AbstractJobPlugin> clazz =
                    (Class<? extends AbstractJobPlugin>) jarLoader
                            .loadClass(loadUtil.getClassName()); // person.wend.plugin1.provider.Plugin1Provider
            AbstractJobPlugin job = clazz.newInstance();
            return job;
        } catch (Exception e) {
            throw new RuntimeException("加载插件失败", e);
        }
    }


}

        PluginProvider中的getUsePlugin 方法是插件机制的核心。其中DataXPluginExample 的LoadUtil 直接复用了DataX 中的LoadUtil  的相关方法,但在此基础上进行了部分修改和简化。

        我们通过loadUtil.getJarLoader() 找到具体的Jar包包含的Class 实例后,后续就可以用claasLoader 的loadClass 方法获取具体的Class,并实例化。

        在DataX中,它获取的具体的Class 是获取的Reader 和Writer Class的内部类,它的内部类分为两种,Job和Task。而DataXPluginExample 简化了插件实现的Class 类,不包含内部类。

2.1 LoadUtil 具体实现
public class LoadUtil {

    public LoadUtil(String pluginName) {
        this.pluginName = pluginName;
        this.pluginPath = getPluginPath(pluginName);
    }

    /**
     * jarLoader的缓冲
     */
    private static Map<String, JarLoader> jarLoaderCenter = new HashMap();
    /**
     * 插件名称
     */
    private String pluginName;
    /**
     * 插件路径
     */
    private String pluginPath;

    public static String getPluginPath(String pluginName) {

        String DEFAULT_PLUGIN_PATH = System.getProperty("user.dir") + File.separator + pluginName + File.separator + "target" + File.separator;
        // 读plugin.json 文件
        File pluginFile = new File(DEFAULT_PLUGIN_PATH);
        if (!pluginFile.exists()) {
            throw new IllegalArgumentException("插件不存在.");
        }
        return pluginFile.getAbsolutePath();
    }

    public String getClassName() {
        // pluginName Class首字母大写
        String pluginNameClass = pluginName.substring(0, 1).toUpperCase() + pluginName.substring(1);

        return "person.wend." + pluginName + ".provider" + "." + pluginNameClass + "Provider";
    }

    public synchronized JarLoader getJarLoader() {

        JarLoader jarLoader = jarLoaderCenter.get(pluginName);
        if (null == jarLoader) {
            if (StringUtils.isBlank(pluginPath)) {
                throw new IllegalArgumentException("插件路径非法.");
            }
            jarLoader = new JarLoader(new String[]{pluginPath});
            jarLoaderCenter.put(pluginName, jarLoader);
        }

        return jarLoader;
    }
}

        loadUtil.getJarLoader(): JarLoader 即ClassLoader的封装,是一个类加载器。这个类加载器会加载默认路径下的所有jar包,也就是编写的插件。

        DataX 的插件机制也是如此,是基于ClassLoader的类加载的,然后按照plginName找到具体的reader和writer的插件jar包,并加载到 jarLoaderCenter 存储起来以方便后续使用。 

2.2 JarLoader 实现
/**
 * 提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。
 */
public class JarLoader extends URLClassLoader {
    public JarLoader(String[] paths) {
        this(paths, JarLoader.class.getClassLoader());
    }

    public JarLoader(String[] paths, ClassLoader parent) {
        super(getURLs(paths), parent);
    }

    private static URL[] getURLs(String[] paths) {
        Validate.isTrue(null != paths && 0 != paths.length,
                "jar包路径不能为空.");

        List<String> dirs = new ArrayList<String>();
        for (String path : paths) {
            dirs.add(path);
            JarLoader.collectDirs(path, dirs);
        }

        List<URL> urls = new ArrayList<URL>();
        for (String path : dirs) {
            urls.addAll(doGetURLs(path));
        }

        return urls.toArray(new URL[0]);
    }

    private static void collectDirs(String path, List<String> collector) {
        if (null == path || StringUtils.isBlank(path)) {
            return;
        }

        File current = new File(path);
        if (!current.exists() || !current.isDirectory()) {
            return;
        }

        for (File child : current.listFiles()) {
            if (!child.isDirectory()) {
                continue;
            }

            collector.add(child.getAbsolutePath());
            collectDirs(child.getAbsolutePath(), collector);
        }
    }

    private static List<URL> doGetURLs(final String path) {
        Validate.isTrue(!StringUtils.isBlank(path), "jar包路径不能为空.");

        File jarPath = new File(path);

        Validate.isTrue(jarPath.exists() && jarPath.isDirectory(),
                "jar包路径必须存在且为目录.");

        /* set filter */
        FileFilter jarFilter = new FileFilter() {
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".jar");
            }
        };

        /* iterate all jar */
        File[] allJars = new File(path).listFiles(jarFilter);
        List<URL> jarURLs = new ArrayList<URL>(allJars.length);

        for (int i = 0; i < allJars.length; i++) {
            try {
                jarURLs.add(allJars[i].toURI().toURL());
            } catch (Exception e) {
                throw new RuntimeException("系统加载jar包出错");
            }
        }

        return jarURLs;
    }
}

        JarLoader 封装了获取ClassLoader 对象的方法。在这里我们需要搞清楚几个概念 URI、 URL和URLClassLoader,以方便更好的掌握类加载器的实现。

  • URI :定义了资源的类型或访问协议,而我们的Jar包标识为file:// 类型,表示访问的是本地文件系统中的资源

  • URL:URL 是一种特殊类型的 URI,它不仅标识了资源,还提供了访问该资源的具体位置信息,是一种用于定位和访问互联网资源的完整地址。

  • URLClassLoader:这个类加载器用于从引用JAR文件和目录的url的搜索路径加载类和资源。假定任何jar: scheme URL (请参阅JarURLConnection) 都指向一个JAR文件。任何以 “” 结尾的file: scheme URL都被假定为指向一个目录。否则,假定URL引用将根据需要打开的JAR文件。此类加载器支持从给定URL引用的多版本JAR文件的内容加载类和资源。创建URLClassLoader实例的线程的AccessControlContext将在随后加载类和资源时使用。默认情况下,加载的类仅被授予访问URLClassLoader创建时指定的url的权限。

        简而言之,我们的项目就是通过将实际的插件Jar包路径转化成ClassLoader 类加载器的方式,去取获取插件具体的Class 实例。

3. Plugin 及其父类的实现

3.1  插件父类
public class AbstractJobPlugin implements PluginTemplate {
    @Override
    public void getPluginName() {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public void getPluginVersion() {
        throw new UnsupportedOperationException("Not implemented");

    }
}

        所有的插件的实现必须继承 AbstractJobPlugin这个插件父类,以方便插件提供者调用的时候使用统一插件类型调用方法。

3.2 插件子类
/**
 * 具体插件
 */
@Component
public class Plugin1Provider extends AbstractJobPlugin {

    public void getPluginName() {
        System.out.println("PluginM");
    }


    public void getPluginVersion() {
        System.out.println("1.0.0");
    }
}

        插件子类封装了具体的实现方法。最终用户则会通过ClassLoader 的类加载机制,将类的字节码加载到 Java 虚拟机(JVM)中,使 JVM 能够识别并创建该类的实例或者访问其静态成员等。而后我们调用的时候便可以通过类的多态找到具体的插件实现,然后调用它的方法。

相关文章推荐

Wend看源码-DataX-CSDN博客

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

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

相关文章

Ubuntu boot-repair安装过程

本教程只是在UBuntu系统无法引导才使用的方法&#xff0c;有可能需要提前使用U盘刻录UBuntu镜像去引导。 假如winUBuntu双系统&#xff0c;找不到紫色的UBuntu界面了&#xff0c;请参考&#xff1a;windows10ubuntu双系统开机引导界面不见的解决办法_双系统不出现引导界面-CSD…

苹果公司即将为iPhone和智能家居改用自主研发的蓝牙和Wi-Fi芯片

美股快讯&#xff1a;苹果公司即将为iPhone和智能家居改用自主研发的蓝牙和Wi-Fi芯片 苹果公司计划从明年开始在其设备上改用国产芯片进行蓝牙和Wi-Fi连接&#xff0c;此举将逐步淘汰目前由博通提供的部分部件。这种代号为Proxima的芯片已经开发了数年&#xff0c;现在计划用于…

汽车车牌识别数据集,支持YOLO,COCO,VOC格式的标注,8493张图片,可识别多种环境下的车牌

汽车车牌识别数据集&#xff0c;支持YOLO&#xff0c;COCO&#xff0c;VOC格式的标注&#xff0c;8493张图片&#xff0c;可识别多种环境下的车牌 数据集分割 训练组82&#xff05; 6994图片 有效集12&#xff05; 999图片 测试集6% 500图片 预处理 自动…

Towards Frame Rate Agnostic Multi-object Tracking—迈向帧率无关的多目标跟踪

Towards Frame Rate Agnostic Multi-object Tracking—迈向帧率无关的多目标跟踪 发表在IJCV 2023年 作者&#xff1a;Weitao Feng, Lei Bai, Yongqiang Yao, Fengwei Yu & Wanli Ouyang 研究目标&#xff1a;多目标跟踪的帧率无关性研究 IJCV 在计算机视觉领域的影响力非常…

day11 性能测试(3)——Jmeter 断言+关联

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、复习 2、查看结果树 多个http请求原因分析 3、作业 4、Jmeter断言 4.1 响应断言 4.1.1 案例 4.1.2 小结 4.2 json断言 4.2.1 案例 4.2.2 小结 4.3 断言持续时间 4.3.1 案例 4.3.2 小结 4.…

高项 - 信息化发展

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 博文更新参考时间点&#xff1a;2024-11-09 高项 - 章节与知识点汇总&#xff1a;点击跳转 文章目录 高项 - 信息化发展信息与信息化信息信息系统信息化 现代化基础设施新型基础设施建设工业互联网车联网 现代化创…

PostgreSQL中事件触发器Event Trigger

在PostgreSQL中&#xff0c;事件触发器&#xff08;Event Trigger&#xff09;是一种特殊的触发器类型&#xff0c;它允许你在特定的数据库系统事件发生时执行特定的操作。与普通的触发器不同&#xff0c;事件触发器并不与特定的表或视图相关联&#xff0c;而是与数据库级别的全…

移远EC200A-CN的OPENCPU使用GO开发嵌入式程序TBOX

演示地址&#xff1a; http://134.175.123.194:8811 admin admin 演示视频&#xff1a; https://www.bilibili.com/video/BV196q2YQEDP 主要功能 WatchDog 1. 守护进程 2. OTA远程升级 TBOX 1. 数据采集、数据可视化、数据上报&#xff08;内置Modbus TCP/RTU/ASCII,GPS协…

深度学习中的多通道卷积与偏置过程详解

目录 ​编辑 多通道卷积的深入理解 &#x1f50d; 卷积核的多维特性 &#x1f30c; 卷积操作的细节 &#x1f527; 多通道卷积的优势 &#x1f31f; 偏置过程的深入理解 &#x1f3af; 偏置的两种实现方式 &#x1f6e0;️ 偏置的作用与重要性 &#x1f308; 多通道卷…

在服务器自主选择GPU使用

比如说&#xff0c;程序使用第 2 张显卡&#xff08;从 0 开始计数&#xff09;。它的作用是告诉系统和深度学习框架&#xff08;如 PyTorch 或 TensorFlow&#xff09;只可见某些 GPU。 export CUDA_VISIBLE_DEVICES1 然后再查看当前使用的显卡&#xff1a; echo $CUDA_VIS…

Vue3+TypeScript+AntVX6实现Web组态(从技术层面与实现层面进行分析)内含实际案例教学

摘要 用Vue3+TypeScript+AntVX6实现Web组态(从技术层面与实现层面进行分析),包含画布创建、节点设计、拖拽实现(实际案例)、节点连线、交互功能,后续文章持续更新。 注:本文章可以根据目录进行导航 文档支持 AntVX6使用文档 https://x6.antv.antgroup.com/tutorial…

jmeter CLI Mode 传参实现动态设置用户数

一.需求 CLI 运行模式下每次运行想要传入不同的用户数&#xff0c;比如寻找瓶颈值的场景&#xff0c;需要运行多次设置不同的用户数。 二.解决思路 查看官方API Apache JMeter - Users Manual: Getting Started api CLI Mode 一节中提到可以使用如下参数做属性的替换&#…

SpringCloudAlibaba教程之注册中心Nacos

目录 概念 架构 设计原则 架构分层 用户层 业务层 内核层 插件 单机部署 1.下载安装包 2.安装nacos 3.启动nacos 快速开始 1.添加Maven依赖 2.添加配置 3.启动 集群部署 搭建步骤 1.搭建数据库&#xff0c;初始化数据库表结构 2.配置nacos 3.启动nacos集群…

Python大数据可视化:基于python的电影天堂数据可视化_django+hive

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 电影数据 看板展示 我的信息 摘要 电影天堂数据可视化是…

Unity屏幕截图、区域截图、读取图片、WebGL长截屏并下载到本地jpg

Unity屏幕截图、区域截图、读取图片、WebGL长截屏并下载到本地jpg 一、全屏截图并保存到StreamingAssets路径下 Texture2D screenShot;//保存截取的纹理public Image image; //显示截屏的Imagepublic void Jietu(){StartCoroutine(ScrrenCapture(new Rect(0, 0, Screen.width…

Go 语言与时间拳击理论下的结对编程:开启高效研发编程之旅

一、引言 结对编程作为一种软件开发方法&#xff0c;在提高代码质量、增强团队协作等方面具有显著优势。而时间拳击理论为结对编程带来了新的思考角度。本文将以 Go 语言为中心&#xff0c;深入探讨时间拳击理论下的结对编程。 在当今软件开发领域&#xff0c;高效的开发方法和…

ArcGIS MultiPatch数据转换Obj数据

文章目录 ArcGIS MultiPatch数据转换Obj数据1 效果2 技术路线2.1 Multipatch To Collada2.2 Collada To Obj3 代码实现4 附录4.1 环境4.2 一些坑ArcGIS MultiPatch数据转换Obj数据 1 效果 2 技术路线 MultiPatch --MultipatchToCollada–> Collada --Assimp–> Obj 2.…

HTML、CSS表格的斜表头样式设置title 画对角线

我里面有用到layui框架的影响&#xff0c;实际根据你自己的框架来小调下就可以 效果如下 上代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…

DMA(Direct Memory Access):直接内存访问

DMA&#xff08;Direct Memory Access&#xff09;&#xff1a;直接内存访问 一、传统CPU存取数据 CPU不直接存取外设的原因主要有两点&#xff1a; 速度差异&#xff1a;CPU的处理速度远高于外设&#xff0c;无法直接同步。格式多样性&#xff1a;外设数据格式种类繁多&…

C语言-排序

常见的排序算法分为以下四种&#xff0c;插入排序&#xff0c;选择排序&#xff0c;交换排序&#xff0c;归并排序。 一、插入排序 (一)直接插入排序 直接插入排序&#xff0c;将一段数组看做被分成已排序序列和未排序序列&#xff0c;排序过程是从未排序序列的元素开始&…