java自定义类加载器的实现

2019-02-25  本文已影响0人  hebiris

前言

java类的加载是将.class文件加载进入内存中,将类的数据结构放置在方法区内,然后在堆区创建class类的对象(垃圾回收)。栈区用来存放局部变量跟基本数据(方法结束后清空)。

类的加载用到了类的加载器,加载器可以是java虚拟机中自带的,也可以是用户自定义的。

子类加载器在加载过程中,先让父类加载器尝试加载,如果父类加载器尝试失败,子类加载器才接着尝试加载,如果加载失败会爆出ClassNotFound的错误。

一个子类加载器只有一个父类加载器。


步入正题

因为安全性更高一点,子类加载器是没法加载父类加载器加载的类的,父类加载过的类不需要重复加载,这样防止恶意代码冒充java核心库,来兴风作浪。

父类跟子类更像是包装关系,子类的命名空间中的类均可被父类加载,但是父类的类是没法被子类加载的。

思路:继承ClassLoader,覆盖核心方法findClass,定义私有方法loadClass将其转化成二进制数据流,从而加载到Class类。

代码部分

MyClassLoader.java


public class MyClassLoader extends ClassLoader {

    private String path="d:\\";

    private final String fileType = ".class";

    // 类加载器名字

    private String name = null;

    public MyClassLoader(String name){

        super();

        this.name = name;

    }

    public MyClassLoader(ClassLoader parent,String name){

        super(parent);

        this.name = name;

    }

    // 调用getClassLoader()时返回此方法,如果不重载,则显示MyClassLoader的引用地址

    public String toString(){

        return this.name;

    }

    // 设置文件加载路径

    public void setPath(String path){

        this.path = path;

    }

    protected Class findClass(String name) throws ClassNotFoundException{

        byte[] data = loadClassData(name);

        // 参数off代表什么?

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

    }

    // 将.class文件读入内存中,并且以字节数形式返回

    private byte[] loadClassData(String name) throws ClassNotFoundException{

        FileInputStream fis = null;

        ByteArrayOutputStream baos = null;

        byte[] data = null;

        try{

            // 读取文件内容

            name = name.replaceAll("\\.","\\\\");

            System.out.println("加载文件名:"+name);

            // 将文件读取到数据流中

            fis = new FileInputStream(path+name+fileType);

            baos = new ByteArrayOutputStream();

            int ch = 0;

            while ((ch = fis.read()) != -1){

                baos.write(ch);

            }

            data = baos.toByteArray();

        }catch (Exception e){

            throw new ClassNotFoundException("Class is not found:"+name,e);

        }finally {

            // 关闭数据流

            try {

                fis.close();

                baos.close();

            }catch (Exception e){

                e.printStackTrace();

            }

        }

        return data;

    }

  public static void main(String[] args) throws Exception {

        MyClassLoader loader1 = new MyClassLoader("loader1");

        // 获取MyClassLoader加载器

        System.out.println("MyClassLoader 加载器:" + MyClassLoader.class.getClassLoader());

        // 设置加载类查找文件路径

        loader1.setPath("D:\\workspace\\bac5\\java\\");

        loader(loader1);

    }

    private static void loader(MyClassLoader loader) throws Exception {

        // MyClassLoader 由系统加载器加载,跟test是不同的加载器,会出现NOClassDefFoundError

        // 如果类中有package,则加载类名时,需要写全,不然找不到该类,会出现NOClassDefFoundError

        Class test = loader.loadClass("test");

        Object test1 = test.newInstance();

        // test test2 = (test) test1;

        // 如果MyClassLoader与test非同一个加载器,访问时,需要用到反射机制

        Field v1 = test.getField("v1");// java反射机制,取test中的静态变量

        System.out.println("被加载出来的类是:"+v1.getInt(test1));

// 卸载,将引用置空

        test = null;

        test1 = null;

        // 重新加载

        test = loader.loadClass("test");

        test1 = test.newInstance();

        System.out.println("test1 hashcode:"+test1.hashCode());

    }

}

test.java


public class test {

    public static int v1= 1;

    public test(){

        System.out.println("调用到了test");

        System.out.println("test加载器为:"+this.getClass().getClassLoader());

    }

}

注意:

1.package,如果加载的类有package文件,则查找时,class的名字应该为包名.类名,不然会报NOClassDefFoundError。

2.如果两类不是同一个加载器加载,强制转换,会报NOClassDefFoundError错误。

3.不是同一加载器加载的两类如果想访问对方,则需要使用反射机制

心得

在编写代码的过程中,同一个包下的文件,总是会报NOClassDefFoundError。直至看到含包名文件的存储方式,才发现需要将loadClass的文件名称替换成包名.类名的形式,才能正确加载到类。

上一篇下一篇

猜你喜欢

热点阅读