最近在看java类加载器的资料,于是写了一个自定义类加载器测试一下,结果就悲剧了,直接报空指针!
跟着报错指引看代码37行是什么东东?
就是一个inputStream, 然后看看它的定义:
这玩意就是从classpath读取class字节码文件,然后转换成IO流,并没有什么问题啊。
打个断点看看,发现inputStream是有值的:
这他妈是什么问题呢,一时间觉得很迷茫,不知从何入手。
而且最诡异的是一旦加上下面一行return的代码,空指针报错就没了!
if (clazz !=null) return clazz;
这是不是很诡异,看来只能重新再次debug了。
再次调试发现inputStream仍然是有值的,但是走完这段代码以后发现该代码又会再来一遍:
此时的类名变成了Object.class!但是之前的读取的类名应该是ClassData.class, 如下所示:
看来这就是问题的关键了! 可以看到此时的inputStream真的是空值null
正是因为从classpath读取不到java.lang.Object的字节码文件,导致读取IO时生成了null,然后在创建字节数组时就引发了空指针异常
那为什么加了if判断的return代码就不报空指针错误了呢?
那是因为此时clazz是有值的:class java.lang.Object, 为什么有值呢?因为Object.class虽然从classPath的物理路径读取不到字节码文件,但是通过super.load(name)的父类方法(累加载器应该是AppClassLoader),jvm能够从内存中读取到java.lang.Object.class的字节码文件!
然后它会根据下面的if判断直接return clazz,而不会执行后面创建字节数组的代码,从而避免了空指针的发生。
以上分析完毕,可以看到关于类加载器的知识,里面的水还是很深的,需要仔细分析才能挖掘到深层次的东西
然后附上完整代码:
public class ClassLoaderTest2 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myClassLoader = new ClassLoader() {
// 自定义类加载器
@Override // 重写loadClass方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
// 下面的代码getSystemClassLoader使用了$AppClassLoader来加载,这样就仍然保持了双亲委派机制
// ClassLoader loader = getSystemClassLoader();
// try {
// clazz = loader.loadClass(name);
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
//
// if (clazz != null) {
// return clazz;
// }
// 读取classPath下编译生成的class字节码文件,然后转化成byte字节流
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream inputStream = getClass().getResourceAsStream(fileName);
if ( inputStream == null) {
clazz = super.loadClass(name);
}
// 当name入参为Object时,直接return clazz,跳过后面代码的执行,避免产生空指针问题。
// if (clazz !=null) return clazz;
// 下面的代码实际打破了双亲委托机制,使得自定义classLoader生效,从而跟AppClassLoader发生冲突
// 导致同名Class的实例对象转换异常,因为类加载器不一样了
try {
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
// defineClass方法将byte字节流转换成jvm能识别的Class对象
clazz = defineClass(name, bytes, 0, bytes.length);
} catch (IOException exception) {
throw new ClassNotFoundException(name);
}
return clazz;
}
};
// 通过自定义类加载器myClassLoader来加载类ClassData并完成实例化
Object clazz = myClassLoader.loadClass("com.mybest.ClassLoaderStudy.ClassData").newInstance();
System.out.println("clazz self is ======>>>" + clazz.getClass());
System.out.println("clazz's classLoader is " + clazz.getClass().getClassLoader());
// 判断通过自定义类加载器myClassLoader生成的clazz是否还是ClassData类型
// 从这里可以看到如果类加载器不同,即便Class类型相同,JVM仍然认为是两个不同的类
System.out.println("clazz instanceof ClassData==> " + (clazz instanceof ClassData));
System.out.println("ClassData's classLoader is " + ClassData.class.getClassLoader());
// 所以这里会发生ClassCastException,因为类加载器不同:一个是自定义的类加载器myClassLoader,
// 一个是系统类加载器:AppClassLoader
ClassData clazz2 = (ClassData) clazz;
}
}