The NoteBook of EricKong

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  611 Posts :: 1 Stories :: 190 Comments :: 0 Trackbacks

      JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoaderExtension ClassLoaderAppClassLoader.其中,Bootstrap是用C++编写的,我们在Java中看不到它,是null。它用来加载核心类库,在JVM源代码中这样写道:
static const char classpathFormat[] =
"%/lib/rt.jar:"
"%/lib/i18n.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/classes";
      知道为什么不需要在classpath中加载这些类了吧?人家在JVM启动的时候就自动加载了,并且在运行过程中根本不能修改Bootstrap加载路径。Extension ClassLoader用来加载扩展类,即/lib/ext中的类。最后AppClassLoader才是加载Classpath的。
      ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。看来ClassLoader还是蛮孝顺的。三者的关系为:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。
     为什么要设计的这么复杂呢?其中一个重要原因就是安全性。比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。
我们来看这段代码:

//A.java
public class A{
    
public static void main(String[] args){
        A a
=new A();
        System.out.println(System.getProperty(
"java.ext.dirs"));
        System.out.println(a.getClass().getClassLoader());
        B b
=new B();
        b.print();
    }

}

//B.java
public class B{
    
public void print(){
        System.out.println(
this.getClass().getClassLoader());
    }

}

1、我们将它放在Classpath中,则打印出
sun.misc.Launcher$AppClassLoader@92e78c
sun.misc.Launcher$AppClassLoader@92e78c
可见都是由AppClassLoader来加载的。
2
、我们将其放在%jre%/lib/ext/classes(ExtClassLoader的加载目录。其加载/lib/ext中的jar文件或者子目录classes中的class文件)中。则会打印出:
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$ExtClassLoader
3
、我们将A.class放到%jre%/lib/ext/classes中,而将B.class放到classpaht中又会怎么样呢?结果是:
sun.misc.Launcher$ExtClassLoader
Exception in thread "main" java.lang.NoClassDefFoundError:B
    at A.main(A.java:6)
怎么会这样呢?这其中有一个重要的问题:A类当然是由ExtClassLoader来加载的,B类要由哪个加载呢?B类要由调用它自己的类的类加载器(真拗口)。也就是说,A调用了B,所以BA的类加载器ExtClassLoader来加载。ExtClassLoader根据委托机制,先拜托Bootstrap加载,Bootstrap没有找到。然后它再自己寻找B类,还是没找到,所以抛出异常。ExtClassLoader不会请求AppClassLoader来加载!你可能会想:这算什么问题,我把两个类放到一起不就行了?
    呵呵,没这么简单。比如JDBC是核心类库,而各个数据库的JDBC驱动则是扩展类库或在classpath中定义的。所以JDBCBootstrap ClassLoader加载,而驱动要由AppClassLoader加载。等等,问题来了,Bootstrap不会请求AppClassLoader加载类啊。那么,他们怎么实现的呢?我就涉及到一个Context ClassLoader的问题,调用Thread.getContextClassLoader

1 - Tomcat的类载入器的结构

Tomcat Server在启动的时候将构造一个ClassLoader树,以保证模块的类库是私有的
Tomcat Server的ClassLoader结构如下:

        +-----------------------------+
        |         Bootstrap           |
        |             |               |
        |          System             |
        |             |               |
        |          Common             |
        |         /      \            |
        |     Catalina Shared        |
        |               /    \        |
        |          WebApp1 WebApp2   |
        +-----------------------------+

其中:
- Bootstrap -
载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar
- System     -
载入$CLASSPATH/*.class
- Common   -
载入$CATALINA_HOME/common/...,它们对TOMCAT和所有的WEB APP都可见
- Catalina    -
载入$CATALINA_HOME/server/...,  它们仅对TOMCAT可见,对所有的WEB APP都不可见
- Shared      -
载入$CATALINA_HOME/shared/..., 它们仅对所有WEB APP可见,对TOMCAT不可见(也不必见)
- WebApp    - 
载入ContextBase/WEB-INF/...,    它们仅对该WEB APP可见

 
2 - ClassLoader的工作原理 
      每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类可以使用Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为ClassLoader被组织成树形,一般的工作原理是:
1) 线程需要用到某个类,于是contextClassLoader被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入
    注意:WebApp ClassLoader的工作原理和上述有少许不同:
    它先试图自己载入类(在ContextBase/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成
由此可得:
    - 对于WEB APP线程,它的contextClassLoader是WebApp ClassLoader
    - 对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader


3 - 部分原代码分析
 
3.1 - org/apache/catalina/startup/Bootstrap.java
Tomcat Server线程的起点
构造ClassLoader树,并设置Tomcat Server线程的contextClassLoader为catalinaloader
载入若干类,然后转入org.apache.catalina.startup.Catalina类中
package org.apache.catalina.startup;

 

// JDK类库
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

//apache自己的类库
import org.apache.catalina.loader.Extension;
import org.apache.catalina.loader.StandardClassLoader;



/**
 * Boostrap loader for Catalina.  This application constructs a class loader
 * for use in loading the Catalina internal classes (by accumulating all of the
 * JAR files found in the "server" directory under "catalina.home"), and
 * starts the regular execution of the container.  The purpose of this
 * roundabout approach is to keep the Catalina internal classes (and any
 * other classes they depend on, such as an XML parser) out of the system
 * class path and therefore not visible to application level classes.
 *
 * 
@author Craig R. McClanahan
 * 
@version $Revision: 1.36 $ $Date: 2002/04/01 19:51:31 $
 
*/


/**
 * 该类的main方法的主要任务:
 * --------------------------
 *
 * 1,创建TOMCAT自己的类载入器(ClassLoader)
 *      +---------------------------+
 *      |         Bootstrap         |
 *      |             |             |
 *      |          System           |
 *      |             |             |
 *      |          Common           |
 *      |         /      \          |
 *      |     Catalina  Shared      |
 *      +---------------------------+
 *    其中:
 *    - Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar
 *    - System    - 载入$CLASSPATH/*.class
 *    - Common    - 载入$CATALINA_HOME/common/,它们对TOMCAT和所有的WEB APP都可见
 *    - Catalina  - 载入$CATALINA_HOME/server/,它们仅对TOMCAT可见,对所有的WEB APP都不可见
 *    - Shared    - 载入$CATALINA_HOME/shared/,它们仅对所有WEB APP可见,对TOMCAT不可见(也不必见)
 *    注意:当一个ClassLoader被请求载入一个类时,它首先请求其父ClassLoader完成载入,
 *    仅当其父ClassLoader无法载入该类时,才试图自己载入该类
 * 2,改变本身线程的默认ClassLoader(本线程就是Tomcat Server线程,类载入器是catalinaLoader)
 * 3,让catalinaLoader载入一些类,类的位置在$CATALINA_HOME/server/lib/catalina.jar中
 * 4,创建org.apache.catalina.startup.Catalina类的一个实例startupInstance,并为其调用方法:
 *    startupInstance.setParentClassLoader(sharedLoader);
 *    startupInstance.process(args);
 *
 *
 * 有关ClassLoader的说明:
 * -----------------------
 *
 * 每个被DEPLOY的WEB APP都会被创建一个ClassLoader,用来载入该WEB APP自己的类
 * 这些类的位置是webappX/WEB-INF/classes/*.class和webappX/WEB-INF/lib/*.jar
 *
 * ClassLoader的工作流程是:
 * 1) 收到一个载入类的的请求
 * 2) 请求其父ClassLoader来完成该类的载入
 * 3) 如果父ClassLoader无法载入,则自己试图完成该类的载入
 *
 * 特别注意WEB APP自己的ClassLoader的实现与众不同:
 * 它先试图从WEB APP自己的目录里载入,如果失败则请求父ClassLoader的代理
 * 这样可以让不同的WEB APP之间的类载入互不干扰
 *
 * WEB APP的ClassLoader的层次结构是:
 *     +----------------------------+
 *     |       Shared               |
 *     |      /      \           |
 *     |   Webapp1  Webapp2      |
 *     +----------------------------+
 * 故对于一个WEB APP,其类载入的优先顺序如下:
 * - /WEB-INF/classes/*.class 和 /WEB-INF/lib/*.jar
 * - Bootstrap classes of JVM
 * - System class loader classes
 * - $CATALINA_HOME/common/
 * - $CATALINA_HOME/shared/
 *
 *
 * 小结:
 * ------
 *
 * 综上分析
 * - Tomcat Server线程使用的classLoader是Catalina
 * - 每个WEB APP线程使用的classloader是Webapp?
 *
 
*/



public final class Bootstrap {
    
/**
     * DEBUG级别
     
*/

    
private static int debug = 0;
    
/**
     * 脚本执行该程序时,提供以下的系统属性:
     * java.endorsed.dirs="$JAVA_ENDORSED_DIRS" -classpath "$CLASSPATH" \
     * java.security.manager \
     * java.security.policy=="$CATALINA_BASE"/conf/catalina.policy \
     * catalina.base="$CATALINA_BASE" \
     * catalina.home="$CATALINA_HOME" \
     * java.io.tmpdir="$CATALINA_TMPDIR" \
     *
     * 
@param args Command line arguments to be processed
     
*/

    
public static void main(String args[]) {
        
// 设置debug
        for (int i = 0; i < args.length; i++)  {
            
if ("-debug".equals(args[i]))
                debug 
= 1;
        }

        
// 设置好系统属性catalina.base,即保证其有值
        if (System.getProperty("catalina.base"== null)
            System.setProperty(
"catalina.base", getCatalinaHome());
        
// 创建三个ClassLoader
        
// 这三个对象是通过ClassLoaderFactory的静态方法创建的
        
// 其实际类型是StandardClassLoader,完成tomcat自定义的类载入
        
// 这些类对非tomcat及其上的webapp的其它java程序不可见,故用自己的Classloader载入
        ClassLoader commonLoader = null;
        ClassLoader catalinaLoader 
= null;
        ClassLoader sharedLoader 
= null;
        
try {
            File unpacked[] 
= new File[1];
            File packed[] 
= new File[1];
            File packed2[] 
= new File[2];    
            ClassLoaderFactory.setDebug(debug);


            
// $CATALINA_HOME/common/classes/*.class - 未压缩的类
            
// $CATALINA_HOME/common/endorsed/*.jar - 压缩的类(endorse:支持)
            
// $CATALINA_HOME/common/lib/*.jar - 压缩的类
            
// 这些类是被tomcat server以及所有的webapp所共享的类,由commonLoader负责载入

            unpacked[
0= new File(getCatalinaHome(),"common" + File.separator + "classes");
            packed2[
0=  new File(getCatalinaHome(),"common" + File.separator + "endorsed");
            packed2[
1=  new File(getCatalinaHome(),"common" + File.separator + "lib");
            commonLoader 
= ClassLoaderFactory.createClassLoader(unpacked, packed2, null);

            
// $CATALINA_HOME/server/classes/*.class
            
// $CATALINA_HOME/server/lib/*.jar
            
// 这些类是仅被tomcat server使用而对webapp不可见的类,由catalinaLoader负责载入

            unpacked[
0= new File(getCatalinaHome(),"server" + File.separator + "classes");
            packed[
0]     = new File(getCatalinaHome(),"server" + File.separator + "lib");
            catalinaLoader 
= ClassLoaderFactory.createClassLoader(unpacked, packed,commonLoader);
 
            
// $CATALINA_BASE/shared/classes/*.class
            
// $CATALINA_BASE/shared/lib/*.jar
            
// 这些类是仅被tomcat的webapp使用的类,由sharedLoader负责载入

            unpacked[
0]  = new File(getCatalinaBase(),"shared" + File.separator + "classes");
            packed[
0]      = new File(getCatalinaBase(),"shared" + File.separator + "lib");
            sharedLoader 
= ClassLoaderFactory.createClassLoader(unpacked, packed,commonLoader);
                                                     
            
// 注意三个自己定置的ClassLoader的层次关系:
            
// systemClassLoader (root)
            
//   +--- commonLoader
            
//          +--- catalinaLoader
            
//          +--- sharedLoader

                    }
 catch (Throwable t) {
            log(
"Class loader creation threw exception", t);
            System.exit(
1);

        }


        
// 为当前的线程更改其contextClassLoader
        
// 一般的线程默认的contextClassLoader是系统的ClassLoader(所有其它自定义ClassLoader的父亲)
        
// 当该线程需要载入类时,将使用自己的contextClassLoader来寻找并载入类
        
// 更改contextClassLoader可以更改该线程的寻找和载入类的行为,但不影响到其它线程
        
// 注意!Tomcat Server线程使用的是catalinaLoader

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        
// Load our startup class and call its process() method

        
try {
            
// 预载入catalinalLoader的一些类
            SecurityClassLoad.securityClassLoad(catalinaLoader);
            
// 获得tomcat的启动类:org.apache.catalina.startup.Catalina,并创建该类的一个实例
            if (debug >= 1)
                log(
"Loading startup class");
            Class startupClass 
= catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
            Object startupInstance 
= startupClass.newInstance();


            
// 设置startupInstance的父ClassLoader,相当于执行:
            
// Catalina startupInstance = new Catailina();
            
// startupInstance.setParentClassLoader(sharedLoader);
            
// 详情参考类org.apache.catalina.startup.Catalina

            
if (debug >= 1)log("Setting startup class properties");
            String methodName 
= "setParentClassLoader";
            Class paramTypes[] 
= new Class[1];
            paramTypes[
0= Class.forName("java.lang.ClassLoader");
            Object paramValues[] 
= new Object[1];
            paramValues[
0= sharedLoader;
            Method method 
=startupInstance.getClass().getMethod(methodName, paramTypes);
            method.invoke(startupInstance, paramValues);


            
// 使用main方法获得的参数args来执行process方法,相当于:
            
// startupInstance.process(args);
            
// 详情参考类org.apache.catalina.startup.Catalina

            
if (debug >= 1)log("Calling startup class process() method");
            methodName 
= "process";
            paramTypes 
= new Class[1];
            paramTypes[
0= args.getClass();
            paramValues 
= new Object[1];
            paramValues[
0= args;
            method 
=startupInstance.getClass().getMethod(methodName, paramTypes);
            method.invoke(startupInstance, paramValues);

            }
 catch (Exception e) {
            System.out.println(
"Exception during startup processing");
            e.printStackTrace(System.out);
            System.exit(
2);
                 }


    }


    
/**
     * 返回$CATALINA_HOME变量。如果该变量没有定义,则将之赋值为用户的当前工作目录。
     
*/

    
private static String getCatalinaHome() {
        
return System.getProperty("catalina.home",System.getProperty("user.dir"));
    }

 
    
/**
     * 返回$CATALINA_BASE变量。如果该变量没有定义,则将之赋值为$CATALINA_HOME。
     
*/

    
private static String getCatalinaBase() {
        
return System.getProperty("catalina.base", getCatalinaHome());
    }


    
/**
     * 输出LOG信息。
     
*/

    
private static void log(String message) {
        System.out.print(
"Bootstrap: ");
        System.out.println(message);
    }


    
/**
     * 输出由异常引起的LOG信息。
     
*/

    
private static void log(String message, Throwable exception) {
        log(message);
        exception.printStackTrace(System.out);

    }

}

3.2 - org/apache/catalina/startup/ClassLoaderFactory.java
根据设置创建并返回StandardClassLoader的实例

 


package org.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.catalina.loader.StandardClassLoader;


/**
* Utility class for building class loaders for Catalina.The factory
* method requires the following parameters in order to build a new class
* loader (with suitable defaults in all cases):
*    A set of directories containing unpacked classes (and resources)
*         that should be included in the class loader's
*         repositories.
*     
*    A set of directories containing classes and resources in JAR files.
*         Each readable JAR file discovered in these directories will be
*         added to the class loader's repositories.
*     
*    ClassLoader instance that should become the parent of the new class loader.
*     
*
@author Craig R. McClanahan
@version $Revision: 1.8 $ $Date: 2002/02/17 08:26:02 $
*/



public final class ClassLoaderFactory {
   
/**
     * Debugging detail level for processing the startup.
    
*/

    
private static int debug = 0;
    
/**
     * Return the debugging detail level.
     
*/


    
public static int getDebug() {
        
return (debug);
    }

    
/**
     * 设置DEBUG级别
     
*/

    
public static void setDebug(int newDebug) {
        debug 
= newDebug;
    }


    
/**
     * 该类是一个静态类,用来创建和返回ClassLoader对象(实际上是StandardClassLoader对象)
     * 它将根据设置和参数返回apache定置的ClassLoader对象,以完成自己的类载入
     *
     * 
@param unpacked 类路径CLASSPATH的数组
     * 
@param packed 含有JAR文件的类路径
     * 
@param parent 父ClassLoader对象。当一个ClassLoader对象无法完成类载入时,它将请求父对象帮助
     *
     * 
@exception Exception if an error occurs constructing the class loader
     
*/


    
public static ClassLoader createClassLoader(File unpacked[],File packed[], ClassLoader parent)
        
throws Exception {
        
if (debug >= 1)log("Creating new class loader");
        
// list里将被填入所有需要附加到CLASSPATH上去的文件名
        ArrayList list = new ArrayList();
        
// Add unpacked directories
        if (unpacked != null{
            
for (int i = 0; i < unpacked.length; i++)  {
                File file 
= unpacked[i];
                
if (!file.isDirectory() || !file.exists() || !file.canRead())continue;
                
if (debug >= 1)log("  Including directory " + file.getAbsolutePath());
                URL url 
= new URL("file"null,file.getCanonicalPath() + File.separator);
                list.add(url.toString());
            }

        }

        
// Add packed directory JAR files
        if (packed != null{
            
for (int i = 0; i < packed.length; i++{
                File directory 
= packed[i];
                
if (!directory.isDirectory() || !directory.exists() ||!directory.canRead()) continue;
                String filenames[] 
= directory.list();
                
for (int j = 0; j < filenames.length; j++{
                    String filename 
= filenames[j].toLowerCase();
                    
if (!filename.endsWith(".jar"))continue;
                    File file 
= new File(directory, filenames[j]);
                    
if (debug >= 1)log("  Including jar file " + file.getAbsolutePath());
                    URL url 
= new URL("file"null,file.getCanonicalPath());
                    list.add(url.toString());
                }

            }

        }

        
// 填好了list
        
// 创建StandardClassLoader对象,并返回

        String array[] 
= (String[]) list.toArray(new String[list.size()]);
        StandardClassLoader classLoader 
= null;
        
if (parent == null){
           classLoader 
= new StandardClassLoader(array); 
        }
else{
             classLoader 
= new StandardClassLoader(array, parent);
        }
           
        classLoader.setDelegate(
true);
        
return (classLoader);

    }

    
/**
     * 输出日志
     
*/

    
private static void log(String message) {
        System.out.print(
"ClassLoaderFactory:  ");
        System.out.println(message);
    }

    
/**
     * 输出日志和异常信息
     
*/

    
private static void log(String message, Throwable exception) {
        log(message);
        exception.printStackTrace(System.out);
    }


}

3.3 - org/apache/catalina/loader/StandardClassLoader.java
类载入器

3.4 - org/apache/catalina/startup/SecurityClassLoad.java
该类仅包含一个静态方法,用来为catalinaLoader载入一些类

 


package org.apache.catalina.startup;


/**
 * Static class used to preload java classes when using the
 * Java SecurityManager so that the defineClassInPackage
 * RuntimePermission does not trigger an AccessControlException.
 *
 * 
@author Glenn L. Nielsen
 * 
@version $Revision: 1.1 $ $Date: 2001/12/30 01:58:20 $
 
*/




/**
 * 该类只有一个静态方法,其作用仅仅相当于一个函数
 * 该静态方法负责载入一些指定的类
 * package org.apache.catalina.core
 * package org.apache.catalina.connector
 * package org.apache.catalina.loader
 * package org.apache.catalina.session
 * package org.apache.catalina.util
 * 这些包都在$CATALINA_HOME/server/catalina.jar文件中
 * (由此看来,该静态方法的合法参数仅为catalinaLoader?)
 
*/



public final class SecurityClassLoad {

    
static void securityClassLoad(ClassLoader loader)
        
throws Exception {

        
if( System.getSecurityManager() == null )return;

        String basePackage 
= "org.apache.catalina.";
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedGetRequestDispatcher");
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedGetResource");
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedGetResourcePaths");
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedLogMessage");
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedLogException");
        loader.loadClass(basePackage 
+ "core.ApplicationContext$PrivilegedLogThrowable");
        loader.loadClass(basePackage 
+ "core.ApplicationDispatcher$PrivilegedForward");
        loader.loadClass(basePackage 
+ "core.ApplicationDispatcher$PrivilegedInclude");
        loader.loadClass(basePackage 
+ "core.ContainerBase$PrivilegedAddChild");
        loader.loadClass(basePackage 
+ "connector.HttpRequestBase$PrivilegedGetSession");
        loader.loadClass(basePackage 
+ "connector.HttpResponseBase$PrivilegedFlushBuffer");
        loader.loadClass(basePackage 
+ "loader.WebappClassLoader$PrivilegedFindResource");
        loader.loadClass(basePackage 
+ "session.StandardSession");
        loader.loadClass(basePackage 
+ "util.CookieTools");
        loader.loadClass(basePackage 
+ "util.URL");
        loader.loadClass(basePackage 
+ "util.Enumerator");
        loader.loadClass(
"javax.servlet.http.Cookie");

    }

}


posted on 2011-12-27 10:25 Eric_jiang 阅读(617) 评论(1)  编辑  收藏 所属分类: Java

Feedback

# re: ClassLoader介绍 2011-12-27 10:29 Eric_jiang
http://www.jobeast.com/puyufanyi  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: