java自定义类加载器的实现
2019-02-25 本文已影响0人
hebiris
前言
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).
- 根(bootstrap)类加载器
-
父类委托加载
子类加载器在加载过程中,先让父类加载器尝试加载,如果父类加载器尝试失败,子类加载器才接着尝试加载,如果加载失败会爆出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的文件名称替换成包名.类名的形式,才能正确加载到类。