美文网首页
java自定义类加载器的实现

java自定义类加载器的实现

作者: hebiris | 来源:发表于2019-02-25 11:33 被阅读0次

    前言

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

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

    • java自带虚拟机

      • 根(bootstrap)类加载器
        没有继承ClassLoader,故调用ClassLoader.getParent()是null。是扩展类加载器的父 类。专门加载虚拟机的类。
      • 扩展类加载器
        加载java.ext.dirs指定的类,也可加载jre\lib\ext目录下的类。如Object类。是系统类加载器的父类。
      • 系统类加载器(应用类加载器)
        从classpath,或者java.class.path所指定的目录下加载类,是用户自定义类加载器的默认父类加载器,用户自定义加载器指定父类加载器方法new ClassLoader(parent,name).
    • 父类委托加载

    子类加载器在加载过程中,先让父类加载器尝试加载,如果父类加载器尝试失败,子类加载器才接着尝试加载,如果加载失败会爆出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的文件名称替换成包名.类名的形式,才能正确加载到类。

    相关文章

      网友评论

          本文标题:java自定义类加载器的实现

          本文链接:https://www.haomeiwen.com/subject/asvwyqtx.html