java ClassLoader作用

1 篇文章 0 订阅

原文地址:http://blog.chinaunix.net/uid-21227800-id-65885.html


ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。 1.1 几个相关概念ClassLoader负责载入系统的所有Resources(Class,文件,来自网络的字节流等),通过ClassLoader从而将资源载入JVM  

每个class都有一个reference,指向自己的ClassLoader。Class.getClassLoader()  
array的ClassLoader就是其元素的ClassLoader,若是基本数据类型,则这个array没有ClassLoader  
1.2 主要方法和工作过程Java1.1及从前版本中,ClassLoader主要方法:  
Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口点  
defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。  
findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。  
resolveClass可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值  
findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法  
一般load方法过程如下:  

调用 findLoadedClass 来查看是否存在已装入的类。  
如果没有,那么采用某种特殊的神奇方式来获取原始字节。(通过IO从文件系统,来自网络的字节流等)  
如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。  
如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。  
如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。  
如果还没有类,返回 ClassNotFoundException。  
否则,将类返回给调用程序。  
1.3 委托模型自从JDK1.2以后,ClassLoader做了改进,使用了委托模型,所有系统中的ClassLoader组成一棵树,ClassLoader在载入类库时先让Parent寻找,Parent找不到才自己找。  
JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++编写的,在Java中看不到它,是null。它用来加载核心类库,就是在lib下的类库,Extension ClassLoader加载lib/ext下的类库,App ClassLoader加载Classpath里的类库,三者的关系为:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap进行寻找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader。  

将ClassLoader设计成委托模型的一个重要原因是出于安全考虑,比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。  

委托模型还带来了一些问题,在某些情况下会产生混淆,如下是Tomcat的ClassLoader结构图:  

                Bootstrap 
                  | 
                System 
                  | 
                Common 
                /     
            Catalina  Shared 
                      /     
                   Webapp1  Webapp2 ... 

由 Common 类装入器装入的类决不能(根据名称)直接访问由 Web 应用程序装入的类。使这些类联系在一起的唯一方法是通过使用这两个类集都可见的接口。在这个例子中,就是包含由 Java servlet 实现的 javax.servlet.Servlet。  
如果在lib或者lib/ext等类库有与应用中同样的类,那么应用中的类将无法被载入。通常在jdk新版本出现有类库移动时会出现问题,例如最初我们使用自己的xml解析器,而在jdk1.4中xml解析器变成标准类库,load的优先级也高于我们自己的xml解析器,我们自己的xml解析器永远无法找到,将可能导致我们的应用无法运行。  

相同的类,不同的ClassLoader,将导致ClassCastException异常  

1.4 线程中的ClassLoader每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类,可以使用方法Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为;也可以通过方法Thread.currentThread().getContextClassLoader()来获得当前线程的ClassLoader。  
实际上,在Java应用中所有程序都运行在线程里,如果在程序中没有手工设置过ClassLoader,对于一般的java类如下两种方法获得的ClassLoader通常都是同一个  

this.getClass.getClassLoader();  
Thread.currentThread().getContextClassLoader();  
方法一得到的Classloader是静态的,表明类的载入者是谁;方法二得到的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader通常都不同。  

1.5 Web应用中的ClassLoader回到上面的例子,在Tomcat里,WebApp的ClassLoader的工作原理有点不同,它先试图自己载入类(在ContextPath/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成。  
由此可得:  

对于WEB APP线程,它的contextClassLoader是WebAppClassLoader  
对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader  
1.6 获得ClassLoader的几种方法可以通过如下3种方法得到ClassLoader  
this.getClass.getClassLoader(); // 使用当前类的ClassLoader  
Thread.currentThread().getContextClassLoader(); // 使用当前线程的ClassLoader  
ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。(注意,system ClassLoader与根ClassLoader并不一样。JVM下system ClassLoader通常为App ClassLoader)  
1.7 几种扩展应用用户定制自己的ClassLoader可以实现以下的一些应用  
安全性。类进入JVM之前先经过ClassLoader,所以可以在这边检查是否有正确的数字签名等  
加密。java字节码很容易被反编译,通过定制ClassLoader使得字节码先加密防止别人下载后反编译,这里的ClassLoader相当于一个动态的解码器  
归档。可能为了节省网络资源,对自己的代码做一些特殊的归档,然后用定制的ClassLoader来解档  
自展开程序。把java应用程序编译成单个可执行类文件,这个文件包含压缩的和加密的类文件数据,同时有一个固定的ClassLoader,当程序运行时它在内存中完全自行解开,无需先安装  
动态生成。可以生成应用其他还未生成类的类,实时创建整个类并可在任何时刻引入JVM  
2.0 资源载入 
所有资源都通过ClassLoader载入到JVM里,那么在载入资源时当然可以使用ClassLoader,只是对于不同的资源还可以使用一些别的方式载入,例如对于类可以直接new,对于文件可以直接做IO等。 2.1 载入类的几种方法假设有类A和类B,A在方法amethod里需要实例化B,可能的方法有3种。对于载入类的情况,用户需要知道B类的完整名字(包括包名,例如"com.rain.B")  
1. 使用Class静态方法 Class.forName  

    Class cls = Class.forName("com.rain.B"); 
    B b = (B)cls.newInstance(); 

2. 使用ClassLoader  
    /* Step 1. Get ClassLoader */ 
    ClassLoader cl; // 如何获得ClassLoader参考1.6 

    /* Step 2. Load the class */ 
    Class cls = cl.loadClass("com.rain.B"); // 使用第一步得到的ClassLoader来载入B 
     
    /* Step 3. new instance */ 
    B b = (B)cls.newInstance(); // 有B的类得到一个B的实例 

3. 直接new  
    B b = new B(); 

2.2 文件载入(例如配置文件等)假设在com.rain.A类里想读取文件夹 /com/rain/config 里的文件sys.properties,读取文件可以通过绝对路径或相对路径,绝对路径很简单,在Windows下以盘号开始,在Unix下以"/"开始  
对于相对路径,其相对值是相对于ClassLoader的,因为ClassLoader是一棵树,所以这个相对路径和ClassLoader树上的任何一个ClassLoader相对比较后可以找到文件,那么文件就可以找到,当然,读取文件也使用委托模型  

1. 直接IO  

/** 
 * 假设当前位置是 "C:/test",通过执行如下命令来运行A "java com.rain.A" 
 * 1. 在程序里可以使用绝对路径,Windows下的绝对路径以盘号开始,Unix下以"/"开始 
 * 2. 也可以使用相对路径,相对路径前面没有"/" 
 * 因为我们在 "C:/test" 目录下执行程序,程序入口点是"C:/test",相对路径就 
 * 是 "com/rain/config/sys.properties" 
 * (例子中,当前程序的ClassLoader是App ClassLoader,system ClassLoader = 当前的 
 * 程序的ClassLoader,入口点是"C:/test") 
 * 对于ClassLoader树,如果文件在jdk lib下,如果文件在jdk lib/ext下,如果文件在环境变量里, 
 * 都可以通过相对路径"sys.properties"找到,lib下的文件最先被找到 
 */ 
File f = new File("C:/test/com/rain/config/sys.properties"); // 使用绝对路径 
//File f = new File("com/rain/config/sys.properties"); // 使用相对路径 
InputStream is = new FileInputStream(f); 

如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,Properties默认认为is的编码是ISO-8859-1,如果配置文件是非英文的,可能出现乱码问题。  
2. 使用ClassLoader  

/** 
 * 因为有3种方法得到ClassLoader,对应有如下3种方法读取文件 
 * 使用的路径是相对于这个ClassLoader的那个点的相对路径,此处只能使用相对路径 
 */ 
InputStream is = null; 
is = this.getClass().getClassLoader().getResourceAsStream( 
       "com/rain/config/sys.properties"); //方法1 
//is = Thread.currentThread().getContextClassLoader().getResourceAsStream( 
       "com/rain/config/sys.properties"); //方法2 
//is = ClassLoader.getSystemResourceAsStream("com/rain/config/sys.properties"); //方法3 

如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,这里要注意编码问题。  
3. 使用ResourceBundle  

    ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys"); 

这种用法通常用来载入用户的配置文件,关于ResourceBunlde更详细的用法请参考其他文档  
总结:有如下3种途径来载入文件  

    1. 绝对路径 ---> IO 
    2. 相对路径 ---> IO 
                ---> ClassLoader 
    3. 资源文件 ---> ResourceBundle 

2.3 如何在web应用里载入资源在web应用里当然也可以使用ClassLoader来载入资源,但更常用的情况是使用ServletContext,如下是web目录结构  
    ContextRoot 
       |- JSP、HTML、Image等各种文件 
        |- [WEB-INF] 
              |- web.xml 
              |- [lib] Web用到的JAR文件 
                |- [classes] 类文件 

用户程序通常在classes目录下,如果想读取classes目录里的文件,可以使用ClassLoader,如果想读取其他的文件,一般使用ServletContext.getResource()  

如果使用ServletContext.getResource(path)方法,路径必须以"/"开始,路径被解释成相对于ContextRoot的路径,此处载入文件的方法和ClassLoader不同,举例"/WEB-INF/web.xml","/download/WebExAgent.rar"


另外一篇关于classLoader的文章,主要介绍classloader加载类时的层次结构,

文章地址:http://hain.bokee.com/1921356.html

Java中的Classloader

作为一种编程语言, 我总觉得Java有那么一点奇异, 或者说煅? 它不象传统的编译型语言(比如C/C++)那么纯粹, 它不仅仅是一种"语言". 比如Java中有Classloader的概念, 而这通常是操作系统的一部分. 理解这一概念对于J2EE尤其重要. 下面的文章译自IBM的一篇文档, 很清楚地解释了这个重要的概念.

Classloader是如何工作的?

Java虚拟机中的每个Java类都是由某个classloader载入的, classloader本身也是Java类, 所以这一点就特别有趣. 那么, 这些classloader又是如何载入的呢? 这好像是一个悖论. 幸运的是, 事实并非如此. Java包含一个自举classloader, 它是用本地代码写的, 是JVM的一部分. 这个自举classloader的主要作用是载入Java核心类, 从而自举整个Java环境.

在一个企业Java应用中, 使用到的许多类都不是Java核心类. 比如, 程序员也许会引用其应用中的另外一个类, 或者Java扩展中的一个类. Java扩展是扩展Java核心平台功能的一些Java包. 为了隔离这两种不同的Java类, Java采用两种不同的classloader: application和extension classloader. 它们都是用Java写的. 这意味着这些类将被它们特定的classloader载入, 如下例所示.
public class WhichClassLoader {
WhichClassLoader( ) {}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));
System.out.println("\nBootstrap classpath= \n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath= "+ extensionClassPath + "\n");
System.out.println("System classpath= "+ systemClassPath + "\n" );

//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader whichClassLoader = new WhichClassLoader();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

这个例子相当简单, 它查询和显示classloader的路径, 然后创建三个新的对象示例, 类型各不相同: 一个Java核心类(java.lang.Object), 一个Java扩展类(javax.naming.InitialContext)和一个用户类(WhichClassLoader). 最后它打印出载入这些类的classloader. 运行这个例子的结果是:
D:\Classpath_Project\src>java -classpath . WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath=
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f0272827
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f023282

这个结果中有几点值得一提. 比如, 你也许想知道为什么用户类是被sun.misc.Launcher$AppClassLoader@f023282所载入的, 而不是更通用的形式比如sun.misc.Launcher$AppClassLoader或者Application ClassLoader. 原因在于getClassLoader()方法的返回类型的是java.lang.ClassLoader. 当这个对象实例被送到一个输出流上时, 打印的是实例的名字, 这一名字在JVM每次启动时都会改变. 在我们这次特定的运行中, application classloader的实例名是sun.misc.Launcher$AppClassLoader@a1b1234. 对于extension classloader也是这样. 有趣的是, 自举classloader的实例名是空. 这是因为自举classloader不是用Java写的, 所以没有java.lang.ClassLoader的实例供返回.

另外一件引起你兴趣的事情也许是我们如何通过系统类查询路径, 比如System.getProperties().getProperty("sun.boot.class.path"). 在这个例子中, 你也许会想我们可以通过动态地改变路径, 从而强制用一个特定的classloader来载入一个特定的类. 我们可以这样修改来验证一下这个想法:
import javax.naming.InitialContext;
public class WhichClassLoader1 {
//Constructor
WhichClassLoader1( ) {
//do nothing
}

public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

//modifying the bootstrapclasspath to include the jar file which contains the InitialContext Class
// This should force the class to be loaded by the bootstrap classloader???????????
String fileSeparator = System.getProperty("file.separator");
String InitialContextJar = "D:"+fileSeparator+"jdk1.2.2"+fileSeparator + "jre"+fileSeparator+"lib"+fileSeparator+"ext"+fileSeparator+"iioprt.jar"
bootstrapClassPath.append(System.getProperty("path.separator")).append(InitialContextJar);
System.setProperty("sun.boot.class.path",bootstrapClassPath.toString());
System.out.println("\nBootstrap classpath=\n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );

//Create new object instances
Object object = new java.lang.Object()
InitialContext initialContext = new InitialContext();
WhichClassLoader1 whichClassLoader1 = new WhichClassLoader1();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, Javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader1, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

运行的结果很有趣:
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\iioprt.jar
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, Javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b290a
User file, WhichClassLoader1, was loaded by: sun.misc.Launcher$AppClassLoader@f01f290a

和我们期待的不同. 事实是改变这些系统级的属性完全没有效果. 这说明了这些classloader很重要的一个特性: 一旦JVM启动后, 它们的路径是不可更改的, 或者说是静态的.

让我们回到这个例子原来的目的. 到目前为止, 看上去Java的classloader正如我们被告知的那样工作: 自举classloader载入Java核心类, extension classloader载入Java扩展包, 而application classloader载入用户类. 然而, classloader在本质上又是分层的, 遵循"委托给父类"的模式. 这意味着除了自举classloader, 每一个classloader都有一个父classloader, 当一个classloader试图载入一个类时, 它首先将这一责任委托给它的父classloader. 这个模式是这样工作的: 一个classloader将尝试载入一个类, 当且仅当这个类在这个classloader所属的层次结构中还未被载入, 并且这个classloader的父classloader找不到这个类.

自顶向下地遍历classloader层次, 我们看到首先是自举classloader, 然后是extension classloader和application classloader. 如果你将classloader层次看作稀疏树结构, 那么自举classloader是根节点, application classloader是叶节点.

这种层次结构可以写程序演示如下:
public class ShowHierarchy{
public static void main (String args[] ) throws Exception {
System.out.println("The System ClassLoader is: " + ClassLoader.getSystemClassLoader().getClass());
System.out.println("The System ClassLoader's Parent is: " + ClassLoader.getSystemClassLoader().getParent().getClass());
System.out.println("The System ClassLoader's Parent's Parent is: " +ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
结果是:
D:\WebSphere\Documents\Classpaths\src>java .classpath . ShowHierarchy
The System ClassLoader is: class sun.misc.Launcher$AppClassLoader
The System ClassLoader's Parent is: class sun.misc.Launcher$ExtClassLoader

为了演示"委托给父类"的关系, 让我们在每个classloader的路径上放一份包含InitialContext.class的JAR文件, 然后执行第一个例子. 记住一旦JVM运行以后, 它的classloader路径是不可变的, 所以我们需要通过Java开关-classpath和-bootclasspath在运行之前改变它们.
D:\Classpath_Project\src\java -classpath D:\jdk1.2.2\jre\lib\ext\jndi.jar;. -XbootclasspathD:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath= D:\jdk1.2.2\jre\lib\ext\jndi.jar;.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: null
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f01a2987

正如你所见, InitialContext类如我们预见的那样是由自举classloader载入的, 你能理解这是为什么吗? 让我们详细描述一下发生的事吧:
1. WhichClassLoader类创建一个InitialContext类的新实例: javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
2. Application classloader查看InitialContext类是否已经在它所属的classloader层次结构中被载入, 也就是说被自举classloader, 被extension classloader或者被它自己所载入.
3. 结果是否定的.
4. 遵循"委托给父类"的模式, application classloader将载入的任务委托给extension classloader.
5. 再一次遵循"委托给父类"的模式, extension classloader将载入的任务委托给自举classloader.
6. 自举classloader没有父classloader, 尝试载入类.
7. 自举classloader成功地载入了InitialContext类.
8. InitialContext的新实例被创建并返回给WhichClassloader.

这一场景需要进一步的解释. 看上去似乎很明显的是, application classloader是第一个收到创建InitialContext新实例的classloader, 因为它位于classloader层次结构的最底层. 但并不总是这样. 随着Java 2的出现, 类将通过调用者的classloader被载入, 这可能是, 但也可能不是位于层次结构最底层的classloader. 在我们的例子中, 调用者是WhichClassLoader, 我们知道它是由application classloader载入的.

Java 2中的classloader遵循"委托给父类, 并组织成层次结构"的模式, 这允许你做一些有趣的事情. 但当类不是由classloader层次结构的叶节点载入时, 也可能引起问题. 比如, 让我们修改一下WhichClassLoader, 重命名为WhichClassLoader2, 并创建两个新类WhichClassLoader3和WhichClassLoader4.

文件WhichClassLoader2.java:
public class WhichClassLoader2 {
//Constructor
WhichClassLoader2() {
//do nothing
}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

System.out.println("\nBootstrap classpath="+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );
//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader2 whichClassLoader2 = new WhichClassLoader2();
WhichClassLoader3 whichClassLoader3 = new WhichClassLoader3();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader2, was loaded by: " + whichClassLoader2.getClass().getClassLoader() + "\n");
System.out.println("\nUser file, WhichClassLoader3, was loaded by: " + whichClassLoader3.getClass().getClassLoader() + "\n");
whichClassLoader3.getTheClass();
}
}

文件WhichClassloader3.java:
public class WhichClassLoader3 {
public WhichClassLoader3 () {
}
public void getTheClass() {
WhichClassLoader4 wcl4 = new WhichClassLoader4 ();
System.out.println("WhichClassLoader4 was loaded by " + wcl4.getClass().getClassLoader());
}
}

文件WhichClassloader4.java:
public class WhichClassLoader4 {
WhichClassLoader4 () {
}
}

我们将WhichClassLoader2和WhichClassLoader4放在当前目录下, 这样它们仅仅存在于application classloader的路径之中. 接下来我们将WhichClassLoader3放在extension classloader的路径中.
D:\Classpath_Project\src>ls
WhichClassLoader2.class
WhichClassLoader2.java
WhichClassLoader1.java
WhichClassLoader4.class
WhichClassLoader4.java
D:\Classpath_Project\src>ls D:\jdk1.2.2\jre\lib\ext\
WhichClassLoader3.jar
cosnaming.jar
iiimp.jar
iioprt.jar
jndi.jar
providerutil.jar
rmiorb.jar
rmiregistry.jar

我们来看一下运行WhichClassLoader2会发生什么.
D:\Classpath_Project\src>java -classpath . WhichClassLoader2
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
User file, WhichClassLoader2, was loaded by: sun.misc.Launcher$AppClassLoader@f01f3492
User file, WhichClassLoader3, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
java.lang.NoClassDefFoundError: WhichClassLoader4
at WhichClassLoader3.getTheClass(WhichClassLoader3.java:8)
at WhichClassLoaderTest.main(WhichClassLoaderTest.java:38)
Exception in thread "main"

你也许会问既然WhichClassLoader4明明在application classloader的路径中, 载入WhichClassLoader4为什么会失败. 这种失败不是因为classloader找不到类, 而是因为WhichClassLoader3的classloader找不到类. 为了更好地理解发生的事情, 让我们从载入WhichClassLoader3的classloader(也就是extension classloader)的角度来看一下.

首先, 要求创建一个WhichClassLoader4的实例, 于是遵循Java 2的"委托给父类"模式, extension classloader先查看这个类是否已经被它所属的classloader层次结构所载入, 这种尝试将失败. 于是它请求它的父classloader, 就是自举classloader, 来载入这个类. 这一请求将返回一个NoClassDefFound异常. Extension classloader将捕获这个异常并求助于它的最后选择: 它试图自己载入这个类. 搜寻了自己的搜索路径之后, extension classloader找不到WhichClassLoader4类的定义, 所以它重新抛出NoClassDefFoundError异常. 此时不再有classloader来捕获这个异常, 于是在屏幕上打印异常, 程序退出.

这就引起一个问题, 既然有可能发生这样的问题, 为什么要自寻烦恼地采用多个classloader呢? 为什么不回到Java 2之前的框架, 在那样的框架之下, 只有一个系统classloader来载入所有东西? 与引发的问题相比, classloader层次结构和"委托给父类"模式带来的好处是否值得呢? 简而言之, 是值得的:
1. 保护. Java预定义了自举classloader和extension classloader, 缺省情况下, 只把application classloader的定义留给了用户. 因为"委托给父类"的模式, 用户定义的任何类都不可能覆盖Java扩展类和核心类.
2. 用户友好. 在同样的方式下, 因为自举classloader和extension classloader是预定义的, 你不再需要把classes.zip文件放在-classpath属性中.
3. 隔离. 也许这是新的classloader定义做提供的最重要的好处. 到目前为止, 这看上去不是那么有用, 但我们还没有谈到Java允许你定义自己的classloader. 使用继承, 可以形成自己的classloader层次树.

理解这个模型所提供的益处的最佳方式是和操作系统的名字空间作一个类比. 让我们假设我们正在开发一个叫MyApp的应用, 这个应用有三个版本: 一个是实际用的, 一个处于Beta测试中, 一个还在开发中. 我们想要在任何时候运行这些应用程序版本中的任何一个, 于是我们将它们放在分开的目录结构中:
/usr/MyApp
/usr/MyApp/v1
/usr/MyApp/v2
/usr/MyApp/v3

所有版本公用的文件放在MyApp目录下, 版本特定的文件分别放在v1, v2和v3目录下. 使用这种方法, 我们能很有效地使用硬盘空间, 同时又能管理版本的差异. 同样的想法对classloader也适用. 核心类是最通用的, 就放在层次结构的根节点上, 而那些特定"版本"的类放在叶节点上.

译者注: 比较了WebSphere, WebLogic和JBoss之后, 发现它们的classloader设计都不一样. 移植代码时, 这可能引发一些问题.


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值