Datax之自定义线程ClassLoader

前言:

学习源码好处多多。最近在学习datax的源码时,碰到一个骚操作,自定义实现URLClassLoader加载器,可以加载指定路径下的jar包。

这个很普通,没什么大不了。关键在于后面的操作,将这个自定义的URLClassLoader设置给创建的Thread中,这样就实现了不同的Thread在执行任务时,使用到了不同的URLClassLoader加载的不同的jar包。我们先一起来看下。

1.自定义URLClassLoader

/**
 * 提供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);
    }

    // 直接通过getURLs()方法将指定path路径下的jar包全部扫描到到当前ClassLoader
    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() {
            @Override
            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 DataXException.asDataXException(
                        FrameworkErrorCode.PLUGIN_INIT_ERROR,
                        "系统加载jar包出错", e);
            }
        }

        return jarURLs;
    }
}

代码不算复杂,就是直接继承URLClassLoader,然后扫描指定urls路径下的所有jar包,加载进来即可

2.设置线程ClassLoader

	/**
     * TaskExecutor是一个完整task的执行器
     * 其中包括1:1的reader和writer
     */
class TaskExecutor {
    private Thread readerThread;
    private Thread writerThread;
    
    public TaskExecutor(Configuration taskConf, int attemptCount) {
        ...
        // 生成writerThread    
        writerRunner = (WriterRunner) generateRunner(PluginType.WRITER);
        this.writerThread = new Thread(writerRunner,
                                       String.format("%d-%d-%d-writer",
                                                     jobId, taskGroupId, this.taskId));
        //通过设置thread的contextClassLoader,即可实现同步和主程序不通的加载器
        this.writerThread.setContextClassLoader(LoadUtil.getJarLoader(
            PluginType.WRITER, this.taskConfig.getString(
                CoreConstant.JOB_WRITER_NAME)));

        // 生成readerThread
        readerRunner = (ReaderRunner) generateRunner(PluginType.READER,transformerInfoExecs);
        this.readerThread = new Thread(readerRunner,
                                       String.format("%d-%d-%d-reader",
                                                     jobId, taskGroupId, this.taskId));
        // 通过设置thread的contextClassLoader,即可实现同步和主程序不通的加载器
        this.readerThread.setContextClassLoader(LoadUtil.getJarLoader(
            PluginType.READER, this.taskConfig.getString(
                CoreConstant.JOB_READER_NAME)));   
    }
}

正常来说,我们在创建Thread时,并不会设置其ClassLoader,直接使用主线程的ClassLoader即可。那么这里为什么要这样使用呢?好处在哪里。

2.1 主动设置Thread ClassLoader优势

首先我们要明白datax所面临的一个现状,它作为一个同步数据库数据的工具,它所面临的源数据库和目标数据库有可能是不同的(源可以是Oracle、mysql...,目标也可以是这些),所以如果我们在主线程中直接加载所有的数据源jar包的话,那么很可能读线程或写线程根本用不到这些jar包,就浪费了相关资源。

所以最好的方式就是,当前线程使用到了哪些jar包就直接只加载这些jar包就可以了,所以才有了这个操作。

总结:

这种线程加载方式对我们有哪些启示呢?

后续我们在做线程执行隔离时,可以考虑使用这种方式,只加载当前线程需要的jar包,这样便有效的实现jar包隔离。

Guess you like

Origin blog.csdn.net/qq_26323323/article/details/121274044
Recommended