通用技术 [java] classloader 加载额外的 jar 包或 class

bauul · 2017年09月19日 · 最后由 bauul 回复于 2017年09月20日 · 2155 次阅读

前提

1. 需要了解 java 可变参数
2. 需要了解 java 反射知识

应用场景

比如允许用户在线提交 jar 包测试,插件化开发,tomcat 等 web 容器,热更新等

java 类加载顺序


每个类加载器有自己的名字空间,对于同一个类加载器实例来说,名字相同的类只能存在一个,并且仅加载一次。不管该类有没有变化,下次再需要加载时,它只是从自己的缓存中直接返回已经加载过的类引用。

首先建一个普通的 java 工程并导出 jar 包

package com.carl.test;

public class TestJar {

    private String hi = "huhu2";

    public String sayHi() {
        System.out.println("sayHi:" + hi);
        return hi;
    }

    public String sayBye(String bye) {
        System.out.println("sayBye:" + bye);
        return bye;
    }

}

使用系统类加载器,加载一个 jar

package com.carl.classloaderdemo;

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

public class MyClassloader {

//  public static void classLoader(File file, String className, String methodName, Object[] args, Class<?>... parameterTypes) {
    public static void classLoader(File file, String className, String methodName, Object[] args, Class<?>[] parameterTypes) {
        try {
            URL url = file.toURI().toURL();
//          URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {url});
            //得到系统类加载器,利用该加载器加载指定路径下的jar包
            URLClassLoader urlClassLoader= (URLClassLoader) ClassLoader.getSystemClassLoader();  
            Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{ URL.class});                                 
            add.setAccessible(true);  
            add.invoke(urlClassLoader, new Object[] {url}); 
            urlClassLoader.loadClass(className);
            Class<?> c = urlClassLoader.loadClass(className);
            //列出所有方法
//          Method[] methods = c.getMethods();
//          for (Method m : methods) {
//              System.out.println(m.getName());
//          }
            if (args == null) {
                c.getMethod(methodName, parameterTypes).invoke(c.newInstance());
            } else {
                c.getMethod(methodName, parameterTypes).invoke(c.newInstance(), args);
            }

//          urlClassLoader.close();

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

    public static void main(String[] args) {
        //无参方法的例子
        classLoader(new File("E:\\ABC\\TestJar.jar"), "com.carl.test.TestJar", "sayHi", null, null);

        //有参方法的例子
        classLoader(new File("E:\\ABC\\TestJar.jar"), "com.carl.test.TestJar", "sayBye", new String[]{"byebye"}, new Class<?>[]{String.class});
    }
}

使用自定义类加载器,加载一个 class

通过编写一个类继承自 ClassLoader,并重写 findClass 方法

package com.carl.classloaderdemo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class MyClassloader2 extends ClassLoader {

    private String fileName;

    public MyClassloader2(String fileName) {
        this.fileName = fileName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File(fileName);
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            while ((len = fis.read()) != -1) {
                bos.write(len);
            }

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

            return defineClass(name, data, 0, data.length);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return super.findClass(name);
    }

}

package com.carl.classloaderdemo;

import java.lang.reflect.Method;

public class ClassloaderTest {

    public static void main(String[] args) {
        MyClassloader2 myClassloader = new MyClassloader2("E:\\devworkspace\\170807\\TestJar\\bin\\com\\carl\\test\\TestJar.class");
        try {
            //加载class文件
            Class<?> c = myClassloader.loadClass("com.carl.test.TestJar");
            if(c != null){
                try {
                    Object obj = c.newInstance();
//                    Method method = c.getDeclaredMethod("sayHi", null);
                    Method method = c.getMethod("sayHi", null);
                    //通过反射调用Test类的say方法
                    method.invoke(obj, null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

我的理解

当我们的工具已经运行起来后,想要再去调用额外的 jar 包的时候,就需要使用 classloader 去加载这些 jar 包,并通过反射调用

参考

  1. 《java 核心技术 卷 2》
  2. https://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/index.html
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

这块的知识点很重要,你写的太笼统了,可以把场景再明确些。你是为了动态执行测试用例是吧

一方面是学习 classloader 的概念,另外在工作中有使用到 “动态执行测试用例”,其他的比如一些在线学习编程的网站,如果是 java 的编程的话应该是将代码片段提交到后台,通过 classloader 相关的方法动态编译执行的,当然也是可以写到文件中通过命令行的方式去编译运行的。
找时间再丰富一下本贴,感谢

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册