示例代码
DataXPluginExample: DataX 项目的plugin 机制学习https://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博客