【JVM】深入理解类加载器(二)

1 如何实现一个自定义类加载器

打开ClassLoader的JavaDoc文件(IDEA中ctrl+Q),文档告诉了我们,

  1. 首先要继承ClassLoader类
  2. 定义loadClassData方法,用于去寻找class文件并返回该文件的二进制数据
  3. 重写findClass方法,将二进制class文件加载到内存中并返回Class对象。
  4. 新建类实例 Object object = loader.loadClass(“com.xx.xx”).newInstance()
    在这里插入图片描述

2 实现自定义类加载器

package com.cj.jvm.classloader;

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

public class MyTest12 extends ClassLoader{
	//类加载器名
    private String classLoaderName;
    private final String fileExtension =  ".class";
    // class的文件路径,不包括包名
    private String path;

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

    public MyTest12(){}

    public MyTest12(String classLoaderName){
        super();
        this.classLoaderName = classLoaderName;
    }


    public MyTest12(ClassLoader parent,String  classLoaderName){
        super(parent);
        this.classLoaderName = classLoaderName;

    }


	//将二进制class文件加载到内存中并返回Class对象
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {

        System.out.println("find class invoke");
        System.out.println("classLoaderName --" + this.classLoaderName);
        byte[] data = loadClassData(className);
        return this.defineClass(className,data,0,data.length);
    }

	//用于去寻找class文件并返回该文件的二进制数据
    private byte[] loadClassData(String className){

        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".","\\");


        try{

            is = new FileInputStream(new File(this.path +className+this.fileExtension));
            System.out.println("class file path"+this.path +className+this.fileExtension);
            baos = new ByteArrayOutputStream();

            int ch = 0;

            while (-1 !=(ch=is.read())){
                baos.write(ch);
            }
            data = baos.toByteArray();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                is.close();
                baos.close();

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

        return data;
    }



    public static void main(String[] args) throws Exception {
        //创建了一个类加载器
        MyTest12 loader1 = new MyTest12("loader1");
        //loader1.setPath("D:\\workspace2\\JVMLearn\\target\\classes\\");
        loader1.setPath("C:\\Users\\CJ\\Desktop\\");
        Class<?> clazz = loader1.loadClass("com.cj.jvm.classloader.MyTest1");
        System.out.println("class:" + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println("getClassLoader:"+object.getClass().getClassLoader());
        System.out.println();


        MyTest12 loader2 = new MyTest12("loader2");
        loader2.setPath("C:\\Users\\CJ\\Desktop\\");

        Class<?> clazz2 = loader2.loadClass("com.cj.jvm.classloader.MyTest1");
        System.out.println("class:"+clazz2.hashCode());
        Object object2 = clazz2.newInstance();
        System.out.println(object2);
        System.out.println();
    }
}

由于双亲委托模型,load1的父加载器是系统类加载器,系统类加载器会默认在classpath路径下寻找class文件加载。在当前classpath路径下存在MyTest1.class的情况下,
在这里插入图片描述

在这里插入图片描述
分析:
两个class对象是同一个。原因是因为,虽然我们调用的是loader1和loader2去加载MyTest1.class,但实际上由于双亲委托,系统类加载器在classpath路径中发现了MyTest1.class,发现它可以加载。所以MyTest1是由系统类加载器进行加载的。

调动loader1.loadClass时,系统类加载器加载了MyTest1,然后返回了一个Class对象。由于一个类只能被加载一次,所以当我们调用loader2.class时,发现MyTest1已经被加载了,所以直接返回之前的Class对象。


在当前classpath路径下不存在MyTest1.class的情况下,删掉MyTest1.class,再进行运行程序
在这里插入图片描述
分析:
结果打印出了findClass中的内容,说明这次MyTest1.class的加载是由我们的自定义类加载器完成的。
那么这次的两个Class对象的hash值不一样,说明这是连个不同的对象,这是说明啥?不是说同一个类只能被加载一次么?这里是加载了两次啊?

原因是:类加载器的命名空间

每个类加载器有自己的命名空间,由该类加载器所有的父类加载器和所加载的类组成。
同一个命名空间不会存在类完整名字(包括包名)相同的两个类
但是不同的命名空间可以

loader1和loader2是两个类加载器,所以他们有各自的命名空间。所以,MyTest1才可以被加载两次。


修改loader2的构造,让loader1成为loader2的父类加载器

    public static void main(String[] args) throws Exception {
        //创建了一个类加载器
        MyTest12 loader1 = new MyTest12("loader1");
        //loader1.setPath("D:\\workspace2\\JVMLearn\\target\\classes\\");
        loader1.setPath("C:\\Users\\CJ\\Desktop\\");
        Class<?> clazz = loader1.loadClass("com.cj.jvm.classloader.MyTest1");
        System.out.println("class:" + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println("getClassLoader:"+object.getClass().getClassLoader());
        System.out.println();

        MyTest12 loader2 = new MyTest12(loader1,"loader2");
        loader2.setPath("C:\\Users\\CJ\\Desktop\\");

        Class<?> clazz2 = loader2.loadClass("com.cj.jvm.classloader.MyTest1");
        System.out.println("class:"+clazz2.hashCode());
        Object object2 = clazz2.newInstance();
        System.out.println(object2);
        System.out.println();


    }
}

在这里插入图片描述

分析:
此时,MyTest1.class依旧被删除了(重新编译项目或者MyTest1.java可以重新获得),所以,MyTest1由我们自定义的loader1加载。

由于双亲委托,loader2委托loader1去加载,发现已经加载过了。所以直接返回Class对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值