Java类加载器

294 阅读5分钟

问题:为什么我自定义ClassLoader来加载一个.class文件。可以显示用的自定义classLoader加载的。但是加载项目中的.java文件。就提示用AppClassLoaser加载的。

我猜测哈,是编译的时候,IDE就给实现了类加载,因此对于.java文件。会用AppClassLoader进行加载。而对于我们自己写的.class文件。因为是动态加载的,所以可以使用自定义的类加载器了。

  • 1.系统ClassLoader,只能加载.java文件。加载时候,传递的是类的全路径名称,没有.class后缀。不能加载.class文件。如果是自定义的ClassLoader,正好相反,只能加载.class文件,并且是类文件所在的正确路径。

  • 2.所谓类加载器,就是真正执行类加载的加载器。如果用自定义的ClassLoader进行加载,那么就自己通过IO的方式,来进行类的加载。

  • 3.系统的加载器,只能加载已经编译期有的类;换句话说,动态加载服务端或者是本地文件系统的类,必须是.class格式的,同时必须是自定义类加载器了。

public static void main(String[] args) throws Exception{
    //动态加载类,获取当前类的Class对象
    Class student = Class.forName("com.justalk.javademo.loader.Student");

    //获取Student类名称为outName地方法
    Method name = student.getMethod("outName");
    //调用outName方法
    name.invoke(student.newInstance()); //通过实例化的对象,调用无参数的方法

    //获取类中名称为Student的方法,String,class是参数类型(调用含有参数的方法,参数的类型必须为包装类)
    Method age = student.getMethod("outAge", Integer.class);//注意参数不是String
    //调用Student方法,其中27是方法传入的参数值
    age.invoke(student.newInstance(),26);//通过对象,调用有参数的方法
    System.out.println("=======================Class.forName加载类完成=======================");

    //这种方式不会进行类初始化
    Class student1 = ClassLoader.getSystemClassLoader().loadClass("com.justalk.javademo.loader.Student");
    Method name1 = student1.getMethod("outName");
    //调用outName方法
    name1.invoke(student1.newInstance()); //通过实例化的对象,调用无参数的方法
    System.out.println("=======================ClassLoader加载类完成=======================");
}

下面的几张图是我通过UT进行类加载器实验。了解类加载器和文件后缀,文件路劲的关系。

image.png

image.png

image.png

对于自定义ClassLoader,通过IO流读取.class文件,反序列化后,生成Class对象的操作,需要重写findClass()方法。 具体调用流程是,loadClass()-》findClass()-》自己写的反序列化的方法。

实现自定义类加载器的三步:

  • 1.继承ClassLoader
  • 2.重写findClass()方法
  • 3.调用defineClass()方法

一个基本的自定义类加载器代码如下:

package com.mobile.waikuai;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {


    private String path = "src/main/java/";    //默认加载路径

    private String name;                    //类加载器名称

    private final String filetype = ".class"; //文件类型


    public MyClassLoader(String name) {
        super();
        this.name = name;
    }

    public MyClassLoader(ClassLoader parent, String name) {
        super(parent);
        this.name = name;
    }


    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // TODO Auto-generated method stub
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }

    /**
     * 在该方法中,有双亲委托机制的运行。但是子类的执行,还是要走到子类重写的findClass方法
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return super.loadClass(name, resolve);

    }

    private byte[] loadClassData(String name) {
        byte[] data = null;
        InputStream in = null;
        name = name.replace('.', '/');
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            in = new FileInputStream(new File(path + name + filetype));
            int len = 0;
            while (-1 != (len = in.read())) {
                out.write(len);
            }
            data = out.toByteArray();
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFoundException !!!");
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("IOException !!!");
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return this.name;
    }

}

或者这套代码

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException(name);
            } else { //defineClass方法将字节码转化为类 
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name) { // 从自定义路径中加载指定类,返回类的字节码文件 
        InputStream in = null;
        ByteArrayOutputStream out = null;
        String path = "/Users/yeyonghao/" + name + ".class";
        try {
            in = new FileInputStream(path);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One", true, customClassLoader);
            Object obj = clazz.newInstance(); // cn.xpleaf.coding.c4.CustomClassLoader@610455d6 
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

类加载的双亲委托机制的原理如下:

先判断当前加载器是否已加载目标类,如已加载直接返回,如未加载则委托父加载器加载;

父类加载器拿到目标类后先判断自己是否已加载目标类,如已加载则返回该类,如未加载再委托自己的父  加载器加载。如此递归循环,直到当前类加载器没有父加载器(即当前加载器为引导类加载器),这时交由引导类加载器去加载;

引导类加载器加载目标类时现在自己的类路径下找寻目标类文件,如找到了
则直接加载该类,如未找到则向下委托子类加载器去加载。直到找到该类文件且成功加载为止

image.png

对应的理论如下:

image.png

最后,如果要跳出双亲委托机制,则直接调用自定义类加载器的findClass()方法。从对应的android内部或者外部存储路径来进行 .class文件的加载。 如果一个目录既有某个要加载文件的.java文件,又有.class文件。那么通过loaderClass()方法,将会通过AppClassLoader来加载。通过直接调用findClass()方法,将会直接通过自定义的ClassLoader进行类加载了。从而跳出了双亲委托机制。

总结就是:

  • 1.如果想正常加载已有的Class文件,则可以使用系统的类加载器;
  • 2.如果想加载外部的文件,使用自定义ClassLoader,然后调用loadClass()方法,会遵循双亲委托机制;
  • 3.如果想跳过双亲委托机制,使用自定义ClassLoader,直接重写并调用findClass()方法。