package test;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoaderTest {
private static int count = -1;
public static void testClassLoader(Object obj) {
if (count < 0 && obj == null) {
System.out.println("Input object is NULL";
return;
}
ClassLoader cl = null;
if (obj != null && !(obj instanceof ClassLoader)) {
cl = obj.getClass().getClassLoader();
} else if (obj != null) {
cl = (ClassLoader) obj;
}
count++;
String parent = "";
for (int i = 0; i < count; i++) {
parent += "Parent ";
}
if (cl != null) {
System.out.println(
parent + "ClassLoader name = " + cl.getClass().getName());
testClassLoader(cl.getParent());
} else {
System.out.println(
parent + "ClassLoader name = BootstrapClassLoader";
count = -1;
}
}
public static void main(String[] args) {
URL[] urls = new URL[1];
URLClassLoader urlLoader = new URLClassLoader(urls);
ClassLoaderTest.testClassLoader(urlLoader);
}
}
以上例程的输出为:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
类装载器请求过程
以上例程1为例.将main方法改为:
ClassLoaderTest tc = new ClassLoaderTest();
ClassLoaderTest.testClassLoader(tc);
输出为:
ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent ClassLoader name = BootstrapClassLoader
程序运行过程中,类路径类装载器发出一个装载ClassLoaderTest类的请求, 类路径类装载器必须首先询问它的Parent---扩展类装载器 ---来查找并装载这个类,同样扩展类装载器首先询问启动类装载器。由于ClassLoaderTest不是 Java API(JAVA_HOME\jre\lib)中的类,也不在已安装扩展路径(JAVA_HOME\jre\lib\ext)上,这两类装载器 都将返回而不会提供一个名为ClassLoaderTest的已装载类给类路径类装载器。类路径类装载器只能以它自己的方式来装载 ClassLoaderTest,它会从当前类路径上下载这个类。这样,ClassLoaderTest就可以在应用程序后面的执行中发挥作用。
在上例中,ClassLoaderTest类的testClassLoader方法被首次调用,该方法引用了Java API中的类 java.lang.String。Java虚拟机会请求装载ClassLoaderTest类的类路径类装载器来装载 java.lang.String。就像前面一样,类路径类装载器首先将请求传递给它的Parent类装载器,然后这个请求一路被委托到启动类装载器。但 是,启动类装载器可以将java.lang.String类返回给类路径类装载器,因为它可以找到这个类,这样扩展类装载器就不必在已安装扩展路径中查找 这个类,类路径类装载器也不必在类路径中查找这个类。扩展类装载器和类路径类装载器仅需要返回由启动类装载器返回的类java.lang.String。 从这一刻开始,不管何时ClassLoaderTest类引用了名为java.lang.String的类,虚拟机就可以直接使用这个 java.lang.String类了。
4、一个经典的实例说明
我们看看下面的代码:
package java.lang;
public class String {
public static void main(String[] args){
}
}
大家发现什么不同了吗?对了,我们写了一个与JDK中String一模一样的类,连包java.lang都一样,唯一不同的是我们自定义的String类有一个main函数。我们来运行一下:
java.lang.NoSuchMethodError: main
Exception in thread "main"
这是为什么? 我们的String类不是明明有main方法吗?
其实联系我们上面讲到的双亲委托模型,我们就能解释这个问题了。
运行这段代码,JVM会首先创建一个自定义类加载器,不妨叫做AppClassLoader,并把这个加载器链接到委托链中:AppClassLoader -> ExtClassLoader -> BootstrapLoader。
然后AppClassLoader会将加载java.lang.String的请求委托给ExtClassLoader,而 ExtClassLoader又会委托给最后的启动类加载器BootstrapLoader。
启动类加载器BootstrapLoader只能加载JAVA_HOME\jre\lib中的class类(即J2SE API),问题是标准API中确实有一个java.lang.String(注意,这个类和我们自定义的类是完全两个类)。BootstrapLoader以为找到了这个类,毫不犹豫的加载了j2se api中的java.lang.String。
最后出现上面的加载错误(注意不是异常,是错误,JVM退出),因为API中的String类是没有main方法的。
结论:我们当然可以自定义一个和API完全一样的类,但是由于双亲委托模型,使得我们不可能加载上我们自定义的这样一个类。所以J2SE规范中希望我们自定义的包有自己唯一的特色(网络域名)。还有一点,这种加载器原理使得JVM更加安全的运行程序,因为黑客很难随意的替代掉API中的代码了。