Java中的ClassLoader 动态加载机制
阅读原文时间:2021年04月20日阅读:1

前言:

Android中的动态加载机制能更好的优化我们的应用,同时实现动态的更新,这就便于我们管理我们的应用,通过插件化来减轻我们的内存以及CPU消耗,在不发布新版本的情况下能更新某些模块。

当然这里要说的并不是android中的动态加载机制,而是java中的ClassLoader动态加载我们的class,虽然android是基于Dalvik,但是先了解java中JVM怎么来加载我们的class的对于我们以后了解Android中的动态加载机制会有很大的帮助。

于是乎在网上查阅了很多关于类加载的文章了解JVM是如何通过ClassLoader来加载我们的class的。

JVM中的ClassLoadr:

java为我们提供了3个不同的ClassLoadr,分别是:

1.BootstrapClassLoader---主要负责加载java中的核心类库。是用C++代码实现的,在java虚拟机启动后初始化。

2.ExtensionClassLoader---主要负责加载java中的扩展类库。

3.AppClassLoader---用于java我们应用下的classpath中的class以及我们的jar。

JVM通过这些ClassLoadr把我们的class转换成字节码文件存贮到内存中,然后创建一个Class用特定的数据结构来封装他们,接着我们就能通过这个Class来访问其中的方法以及变量了。(这不就是我们的反射机制么,通过Class对象来获取其中的方法或者变量),当然JVM还有个验证的过程,只有通过javac编译来的class才能被ClassLoader所加载。

而ClassLoader是通过双亲委托模式来加载我们的class,就是先通过父类的ClassLoader来加载我们的class,如果父类加载失败,则通过我们的子ClassLoader来加载我们的class。(这里的父类与子类并不是集成关系)

我们可以通过URLClassLoader来动态加载我们的class也可以通过继承ClassLoader来实现我们的动态加载。而我们的ExtensionClassLoader与AppClassLoader都是继承自URLClassLoader。我这里是通过继承一个ClassLoader来实现的,通过获取Class的字节码文件来生成我们的Class,然后通过反射机制来调用我们类中的方法。

ClassLoader的扩展方法介绍:

loadClass 通过指定的二进制名来加载类(这里要把我们的包路径传过去,例如"com.ljx.test.Test")

defineClass 将byte数据转换成我们的Class类的实例

findloadClass 如果某个类加载器已经加载过这个class,则返回该类

…..还有很多这里就不一一例举了。

Test.java 先来创建需要加载的class,路径是F:Test.java,然后通过javac来生成我们的Test.class

这就是我们的Test的代码

MyClassLoader.java 通过继承ClassLoader来创建我们自己的ClassLoader,来加载Test.class

public class MyClassLoader extends ClassLoader {

    private byte[] results;

    public MyClassLoader(String pathName) {
        //拿到class转成的字节码文件
        results = loadClassFile(pathName);
    }

    public static void main(String[] args) {
        //初始化我们的classloader,同时拿到class所转成的字节码文件
        MyClassLoader classLoader = new MyClassLoader("F:\\Test.class");
        try {
            //这里要把包路径传入进去
            Class<?> clazz = classLoader.loadClass("com.ljx.yyy.Test");
            Object o = clazz.newInstance();

            //通过反射机制调用我们的Test.java中的printToString方法
            Method method = clazz.getMethod("printToString");
            method.invoke(clazz.newInstance());
            System.out.println(o.getClass().getClassLoader().toString());

            Method[] methods = clazz.getMethods();
            for (int i = 0; i < methods.length; i++) {
                //获取类中的方法名字
                String methodName = methods[i].getName();
                System.out.println("MethodName : " + methodName);
                Class<?>[] params = methods[i].getParameterTypes();
                for (int j = 0; j < params.length; j++) {
                    //获取方法中的参数类型
                    System.out.println("ParamsType : " + params[j].toString());
                }
            }

        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    //把我们的class文件转成字节码,用于classloader动态加载
    private byte[] loadClassFile(String classPath) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            FileInputStream fi = new FileInputStream(classPath);
            BufferedInputStream bis = new BufferedInputStream(fi);
            byte[] data = new byte[1024 * 256];
            int ch = 0;
            while ((ch = bis.read(data, 0, data.length)) != -1) {
                bos.write(data, 0, ch);
            }

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return bos.toByteArray();

    }

    @Override
    protected Class<?> loadClass(String arg0, boolean arg1)
            throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(arg0);
        if (clazz == null) {
            if (getParent() != null) {
                try {
                    //这里我们要用父加载器加载如果加载不成功会抛异常
                    clazz = getParent().loadClass(arg0);
                } catch (Exception e) {
                    //我们自定义的类加载器的父类 sun.misc.Launcher$AppClassLoader@c387f44
                    System.out.println("getParent : " + getParent());
                    //父类的父类 sun.misc.Launcher$ExtClassLoader@659e0bfd
                    System.out.println("getParent.getparent : " + getParent().getParent());
                    //父类的父类的父类 为null 也就是我们的Bootstrap ClassLoader 因为它是JVM生成的由C++实现;
                    //所以拿到的是空
                    System.out.println("getParent.getparent.getparent : " + getParent().getParent().getParent());
                    System.out.println("父类ClassLoader加载失败!");
                }
            }

            if (clazz == null) {
                clazz = defineClass(arg0, results, 0, results.length);
            }
        }

        return clazz;
    }
}

先是获取我们在F:Test.class的字节码,然后通过它来得到我们的class,然后我们就能通过这个class来使用Test这个类中的方法,以下是执行main方法之后的结果:

这样,基本上就实现了从本地加载一个class到我们的项目中,是不是感觉很神奇,通过这种方式我们就能动态的加载不在我们项目中的类,例如从网上获取class的byte来动态更新我们的功能模块,或者动态加载jar中的class来实现我们要实现的功能。

代码中很大一部分是参照了网上的一些例子,当然最主要的还是为了要阐述如何通过ClassLoader来动态加载我们需要加载的类,通过ClassLoader来更好的优化我们的应用。了解了JVM如何来加载class能更好的便于我们理解Android中的动态加载技术;由于技术有限,如果上述有不正确的地方希望见谅。