Java破坏双亲委派实现自定义加载器加载不同版本类

借鉴datax的一些代码,添加破坏双亲委派功能,实现在JVM中自定义加载器加载同包名、类名不同版本的类文件

1、创建类加载器切换类,使用Thread的ClassLoaderContext控制

/**
 *
 * 为避免jar冲突,比如hbase可能有多个版本的读写依赖jar包
 * 就需要脱离当前classLoader去加载这些jar包,执行完成后,又退回到原来classLoader上继续执行接下来的代码
 */
public final class ClassLoaderSwapper {
    private ClassLoader storeClassLoader = null;

    private ClassLoaderSwapper() {
    }

    public static ClassLoaderSwapper newCurrentThreadClassLoaderSwapper() {
        return new ClassLoaderSwapper();
    }

    /**
     * 保存当前classLoader,并将当前线程的classLoader设置为所给classLoader
     *
     * @param
     * @return
     */
    public ClassLoader setCurrentThreadClassLoader(ClassLoader classLoader) {
        this.storeClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(classLoader);
        return this.storeClassLoader;
    }

    /**
     * 将当前线程的类加载器设置为保存的类加载
     * @return
     */
    public ClassLoader restoreCurrentThreadClassLoader() {
        ClassLoader classLoader = Thread.currentThread()
                .getContextClassLoader();
        Thread.currentThread().setContextClassLoader(this.storeClassLoader);
        return classLoader;
    }
}

2、自定义Jar包类加载器,重写loadClass方法,将双亲委派,改为逆向双亲委派

/**
 * 提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。
 * 破坏双亲委派机制,改为逆向
 * */
public class JarLoader extends URLClassLoader {
    private static ThreadLocal threadLocal = new ThreadLocal<>();
    private URL[] allUrl;
    public JarLoader(String[] paths) {
        this(paths, JarLoader.class.getClassLoader());
    }

    public JarLoader(String[] paths, ClassLoader parent) {
        super(getURLs(paths), parent);
        //暂时先这样
        allUrl = threadLocal.get();
    }

    private static URL[] getURLs(String[] paths) {
        if (null == paths || 0 == paths.length) {
            throw new RuntimeException("jar包路径不能为空.");
        }

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

        List urls = new ArrayList();
        for (String path : dirs) {
            urls.addAll(doGetURLs(path));
        }
        URL[] urls1 = urls.toArray(new URL[0]);
        threadLocal.set(urls1);
        return urls1;
    }

    private static void collectDirs(String path, List collector) {
        if (null == path || "".equalsIgnoreCase(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 doGetURLs(final String path) {
        if (null == path || "".equalsIgnoreCase(path)) {
            throw new RuntimeException("jar包路径不能为空.");
        }
        File jarPath = new File(path);

        if (!jarPath.exists() || !jarPath.isDirectory()) {
            throw new RuntimeException("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 jarURLs = new ArrayList(allJars.length);

        for (int i = 0; i < allJars.length; i++) {
            try {
                jarURLs.add(allJars[i].toURI().toURL());
            } catch (Exception e) {
                throw new RuntimeException("系统加载jar包出错", e);
            }
        }
        return jarURLs;
    }
    //破坏双亲委派模型,采用逆向双亲委派
    @Override
    public Class loadClass(String name) throws ClassNotFoundException {
        if (allUrl != null) {
            String classPath = name.replace(".", "/");
            classPath = classPath.concat(".class");
            for (URL url : allUrl) {

                byte[] data = null;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream is = null;
                try {
                    File file = new File(url.toURI());
                    if (file != null && file.exists()) {
                        JarFile jarFile = new JarFile(file);
                        if (jarFile != null) {
                            JarEntry jarEntry = jarFile.getJarEntry(classPath);
                            if (jarEntry != null) {
                                is = jarFile.getInputStream(jarEntry);
                                int c = 0;
                                while (-1 != (c = is.read())) {
                                    baos.write(c);
                                }
                                data = baos.toByteArray();
                                return this.defineClass(name, data, 0, data.length);
                            }
                        }

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
        return super.loadClass(name);
    }


}

3、测试使用

@RunWith(JUnit4.class)
public class JarClassloaderTests {
    String jar1_3 = "C:\\Users\\Administrator\\.gradle\\caches\\modules-2\\files-2.1\\com.iscas\\base\\1.3-RELEASE\\f068847d9148a666f6c4c74cd4c8cec6ee41fda5";
    String jar1_4 = "C:\\Users\\Administrator\\.gradle\\caches\\modules-2\\files-2.1\\com.iscas\\base\\1.4-RELEASE\\9204c31cae18a2b4aef592e2bd7805d133da71a6";
    @Test
    public void test() throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, ClassNotFoundException {
        System.out.println("=========================开始第一次测试,读取指定jar包1.3版本:=========================");
        JarLoader jarLoader = new JarLoader(new String[]{jar1_3});
        ClassLoaderSwapper classLoaderSwapper = ClassLoaderSwapper.newCurrentThreadClassLoaderSwapper();
        classLoaderSwapper.setCurrentThreadClassLoader(jarLoader);
        Class aClass = Thread.currentThread().getContextClassLoader().loadClass("com.iscas.test.base.StringUtils");
        classLoaderSwapper.restoreCurrentThreadClassLoader();
        Object o = aClass.newInstance();
        Method isEmptyMethod = aClass.getDeclaredMethod("isEmpty", String.class);
        Object invoke = isEmptyMethod.invoke(o, "ewe");


        System.out.println("=========================开始第二次测试,读取指定jar包1.4版本:=========================");
        JarLoader jarLoader2 = new JarLoader(new String[]{jar1_4});
        classLoaderSwapper.setCurrentThreadClassLoader(jarLoader2);
        Class aClass2 = Thread.currentThread().getContextClassLoader().loadClass("com.iscas.test.base.StringUtils");
        classLoaderSwapper.restoreCurrentThreadClassLoader();
        Object o2 = aClass2.newInstance();
        Method isEmptyMethod2 = aClass2.getDeclaredMethod("isEmpty", String.class);
        Object invoke2 = isEmptyMethod2.invoke(o2, "ewe");
    }
}

4、得到结果,带版本号的输出是我随便写的StringUtils.isEmpty函数的System.out

=========================开始第一次测试,读取classpath下默认加载的StringUtils:=========================
这是1.4版本的测试
=========================开始第二次测试,读取指定jar包1.3版本:=========================
这是1.3版本的测试
=========================开始第二次测试,读取指定jar包1.4版本:=========================
这是1.4版本的测试

Process finished with exit code 0

你可能感兴趣的:(java)