当前位置: > Linux服务器 > Tomcat >

tomcat源码阅读

时间:2016-05-29 22:30来源:linux.it.net.cn 作者:IT
读tomcat的源码的时候,我建议和官方的User Guide一起阅读,明白tomcat做某件事情的目的之后,看源码会容易一点。另外,debug当然是一个非常重要的工具。源码上了规模之后,如果单纯静态地看,基本是看不出什么来的,但是跟着数据流走一走,很多问题就清楚了  

debug环境的搭建方法,请看另外一篇博客:http://zhh2009.iteye.com/blog/1557891。这篇文章写得很清楚了,但是我不太明白为什么需要转换成maven工程,以及为什么需要一个dist版本  

作为本系列的第一篇文章,本文不涉及源码,首先介绍一下tomcat的classloader机制  

参考的文档包括:  

http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html  
servlet-spec-2.4-fr  

一、过时的模型  

在网上搜索“tomcat classloader”,很容易搜索到下图,但是这是一个过时的模型  

   

这个模型是在tomcat5.x使用的,可以看一下tomcat5.x的目录结构  

   

再对比一下tomcat7.x的目录结构  

   

可以看到,5.x里的server、shared、common目录,在7.x中已经废弃了。所以上图中的ClassLoader模型也是过时的  

在tomcat7.x里,ClassLoader的模型应该是下图这样:  

   

至于这个模型中,各个ClassLoader的具体作用,下文会说明  

二、JVM默认的classloader机制  

jvm默认定义了三种classloader,分别是bootstrap classloader、extension classloader、system classloader  

bootstrap是jvm的一部分,用C写的,每一个java程序都会启动它,去加载%JAVA_HOME%/jre/lib/rt.jar  

extension也差不多,它会去加载%JAVA_HOME%/jre/lib/ext/下的类  

system则是会去加载系统变量CLASSPATH下的所有类  

这3个部分,在上面的tomcat classloader模型图中都有体现。不过可以看到extension没有画出来,可以理解为是跟bootstrap合并了,都是去%JAVA_HOME%/jre/lib下面加载类  

另外,java的classloader一般是采用委托机制,即classloader都有一个parent classloader,当它收到一个加载类的请求时,会首先请求parent classloader加载,如果parent classloader加载不到,才会自己去尝试加载(如果自己也加载不到,则抛出ClassNotFoundException)。采用这种机制的目的,主要是从安全角度考虑。比如用户自己定义了一个java.lang.Object,把jdk中的覆盖了,那显然是有问题的  

当然,这个机制不是绝对的,比如在OSGi中,就故意违反了这个模式。后面可以看到,tomcat里的webapp classloader也违反了这个规定  

三、tomcat为什么要自定义classloader  

主要有2个目的,首先是要实现servlet规范中对类加载的要求,其次是实现不同web app的类隔离  

servlet规范中对类加载要求如下:  

This specification defines a hierarchical structure used for deployment and  
packaging purposes that can exist in an open file system, in an archive file, or in some other form. It is recommended, but not required, that servlet containers  
support this structure as a runtime representation.  

Web applications can be packaged and signed into a Web ARchive format (WAR)  
file using the standard Java archive tools. For example, an application for issue  
tracking might be distributed in an archive file called issuetrack.war.  
When packaged into such a form, a META-INF directory will be present which  
contains information useful to Java archive tools. This directory must not be  
directly served as content by the container in response to a Web client’s request, though its contents are visible to servlet code via the getResource and getResourceAsStream calls on the ServletContext. Also, any requests to access the  
resources in META-INF directory must be returned with a SC_NOT_FOUND(404)  
response.  

四、各classloader详细说明  

4.1 Bootstrap — This class loader contains the basic runtime classes provided by the Java Virtual Machine, plus any classes from JAR files present in the System Extensions directory ($JAVA_HOME/jre/lib/ext). Note: some JVMs may implement this as more than one class loader, or it may not be visible (as a class loader) at all. 

4.2 System — 这个classloader通常是由CLASSPATH这个环境变量初始化的,通过这个classloader加载的所有类,都对tomcat自身的类,以及所有web应用的类可见。但是,标准的tomcat启动脚本($CATALINA_HOME/bin/catalina.bat),完全无视默认的CLASSPATH环境变量,而是加载了以下3个.jar  

$CATALINA_HOME/bin/bootstrap.jar — Contains the main() method that is used to initialize the Tomcat server, and the class loader implementation classes it depends on.  

$CATALINA_HOME/bin/tomcat-juli.jar — Logging implementation classes. These include enhancement classes to java.util.logging API, known as Tomcat JULI, and a package-renamed copy of Apache Commons Logging library used internally by Tomcat.  

$CATALINA_HOME/bin/commons-daemon.jar — The classes from Apache Commons Daemon project.(这个类不是直接在$CATALINA_HOME/bin/catalina.bat里加进来的,不过在bootstrap.jar的manifest文件中包含进来了)  

4.3 Common — 这个classloader加载的类,对tomcat的类和web app的类都是可见的。通常来说,应用程序的类不应该放在这里。该加载器的加载路径是在$CATALINA_BASE/conf/catalina.properties文件里,通过common.loader属性来定义的,默认是:  
Text代码   收藏代码
  1. common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar  

By default, this includes the following:  

annotations-api.jar — JavaEE annotations classes.  
catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.  
catalina-ant.jar — Tomcat Catalina Ant tasks.  
catalina-ha.jar — High availability package.  
catalina-tribes.jar — Group communication package.  
ecj-*.jar — Eclipse JDT Java compiler.  
el-api.jar — EL 2.2 API.  
jasper.jar — Tomcat Jasper JSP Compiler and Runtime.  
jasper-el.jar — Tomcat Jasper EL implementation.  
jsp-api.jar — JSP 2.2 API.  
servlet-api.jar — Servlet 3.0 API.  
tomcat-api.jar — Several interfaces defined by Tomcat.  
tomcat-coyote.jar — Tomcat connectors and utility classes.  
tomcat-dbcp.jar — Database connection pool implementation based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP.  
tomcat-i18n-**.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.  
tomcat-jdbc.jar — An alternative database connection pool implementation, known as Tomcat JDBC pool. See documentation for more details.  
tomcat-util.jar — Common classes used by various components of Apache Tomcat.  

4.4 WebappX — 该classloader加载所有WEB-INF/classes里的类,以及WEB-INF/lib里的jar  

该classloader就有意违反了上述的委托模型,它首先看WEB-INF/classes和WEB-INF/lib里是否有请求的类,而不是委托parent classloader去加载。但是,JRE里定义的类不能被覆盖(比如java.lang.String),以及Servlet API会明确地被忽略。  

前面说的bootstrap、system、common,都遵循普通的委托模型  

4.5 总的来说,从web app的角度来看,类或者资源加载是按照以下的顺序来查找的:  

Bootstrap classes of your JVM(rt.jar)  
System class loader classes(bootstrap.jar、tomcat-juli.jar、commons-deamon.jar)  
/WEB-INF/classes of your web application  
/WEB-INF/lib/*.jar of your web application  

Common class loader classes (在$CATALINA_HOME/lib里的jar包)

一、工具准备 

需要SVN、Maven、JDK、Eclipse、M2Eclipse 

二、下载源码及发布包 

源码在: 
http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_27/ 

发布包在: 
http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.27/bin/ 

说明:下载发布包这个步骤是可选的,好处是免得从源码再自行构建,节省时间;另外发布包里的配置文件等,接下来可以直接拿来用,很方便 

三、整理目录 

前面下载得到了源码和发布包,现在要把它们放到同一个目录里,再整理一下,方便后面把它转化成eclipse工程,毕竟后续读源码,以及调试,都要在eclipse里完成 

新建一个单独的目录,叫tomcat7.0.27,然后把刚才下载的源码和发布包都放进去。源码目录重命名为code;发布包重命名为launch 

得到的目录结构见下图: 

 

一会就会把这个目录导入eclipse,变成可运行,可调试的eclipse工程 

四、转换成maven工程 

将附件中的pom.xml放入目录,与code、launch目录平行 

得到的目录结构见下图: 

 

说明:这也不是必须的,只是为了方便 

五、导入eclipse 

 

 

导入成功以后,eclipse里的工程目录结构如下图: 

 

接下来就可以在eclipse里运行和调试tomcat了,也可以随意修改源代码,或者自己添加测试用例 

六、启动tomcat 

tomcat启动入口类是:org.apache.catalina.startup.Bootstrap 

平时我们用发布包启动tomcat一般是用脚本startup.bat或者startup.sh,其实就是在脚本中先处理启动参数和系统变量,然后调用这个入口类的main()方法 

所以在eclipse里启动,我们也是直接执行这个类的main()方法,只是模拟脚本,设置一下启动参数和系统变量 

方法1: 

在VM arguments中,拷贝以下参数 

-Dcatalina.home=launch -Dcatalina.base=launch -Djava.endorsed.dirs=launch/endorsed -Djava.io.tmpdir=launch/temp -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=launch/conf/logging.properties 

如图: 

 

方法2: 

将附件中的启动脚本,拷贝到工程目录下,结构如下图: 

 

然后直接在start-tomcat7.launch上右键点击,run就可以 

启动效果如下图: 

 

眼熟,和普通的脚本启动,以及启动嵌入式tomcat的信息都是一样的 

最后用浏览器访问:http://localhost:8080/examples/ 

七、tomcat7核心架构 


 

包名 作用
javax.* 各种JSR的API,如jsp、servlet、el等
org.apache.catalina tomcat自身架构
org.apache.coyote http、ajp协议实现
org.apache.el EL规范实现
org.apache.jasper JSP规范实现
org.apche.juli 日志
org.apache.naming JNDI实现
org.apache.tomcat 工具包、XML解析器等
1、ClassLoader结构  

   

tomcat的ClassLoader模型如上图,主要是为了满足servlet规范中类隔离的要求(见JSR154的Section9.4、9.6、9.7)  

1.1 Bootstrap  

这个类加载器和普通的JAVA应用一样,都是由JVM启动的,加载%JAVA_HOME%/jre/lib下的JAR包,如rt.jar等  

通常情况下,Bootstrap和Extension是分开考虑的,但是在tomcat的ClassLoader体系里,没有将二者区分开。当谈到Bootstrap时,就包括了Bootstrap和Extension  

1.2 System  

System类加载器,也叫App类加载器,一般就是启动应用程序的那个加载器,是根据classpath创建的  

但是在tomcat里,完全忽略了默认的classpath,而是根据指定的参数,直接创建System类加载器,默认情况下,会加载%CATALINA_HOME%/bin目录下的bootstrap.jar、tomcat-juli.jar、commons-daemon.jar  

这些jar包充当了启动入口的角色,但是tomcat真正的核心实现类,不是在这个ClassLoader里加载的,所以后面会提到,源码里调用核心实现类(Catalina等)的方法,必须指定ClassLoader,通过反射完成  

1.3 Common  

这个类加载器是tomcat特有的,对于所有web app可见。这个类加载器默认会加载%CATALINA_HOME%/lib下的所有jar包,这些都是tomcat的核心  

1.4 WebappX  

对于部署在容器中的每一个webapp,都有一个独立的ClassLoader,在这里实现了不同应用的类隔离  

这里的ClassLoader与标准的ClassLoader委托模型不同,当需要加载一个类的时候,首先是委托Bootstrap和System;然后尝试自行加载;最后才会委托Common  

加载顺序如下:  

Bootstrap -> System -> WEB-INF/classes -> WEB-INF/lib -> Common  

2、疑问  

Bootstrap.class和Catalina.class是打在不同的JAR包里的。前者在bootstrap.jar里,后者在catalina.jar里  

并且这2个jar包,是由不同的ClassLoader加载的。前者由System ClassLoader加载,后者由Common ClassLoader加载  

这造成代码比较麻烦,Bootstrap.class要引用Catalina.class的时候,不是直接引用,而是通过反射实现  

我还没搞清楚tomcat这样设计的原因,跟类隔离貌似没有直接关系  

3、tomcat启动  

3.1 执行脚本  

tomcat启动是从运行startup.bat脚本开始的,在此脚本中首先会设置一系列环境变量,然后配置参数,最后实际上执行的catalina.bat脚本。所以用catalina.bat start命令,也可以启动tomcat  

3.2 加载Bootstrap类  

在catalina.bat中,会将classpath设置为%CATALINA_HOME%/bin/bootstrap.jar和%CATALINA_HOME%/bin/tomcat-juli.jar,然后根据此classpath创建System类加载器,加载bootstrap.jar中的Bootstrap.class,执行main()方法  

3.3 调用Catalina类  

在Bootstrap.class里,读取配置文件(%CATALINA_HOME%/conf/catalina.properties),然后创建Common ClassLoader,加载%CATALINA_HOME%/lib里的所有jar包  

之后根据实际的命令行参数,调用org.apache.catalina.startup.Catalina中相应的方法,比如start()等  

4、参考文档  

http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html

前几天想了一下,最近主要学习linux和httpd,所以tomcat源码阅读先放一放,可能到9月份左右再继续。不过先把已经写好的几篇陆续贴上来 

tomcat用到很多ClassLoader相关的代码,如果缺乏这方面的背景知识,阅读源码会遇到很多障碍,所以本文首先总结一下这方面的内容,和tomcat源码的关系不大 

1 标准的ClassLoader体系 

 

1.1 bootstrap 

bootstrap classloader是由JVM启动的,用于加载%JAVA_HOME%/jre/lib/下的JAVA平台自身的类(比如rt.jar中的类等)。这个classloader位于JAVA类加载器链的顶端,是用C/C++开发的,而且JAVA应用中没有任何途径可以获取到这个实例,它是JDK实现的一部分 

1.2 extension 

entension classloader用于加载%JAVA_HOME%/jre/lib/ext/下的类,它的实现类是sun.misc.Launcher$ExtClassLoader,是一个内部类 

基本上,我们开发的JAVA应用都不太需要关注这个类 

1.3 system 

system classloader是jvm启动时,根据classpath参数创建的类加载器(如果没有显式指定classpath,则以当前目录作为classpath)。在普通的JAVA应用中,它是最重要的类加载器,因为我们写的所有类,通常都是由它加载的。这个类加载器的实现类是sun.misc.Launch$AppClassLoader 

用ClassLoader.getSystemClassLoader(),可以得到这个类加载器 

1.4 custom 

一般情况下,对于普通的JAVA应用,ClassLoader体系就到system为止了。平时编程时,甚至都不会感受到classloader的存在 

但是对于其他一些应用,比如web server,插件加载器等,就必须和ClassLoader打交道了。这时候默认的类加载器不能满足需求了(类隔离、运行时加载等需求),需要自定义类加载器,并挂载到ClassLoader链中(默认会挂载到system classloader下面) 

2 双亲委派模型 

从上面的图可以看到,classloader链,是一个自上而下的树形结构。一般来说,java中的类加载,是遵循双亲委派模型的,即: 

当一个classloader要加载一个类时,首先会委托给它的parent classloader来加载,如果parent找不到,才会自己加载。如果最后也找不到,则会抛出熟悉的ClassNotFoundException 

这个模型,是在最基础的抽象类ClassLoader里确定的: 

Java代码   收藏代码
  1. protected synchronized Class<?> loadClass(String name, boolean resolve)  
  2.     throws ClassNotFoundException  
  3.     {  
  4.     // First, check if the class has already been loaded  
  5.     Class c = findLoadedClass(name);  
  6.     if (c == null) {  
  7.         try {  
  8.         if (parent != null) {  
  9.             c = parent.loadClass(name, false);  
  10.         } else {  
  11.             c = findBootstrapClassOrNull(name);  
  12.         }  
  13.         } catch (ClassNotFoundException e) {  
  14.                 // ClassNotFoundException thrown if class not found  
  15.                 // from the non-null parent class loader  
  16.             }  
  17.             if (c == null) {  
  18.             // If still not found, then invoke findClass in order  
  19.             // to find the class.  
  20.             c = findClass(name);  
  21.         }  
  22.     }  
  23.     if (resolve) {  
  24.         resolveClass(c);  
  25.     }  
  26.     return c;  
  27.     }  

自定义ClassLoader的时候,一般来说,需要做的并不是覆盖loadClass()方法,这样的话就“破坏”了双亲委派模型;需要做的只是实现findClass()方法即可  

不过,从上面的代码也可以看出,双亲委派模型只是一种“建议”,并没有强制保障的措施。如果自定义的ClassLoader无视此规定,直接自行加载,不将请求委托给parent,当然也是没问题的  

在实际情况中,双亲委派模型被“破坏”也是很常见的。比如在tomcat里,webappx classloader就不会委托给上层的common classloader,而是先委托给system,然后自己加载,最后才委托给common;再比如说在OSGi里,更是有意完全打破了这个规则  

当然,对于普通的JAVA应用开发来说,需要自定义classloader的场景本来就不多,需要去违反双亲委派模型的场景,更是少之又少  

3 自定义ClassLoader   

3.1 自定义ClassLoader的一般做法  

从上面的代码可以看到,自定义ClassLoader很简单,只要继承抽象类ClassLoader,再实现findClass()方法就可以了  

3.2 自定义ClassLoader的场景  

事实上,需要实现新的ClassLoader的场景是很少的  

注意:需要增加一个自定义ClassLoader的场景很多;但是,需要自己实现一个新的ClassLoader子类的场景不多。这是两回事,不可混淆  

比如,即使在tomcat里,也没有自行实现新的ClassLoader子类,只是创建了URLClassLoader的实例,作为custom classloader  

3.3 ClassLoader的子类  

在JDK中已经提供了若干ClassLoader的子类,在需要的时候,可以直接创建实例并使用。其中最常用的是URLClassLoader,用于读取一个URL下的资源,从中加载Class  
Java代码   收藏代码
  1. @Deprecated  
  2. public class StandardClassLoader  
  3.     extends URLClassLoader  
  4.     implements StandardClassLoaderMBean {  
  5.   
  6.     public StandardClassLoader(URL repositories[]) {  
  7.         super(repositories);  
  8.     }  
  9.   
  10.     public StandardClassLoader(URL repositories[], ClassLoader parent) {  
  11.         super(repositories, parent);  
  12.     }  
  13.   
  14. }  

可以看到,tomcat就是在URLClassLoader的基础上,包装了StandardClassLoader,实际上并没有任何功能上的区别  

3.4 设置parent  

在抽象类ClassLoader中定义了一个parent字段,保存的是父加载器。但是这个字段是private的,并且没有setter方法  

这就意味着只能在构造方法中,一次性地设置parent classloader。如果没有设置的话,则会默认将system classloader设置为parent,这也是在ClassLoader类中确定的:  
Java代码   收藏代码
  1. protected ClassLoader(ClassLoader parent) {  
  2.         this(checkCreateClassLoader(), parent);  
  3.     }  
  4.   
  5. protected ClassLoader() {  
  6.         this(checkCreateClassLoader(), getSystemClassLoader());  
  7.     }  


4 ClassLoader隐性传递   

“隐性传递”这个词是我乱造的,在网上和注释中没有找到合适的描述的词  

试想这样一种场景:在应用中需要加载100个类,其中70个在classpath下,默认由system来加载,这部分不需要额外处理;另外30个类,由自定义classloader加载,比如在tomcat里:  
Java代码   收藏代码
  1. Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");  
  2. Object startupInstance = startupClass.newInstance();  

如上,org.apache.catalina.startup.Catalina是由自定义类加载器加载的,需要额外编程来处理(如果是system加载的,直接new就可以了)  

如果30个类,都要通过这种方式来加载,就太麻烦了。不过classloader有一个特性,就是“隐性传递”,即:  

如果一个ClassA是由某个ClassLoader加载的,那么ClassA中依赖的需要加载的类,默认也会由同一个ClassLoader加载  

这个机制是由JVM保证的,对于程序员来说是透明的  

5 current classloader   

5.1 定义  

与前面说的extension、system等不同,在运行时并不存在一个实际的“current classloader”,只是一个抽象的概念。指的是一个类“当前的”类加载器。一个对象实例所属的Class,是由哪一个ClassLoader加载的,这个ClassLoader就是这个对象实例的current classloader  

获得的方法是:  
Java代码   收藏代码
  1. this.getClass().getClassLoader();  


5.2 实例  

current classloader概念的意义,主要在于它会影响Class.forName()方法的表现,贴一段代码进行说明:  
Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public void tryForName() {  
  4.   
  5.         System.out.println("current classloader: "  
  6.                 + this.getClass().getClassLoader());  
  7.   
  8.         try {  
  9.             Class.forName("net.kyfxbl.test.cl.Target");  
  10.                         System.out.println("load class success");  
  11.         } catch (ClassNotFoundException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.   
  15.     }  
  16.   
  17. }  

这个类调用了Class.forName()方法,试图加载net.kyfxbl.test.cl.Target类(Target是一个空类,作为加载目标,不重要)。这个类在运行时能否加载Target成功,取决于它的current classloader,能不能加载到Target  

首先,将Test和Target打成jar包,放到classpath之外,jar包中内容如下:  

 

然后在工程中删除Target类(classpath中加载不到Target了)  

在Main中用system 加载Test,此时Test的current classloader是system,加载Target类失败  
Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.   
  3.         Test t = new Test();  
  4.         t.tryForName();  
  5.   
  6.     }  

然后,这次用自定义的classloader来加载  
Java代码   收藏代码
  1. public static void main(String[] args) throws Exception {  
  2.   
  3.         ClassLoader cl = createClassLoader();  
  4.   
  5.         Class<?> startupClass = cl.loadClass("net.kyfxbl.test.cl.Test");  
  6.         Object startupInstance = startupClass.newInstance();  
  7.   
  8.         String methodName = "tryForName";  
  9.         Class<?>[] paramTypes = new Class[0];  
  10.         Object[] paramValues = new Object[0];  
  11.         Method method = startupInstance.getClass().getMethod(methodName,  
  12.                 paramTypes);  
  13.         method.invoke(startupInstance, paramValues);  
  14.   
  15.     }  
  16.   
  17.     private static ClassLoader createClassLoader() throws MalformedURLException {  
  18.   
  19.         String filePath = "c://hehe.jar";  
  20.         File file = new File(filePath);  
  21.         URL url = file.toURI().toURL();  
  22.         URL[] urls = new URL[] { url };  
  23.         ClassLoader myClassLoader = new URLClassLoader(urls);  
  24.         return myClassLoader;  
  25.   
  26.     }  

在想象中,这次Test的current classloader应该变成URLClassLoader,并且加载Target成功。但是还是失败了  

这是因为前面说过的“双亲委派模型”,URLClassLoader的parent是system classloader,由于工程里的Test类没有删除,所以classpath里还是能找到Test类,所以Test类的current classloader依然是system classloader,和第一次一样  

   

接下来把工程里的Test类也删除,这次就成功了  

   

5.3 Class.forName()  

前面说的是单个参数的forName()方法,默认使用current ClassLoader  

除此之外,Class类还定义了3个参数的forName()方法,方法签名如下:  
Java代码   收藏代码
  1. public static Class<?> forName(String name, boolean initialize,  
  2.                    ClassLoader loader)  
  3.         throws ClassNotFoundException  

这个方法的最后一个参数,可以传递一个ClassLoader,会用这个ClassLoader进行加载。这个方法很重要  

比如像JNDI,主体类是在JDK包里,由bootstrap加载。而SPI的实现类,则是由厂商提供,一般在classpath里。那么在JNDI的主体类里,要加载SPI的实现类,直接用Class.forName()方法肯定是不行的,这时候就要用到3个参数的Class.forName()方法了  

6 ContextClassLoader   

6.1 获取ClassLoader的API  

前面说过,已经有2种方式可以获取到ClassLoader的引用  

一种是ClassLoader.getSystemClassLoader(),获取的是system classloader  

另一种是getClass().getClassLoader(),获取的是current classloader  

这2种API都只能获取classloader,没有办法用来传递  

6.2 传递ClassLoader  

每一个thread,都有一个contextClassLoader,并且有getter和setter方法,用来在线程之间传递ClassLoader  

有2条默认的规则:  

首先,contextClassLoader默认是继承的,在父线程中创建子线程,那么子线程会继承父线程的contextClassLoader  

其次,主线程,也就是执行main()方法的那个线程,默认的contextClassLoader是system classloader  

6.3 例子  

对上面例子中的Test和Main稍微改一下(Test和Target依然打到jar包里,然后从工程中删除,避免被system classloader加载到)  
Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public void tryForName() {  
  4.   
  5.         System.out.println("current classloader: "  
  6.                 + getClass().getClassLoader());  
  7.         System.out.println("thread context classloader: "  
  8.                 + Thread.currentThread().getContextClassLoader());  
  9.   
  10.         try {  
  11.             Class.forName("net.kyfxbl.test.cl.Target");  
  12.             System.out.println("load class success");  
  13.         } catch (Exception exc) {  
  14.             exc.printStackTrace();  
  15.         }  
  16.   
  17.     }  
  18. }  

Java代码   收藏代码
  1. public static void main(String[] args) throws Exception {  
  2.   
  3.         ClassLoader cl = createClassLoader();  
  4.   
  5.         Class<?> startupClass = cl.loadClass("net.kyfxbl.test.cl.Test");  
  6.         final Object startupInstance = startupClass.newInstance();  
  7.   
  8.         new Thread(new Runnable() {  
  9.   
  10.             @Override  
  11.             public void run() {  
  12.                 String methodName = "tryForName";  
  13.                 Class<?>[] paramTypes = new Class[0];  
  14.                 Object[] paramValues = new Object[0];  
  15.                 try {  
  16.                     Method method = startupInstance.getClass().getMethod(  
  17.                             methodName, paramTypes);  
  18.                     method.invoke(startupInstance, paramValues);  
  19.                 } catch (Exception exc) {  
  20.                     exc.printStackTrace();  
  21.                 }  
  22.             }  
  23.         }).start();  
  24.   
  25.     }  

这次的tryForName()方法在一个子线程中被调用,并依次打印出current classloader和contextClassLoader,如图:  

   

可以看到,子线程继承了父线程的contextClassLoader  

同时可以注意到,contextClassLoader对Class.forName()方法没有影响,contextClassLoader只是起到在线程之间传递ClassLoader的作用  

6.4 题外话  

从这个例子还可以看出,一个方法在运行时的表现,在编译期是无法确定的  

在运行时的表现,有时候取决于方法所在的类是被哪个ClassLoader加载;有时候取决于是运行在单线程环境下,还是多线程环境下  

这在编译期是不可知的,所以在编程的时候,要考虑运行时的情况。比如所谓“线程安全”的类,并不是说它“一定”会运行在多线程环境下,而是说它“可以”运行在多线程环境下  

7 总结   

本文大致总结了ClassLoader的背景知识。掌握了背景,再阅读tomcat的源码,基本就不会遇到ClassLoader方面的困难  

本文介绍了ClassLoader的标准体系、双亲委派模型、自定义ClassLoader的方法、以及current classloader和contextClassLoader的概念  

其中最重要的是current classloader和contextClassLoader  

用于获取ClassLoader的API主要有3种:  

ClassLoader.getSystemClassLoader();  
Class.getClassLoader();  
Thread.getContextClassLoader();  

第一个是静态方法,返回的永远是system classloader,也就是说,没什么用  
后面2个都是实例方法,一个是返回实例所属的类的ClassLoader;另一个返回当前线程的contextClassLoader,具体的结果都要在运行时才能确定  

其中,contextClassLoader可以起到传递ClassLoader的作用,所以特别重要



(责任编辑:IT)
------分隔线----------------------------
栏目列表
推荐内容