关于ClassLoader In Tomcat 的研究

Posted on 2007-05-04 09:22 久城 阅读(4133) 评论(7)  编辑  收藏 所属分类: Java转载

收回五一假期那颗破碎的心,继续我的程序人生。

今天打算研究下ClassLoader在Tomcat中的工作。

以下文字来自http://hi.baidu.com/wuzejian/blog/item/678c21e92aa6483eb90e2d21.html

1 - Tomcat的类载入器的结构

Tomcat Server在启动的时候将构造一个ClassLoader树,以保证模块的类库是私有的
Tomcat Server的ClassLoader结构如下:
其中:
- 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);

    }


}
--------------------------------------------------------
           +-----------------------------+
           |            Bootstrap              |
           |                |                  |
           |             System                |
           |                |                  |
           |             Common                |
           |            /         \               |
           |        Catalina     Shared           |
           |                  /       \           |
           |             WebApp1     WebApp2      |
           +-----------------------------+
还有很多让我疑惑的地方,转过来慢慢学习。


欢迎来访!^.^!
本BLOG仅用于个人学习交流!
目的在于记录个人成长.
所有文字均属于个人理解.
如有错误,望多多指教!不胜感激!

Feedback

# re: 关于ClassLoader In Tomcat 的研究[未登录]  回复  更多评论   

2007-05-04 10:38 by Test
testtest

# re: 关于ClassLoader In Tomcat 的研究  回复  更多评论   

2007-05-04 10:56 by BeanSoft
哥们可以研究研究热加载, 很有意思的哦... 不用重启可以更新类库

# re: 关于ClassLoader In Tomcat 的研究  回复  更多评论   

2007-05-04 11:04 by 久城
@BeanSoft
楼上乃高人也。我的导师也提过这个内容。很想研究研究。在tomcat中经常遇到这样的问题,必须要重新启动Tomcat才能加载自己已更新的类。我想,剖析一下Tomcat中的ClassLoader,应该能解决这个问题。正好写在毕业论文里。只是现在可以参考的资料好少..还在探索中..

# re: 关于ClassLoader In Tomcat 的研究  回复  更多评论   

2007-05-04 13:05 by BeanSoft
高人不敢当了. 原来看过一些, 但是感觉热加载还是挺复杂的. 可以参考 Tomcat 里面有 Reload 字样的文集, JBoss 的热加载据说做的比较好. 原来试过, 但是才疏学浅, 遇到些问题.

org\jboss\mx\loading 这个包下面的是类加载

Tomcat 5.0 的位于:
org\apache\catalina\loader

# re: 关于ClassLoader In Tomcat 的研究  回复  更多评论   

2007-05-04 13:23 by 久城
very thanks!

# re: 关于ClassLoader In Tomcat 的研究[未登录]  回复  更多评论   

2008-04-25 21:00 by Jerry
好文章

# re: 关于ClassLoader In Tomcat 的研究  回复  更多评论   

2012-02-16 22:21 by cll
Tomcat 6之后classloader 稍微有些改动了,去掉了server 和 shared, 都有common classloader 来载入了。http://www.jianbozhu.net/2012/02/14/tomcat-classloader-demonstration/ 这篇文章做了个简单的实验,比叫有意思。

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


网站导航:
 

Copyright © 久城