概述
首先说一下类加载器加载类的先后关系即双亲委派模型:
例子在这里:
https://m.imooc.com/wenda/detail/495158
如果如果有一个类A是由扩展类加载器加载的,Class B是由系统类加载器加载的。在A中有一个函数,函数的参数是一个Object类型的。那么现在A调用这个函数时将这个B的实例传进去会报错。为什么呢?在这里A的加载器不会下去向子类加载器去询问,这个是关键,即使子类加载器加载了这个类,所以问题就是出现在了这里。
通过此图可以一目了然的看到答案,在当前的加载器的基础上想上查找,看看弗雷加载器有没有加载过,如果加载过那么就返回,如果没有那么就再向上查找,直到找到最顶级的类加载器。
通过底层每一个类加载器的命名空间来保证类加载的安全性。越是子类加载器越具有包容性,越是上层加载器越局限。从第一张图中可以看出自定义类加载器的命名空间是最大的,启动类加载器的命名空间是最小的。
其实从其类加载的范围也是可以解决这个问题的,启动类加载器jre/lib下的文件,扩展类加载器加载的是jre/lib/ext下的文件,系统类加载器加载的是classpath下的文件,自定义类加载器加载的是自定义文件夹下的文件,所以从这一角度也是可以知道每一个类加载器从本身加载的范围来说是不尽相同的,再加上类继承关系所获得属性就可以知道父类加载器所加载的类,反之则不行,从路径来看如果自定义文件夹在F盘,classpath所在的路径在E盘,那么系统类加载器如何也不可能知道自定义加载器加载的类在什么地方。
https://blog.csdn.net/qq_20846769/article/details/100026116
这篇文章写的不错,综合对比了加载器之间的混合调用,并得出结论:子类加载器可以获取父类加载器加载的类,反之则不行。
并且同一个自定义类加载器的不同实例加载同一个类会在各自的类加载器的命名空间内部。
//自定义类加载器
public class EdClassLoader extends ClassLoader{
String path;
byte[] returncontext = new byte[1024*1024];
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File(getPath());
try {
FileInputStream fileInputStream = new FileInputStream(file);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//每次读取的大小是4kb
byte[] b = new byte[4 * 1024];
int n = 0;
while ((n = fileInputStream.read(b)) != -1) {
// 下个将数据读入到字节数组中,再将字节数组输出到字节输出流中
outputStream.write(b, 0, n);
}
//将Dog.class类读取到byte数组里
returncontext = outputStream.toByteArray();
//调用defineClass 将byte 加载成class
Class<?> aClass = this.defineClass(name, returncontext, 0, returncontext.length);
return aClass;
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public byte[] getReturncontext() {
return returncontext;
}
public void setReturncontext(byte[] returncontext) {
this.returncontext = returncontext;
}
}
//对比
public class testAppClassLoader {
Person person = null;
public testAppClassLoader(Object o,Object o1){
System.out.println(o instanceof Person);
System.out.println(o1 instanceof Person);
}
}
public static void main(String[] args) {
try {
EdClassLoader edClassLoader = new EdClassLoader();
edClassLoader.setPath("C:\Users\mcl\Desktop\test\sss\Person.class");
Class<?> person = edClassLoader.findClass("Person");
Object o = person.newInstance();
EdClassLoader edClassLoader1 = new EdClassLoader();
edClassLoader1.setPath("C:\Users\mcl\Desktop\test\sss\Person.class");
Class<?> person1 = edClassLoader1.findClass("Person");
Object o1 = person1.newInstance();
testAppClassLoader testAppClassLoader = new testAppClassLoader(o, o1);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
结果表明没有问题,且不可以向系统类加载器加载的转换。
再试一下,换成自定义来加载器是一样的:
public static void main(String[] args) {
// new cas_test();
try {
EdClassLoader edClassLoader = new EdClassLoader();
edClassLoader.setPath("C:\Users\mcl\Desktop\test\sss\Person.class");
Class<?> person = edClassLoader.findClass("Person");
Object o = person.newInstance();
edClassLoader.setPath("C:\Users\mcl\Desktop\test\Person.class");
Class<?> person1 = edClassLoader.findClass("Person");
Object o1 = person1.newInstance();
testAppClassLoader testAppClassLoader = new testAppClassLoader(o, o1);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
提示类已经被加载过,无需再次加载,可以知道命名空间里已经有了Person,那么就不会再次加载。
这个问题是我面试阿里时面试官问的一个题目,一直百思不得其解。哎对于类加载器这里的学习还是不到位,直到最近仔细的学习了一下类加载。但是网上关于这个解释的并不是很到位或是长篇大论很难定位到具体的问题,于是为了加深理解,写了这篇博客。
如有错误请不吝赐教,感谢!
注:孤陋寡闻了,这篇文章写的很好之前没有看到,在此标注一下:
https://blog.csdn.net/codehole_/article/details/100892463
最后
以上就是哭泣睫毛膏为你收集整理的理解:父类加载器加载的类不能使用子类加载器加载的类的全部内容,希望文章能够帮你解决理解:父类加载器加载的类不能使用子类加载器加载的类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复