ClassLoader:Java实现从指定路径加载jar包/class文件

项目背景:

开发平台在 yarn客户端 提交机上 提交Flink任务时,需要加载 非结构化数据解析器jar 和 自定义函数jar

方案:

考虑到 java 自带的 ClassLoader 无法灵活的添加某路径下的类,考虑以下三种方式实现:

  1. AppClassLoader 继承自 URLClassLoader,以反射的方式将 addURL 方法设置为 public,以添加自定义路径为 classpath
  2. 自定义类加载器实现 URLClassLoader,将 addURL 由 protected 变更为 public,以方便添加自定义路径为 classpath
  3. 自定义类加载器实现 ClassLoader,将本地类文件读入byte[],调用 defineClass 方法生成类

demo说明:

  1. 方案一和方案二本质上是一样的,此处只实现方案一

    demo类:ExtClasspathLoader.java

  2. 方案三扩展性更强,不仅仅可以实现加载类,也可实现加载资源文件。但是使用各个类时,必须手动引入,而方案一和方案二可以把路径上的所有类都自动引入

    demo类:DiskClassLoader.java

参考:https://blog.csdn.net/briblue/article/details/54973413


ExtClasspathLoader.java

package com.wj.classloader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 根据properties中配置的路径把jar和配置文件加载到classpath中。
* 此工具类加载类时使用的是SystemClassLoader,如有需要对加载类进行校验,请另外实现自己的加载器 * */ public class ExtClasspathLoader { private static final Logger LOG = LoggerFactory.getLogger(ExtClasspathLoader.class); private static final String JAR_SUFFIX = ".jar"; private static final String ZIP_SUFFIX = ".zip"; /** * URLClassLoader的addURL方法 */ private static Method addURL = initAddMethod(); /** * Application Classloader */ private static URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); /** * 初始化addUrl 方法. * * @return 可访问addUrl方法的Method对象 */ private static Method initAddMethod() { try { Method add = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); add.setAccessible(true); return add; } catch (Exception e) { throw new RuntimeException(e); } } /** * 通过filepath加载文件到classpath。 * * @param file 文件路径 */ private static void addURL(File file) throws Exception { addURL.invoke(classloader, file.toURI().toURL()); } /** * load Resource by Dir * * @param file dir */ public static void loadResource(File file) throws Exception { // 资源文件只加载路径 LOG.info("load Resource of dir : " + file.getAbsolutePath()); if (file.isDirectory()) { addURL(file); File[] subFiles = file.listFiles(); if (subFiles != null) { for (File tmp : subFiles) { loadResource(tmp); } } } } /** * load Classpath by Dir * * @param file Dir */ public static void loadClasspath(File file) throws Exception { LOG.info("load Classpath of dir : " + file.getAbsolutePath()); if (file.isDirectory()) { File[] subFiles = file.listFiles(); if (subFiles != null) { for (File subFile : subFiles) { loadClasspath(subFile); } } } else { if (file.getAbsolutePath().endsWith(JAR_SUFFIX) || file.getAbsolutePath().endsWith(ZIP_SUFFIX)) { addURL(file); } } } }

方法 loadClasspath 和 loadResource 分别可以加载 指定路径 下的jar 和 资源文件,此方案使用最为方便。

具体使用方法,可查看最后的测试demo。

ExtClasspathLoader.java

package com.wj.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;

/**
 * 从本地加载文件
 */
public class DiskClassLoader extends ClassLoader {
    private String mLibPath;

    public DiskClassLoader(String path) {
        mLibPath = path;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        String fileName = getFileName(name);

        File file = new File(mLibPath, fileName);

        try {
            FileInputStream is = new FileInputStream(file);

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            try {
                while ((len = is.read()) != -1) {
                    bos.write(len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            byte[] data = bos.toByteArray();
            is.close();
            bos.close();

            return defineClass(name, data, 0, data.length);

        } catch (IOException e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    /**
     * 获取要加载 的class文件名
     *
     * @param name String
     * @return String
     */
    private String getFileName(String name) {
        int index = name.lastIndexOf('.');
        if (index == -1) {
            return name + ".class";
        } else {
            return name.substring(index + 1) + ".class";
        }
    }

    @Override
    public URL getResource(String name) {
        return super.getResource(name);
    }
}

此方式适合加载指定路径上的某个文件,具体使用方法,可查看最后的测试demo。

使用示例

package com.wj.classloader;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 测试 ExtClasspathLoader 和 DiskClassLoader
 */
public class Main {

    private static final String PATH = "E:\\IDEJ_workspace\\wj_proj\\java-se\\src\\main\\java\\com\\wj\\classloader\\Person.java";

    private static final String CLASS_FULL_NAME = "com.wj.classloader.Person";

    public static void main(String[] args) throws Exception {
        System.out.println("测试 DiskClassLoader.");
        testDiskClassLoader();

        System.out.println("---------------------");
        System.out.println("测试 ExtClasspathLoader.");

        testExtClasspathLoader();

    }

    private static void testExtClasspathLoader() throws Exception {
        ExtClasspathLoader.loadClasspath(new File(PATH));

        Object obj = Class.forName(CLASS_FULL_NAME).newInstance();

        System.out.println(obj);
    }

    private static void testDiskClassLoader() {
        // 创建自定义classloader对象
        DiskClassLoader diskLoader = new DiskClassLoader(PATH);
        try {
            // 加载class文件
            Class c = diskLoader.loadClass(CLASS_FULL_NAME);

            if (c != null) {
                try {
                    Object obj = c.newInstance();
                    Method method = c.getDeclaredMethod("say", null);
                    //通过反射调用Test类的say方法
                    method.invoke(obj, null);
                    System.out.println(obj);
                } catch (InstantiationException | IllegalAccessException
                        | NoSuchMethodException
                        | SecurityException |
                        IllegalArgumentException |
                        InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Person.java

package com.wj.classloader;

/**
 * 测试类 Person
 */
public class Person {

    public void say() {
        System.out.println("I am wj.");
    }

}

测试结果如下:
ClassLoader:Java实现从指定路径加载jar包/class文件_第1张图片
结论:DiskClassLoader 和 ExtClasspathLoader 方式都能实现加载Person类,且都能正常使用

你可能感兴趣的:(#,base,JAVA)