上篇文章我们说,A通过自定义ClassLoader去加载B类,从而能够使用到不在同一个目录下的B。
多说两句关于ClassLoader的面试题 我们留了个问题,今天来解释一下。
问题2:A通过ClassLoader加载了B,B对C有引用,那么B和C的ClassLoader是谁?
熟悉Java的朋友应该知道,对于A类来说,如果是默认情况下,它的ClassLoader应该是AppClassLoader, 我们可以通过 A.class.getClassLoader()来获取,输出
sun.misc.Launcher.AppClassLoader@6d06d69c
这是很基础的一个知识点。如果再问深一层,AppClassLoader上面是谁呢? 这里可以用 ClassLoader的 getParent()方法来获取,依次输出父loader的话,结果是
sun.misc.Launcher.AppClassLoader@6d06d69c sun.misc.Launcher.ExtClassLoader@70dea4e parent : null
提到ClassLoader的层级结构的目的是要来解释B的ClassLoader是谁。 上面说到,B类是通过自定义ClassLoader去加载的,那么B跟A的ClassLoader虽然不同,但是它们依然有层级结构, 一样用getClassLoader()方法输出
B clasLoader : DiskClassLoader@33909752 sun.misc.Launcher.AppClassLoader@6d06d69c sun.misc.Launcher.ExtClassLoader@70dea4e
可以看到这时候它的层级结构是这样的。细心的话可以留意一下B的 DiskClassLoader的父loader,跟A的loader的ID。 至于 C 的ClassLoader,其实没有那么复杂,因为ClassLoader在加载B的时候,会发现它对C有引用, 所以同时会把C也加载进来,C的ClassLoader结构也跟B一样
C clasLoader : DiskClassLoader@33909752 sun.misc.Launcher.AppClassLoader@6d06d69c sun.misc.Launcher.ExtClassLoader@70dea4e
还记得双亲委派吗,Java在寻找类的时候会从根ClassLoader去加载的逻辑? 好了,先记住双亲委派,然后来思考下面这种场景。
我们可以直接在B类里 new 一个 A类对象吗?可以的话,为什么可以?
其实这是一个考察Java基础知识的问题,可以用下面的方法来验证它。 我们写一个B的代码像下面这样
public class B {
public void invokeBmethod(){
System.out.println("B method invoke");
ClassLoader bLoader = B.class.getClassLoader();
System.out.println("B clasLoader : " + bLoader);
while(bLoader != null) {
System.out.println(bLoader);
bLoader = bLoader.getParent();
}
invokeCmethod();
A classA = new A(); //<--实例化A
}
private void invokeCmethod() {
System.out.println("invokeCmethod called");
C cClass = new C();
cClass.cMethodInvoke();
}
}
然后把 A.class 文件放到B的目录中,让它编译成功先。
编译完成后把 A.class 挪出 B 的目录。我们在A的构造函数里输出一段log,看实例化有没有成功,
public class A {
public A() {
System.out.println("A constructed");
}
//省略一堆main方法
}
运行A,输出结果
java A clas A initiated sun.misc.Launcher.AppClassLoader@6d06d69c sun.misc.Launcher.ExtClassLoader@70dea4e parent : null B method invoke B clasLoader : DiskClassLoader@33909752 sun.misc.Launcher.AppClassLoader@6d06d69c sun.misc.Launcher.ExtClassLoader@70dea4e A constructed //<---- A构造输出
看注释这里,我们虽然没用ClassLoader去加载另外一个目录下的A,但是也能成功的实例化它! 为什么呢,这就是双亲委派。 B在构造A的时候,它会先从根ClassLoader里去找,从上往下,直到在 AppClassLoader 中找到。因此即使不在同一个目录下,也能正常的去用它。