1 如何实现一个自定义类加载器
打开ClassLoader的JavaDoc文件(IDEA中ctrl+Q),文档告诉了我们,
- 首先要继承ClassLoader类
- 定义loadClassData方法,用于去寻找class文件并返回该文件的二进制数据
- 重写findClass方法,将二进制class文件加载到内存中并返回Class对象。
- 新建类实例 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对象。