精华内容
下载资源
问答
  • Tomcat源码下载

    千次阅读 2018-01-17 22:11:01
    Tomcat官网:http://tomcat.apache.org/Tomcat各版本源码:http://archive.apache.org/dist/tomcat/比如,Tomcat-6.0.01源码下载地址为:http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.1/src/

    Tomcat官网:http://tomcat.apache.org/

    Tomcat各版本源码:http://archive.apache.org/dist/tomcat/

    比如,Tomcat-6.0.01源码的下载地址为:http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.1/src/



    展开全文
  • Tomcat源码下载

    2012-03-30 12:11:23
    接下来我要写的文章是Tomcat源码系列的文章,文章(一)是关于Tomcat源码编译和如何将Tomcat工程导入Eclips的,这里分享一下编译经验。 大家都知道Java的开源项目很强大,正是这些开源项目促进大量优秀的程序员进步...
    由于现在项目相对比较轻松,而且项目是研究SOA和Web Service的,因此这里我要对一些相关的开源技术做一些研究。接下来我要写的文章是Tomcat源码系列的文章,文章(一)是关于Tomcat源码编译和如何将Tomcat工程导入Eclips的,这里分享一下编译经验。
    大家都知道Java的开源项目很强大,正是这些开源项目促进大量优秀的程序员进步和推动软件的快速发展。了解和看过开源项目的程序员不少,但是真正参与进去的程序开发者就不多了。其实只有真正参与到开源项目中,我们的开发能力就会大大增强,我们的开发者就会更加优秀、更善于软件管理,我们的软件产业也会越做越强。
    我觉得可以用构建和谐社会的一句话来描述开源软件,那就是:在共享中共建,在共建中共享。好了,不说这么多了,开始我们的Tomcat源码之旅了。

    1.下载Tomcat源码

    请先从 [url]http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91[/url] 下载版本控制工具SubVersion,下载到的是svn-win32-1.5.4.zip,解压后将bin路径放到环境变量path下,就可以使用这个版本控制工具了。
    在MS-DOS窗口中输入:svn export [url]http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_18/[/url] F:\project\tomcat\apache-tomcat-6.0.18-src\ 这里我把要下载的源码放到F盘的这个目录了。TOMCAT_6_0_18是当前最新的源码版本。
    该目录显示的就是Tomcat源码目录,这里的output目录和eclipse_output目录是没有的,这是我之后编译生成的目录。因此大家需要注意。

    2.编译并运行

    使用Ant工具进行代码编译。从 [url]http://ant.apache.org/bindownload.cgi[/url] 处下载Ant工具。我下载的是apache-ant-1.7.1-bin.zip,解压后把bin路径放到环境变量path下就可以使用了。
    把该目录下的build.properties.default文件改名为:build.properties。打开build.properties,找到base.path=/usr/share/java,改为base.path=F:/project/share。F盘下的这个目录是我本地的存在的目录,读者可以设置自己电脑上一个存在的目录即可。
    在Tomcat编译过程中,Ant会让我们下载一些必要的依赖项目,base.path目录就是用来保存这些项目文件的,我们可以将这个属性指向一个已经存在的目录。修改完base.path后,我们回到MS-DOS窗口,切换到Tomcat源代码所在目录,然后运行ant download命令,如下图所示:


    这次我们等的时间较长,因为eclipse-JDT-3.2.zip大约有20多M,下载需要一段时间。一段时间后,编译器可能给出几个警告。这时我们可发现刚才创建的base.path目录(F:/project/share)中已经下载了6个依赖项目,它们都是Tomcat编译所必须的。
    下面就开始真正的编译任务了,请在MS-DOS窗口内键入ant并回车,Ant将在2分钟内编译1000多个源文件并将Tomcat部署到output目录。编译顺利完成后,请打开Tomcat的源代码目录,会发现多了一个output目录,这是Ant的编译后的输出目录。请打开Tomcat源代码的output\build\bin子目录,双击startup.bat文件,我们即可成功启动Tomcat6.0,如果不成功,请注意设置环境变量CATALINA_HOME,设置为F:\project\tomcat\apache-tomcat-6.0.18-src\output\build。此时我们的编译工作就算顺利完成了。
    这里可能会有错误出现:
    (1)执行ant download命令时出现错误,原因可能是找不到build.properties文件中的行 jdt.loc=http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops/R-3.3.1-200709211145/eclipse-JDT-3.3.1.zip 这个包,读者在网上搜索到这个包后,把网址换一下即可。不过我用的都是最新的版本,至少在一段时间内都是可用的。
    (2)执行ant命令时出错,原因有很多。我遇到的错误是如下错误:

    [javac] D:\tomcat\share\tomcat6-deps\dbcp\src\java\org\apache\tomcat\dbcp\db
    cp\BasicDataSource.java:43: org.apache.tomcat.dbcp.dbcp.BasicDataSource 不是抽象
    的,并且未覆盖 java.sql.Wrapper 中的抽象方法 isWrapperFor(java.lang.Class<?>)
    [javac] public class BasicDataSource implements DataSource {
    [javac] ^
    [javac] D:\tomcat\share\tomcat6-deps\dbcp\src\java\org\apache\tomcat\dbcp\db
    cp\DelegatingStatement.java:45: org.apache.tomcat.dbcp.dbcp.DelegatingStatement
    不是抽象的,并且未覆盖 java.sql.Statement 中的抽象方法 isPoolable()

    等等一大堆错误,这个原因是由于我使用的JDK版本是1.6的,所以会出现这个问题,把JDK版本换成1.5的,把JAVA_HOME也换成1.5版本的JDK即可,这些错误就解决了。还有一些警告,不过没有关系。

    如果读者在编译过程中还有其他错误和问题,请留言联系我。

    3.导入源码到Eclipse中

    (1)打开Eclipse,新建一个Java工程,选择“Create project from existing source”,导入现有的代码工程,如下图所示:


    点击“Finish”完成工程创建。
    (2)导入完成后,会看到一些错误提示。右击工程tomcat,选择"build Path"->Config Build path ,删除两个以TOMCAT_LIBS开头的两个库,保留JRE库和JUnit库。
    (3)把ant.jar(刚才下载的ant中的lib里有)和org.eclipse.jdt.core_3.3.1.v_780_R33x.jar(在刚才设置的F:\project\share中有)copy到代码工程下的lib文件夹下(lib文件夹之前没有,需要手动新建)。选择“Config Build path”->"Add library",加入一个User library,把这两个jar包加进来,命名成tomcat_lib。在Tomcat最新版本6.0.18中,有两个.java代码文件:ServiceProxy.java和ServiceRefFactory.java中用到了一些其他包中的类,如import javax.xml.rpc.Service和import javax.wsdl.Definition等类,这些类分别在包jaxrpc.jar和wsdl4j-1.5.1.jar中,这两个包都在axis2的lib中,我下载的版本是axis-bin-1_4.zip,下载地址是:[url]http://apache.freelamp.com/ws/axis/1_4/axis-bin-1_4.zip [/url] 解压后在lib目录可以看到这两个jar包,直接加入到工程即可。
    添加完成后,如下图所示:


    (4)运行tomcat:找到tomcat的启动主类org.apache.catalina.startup包下的Bootstrap类,右击->“Run AS”->"Run Configurations",在Arguments窗口中做如下设置:
    在Program arguments中写入:start
    在VM arguments中写入:-Dcatalina.home="F:\project\tomcat\apache-tomcat-6.0.18-src\output\build" 。如下图所示:


    然后运行,可以看到tomcat启动成功,如下图所示:


    (5)停止tomcat:跟上面步骤一样,在Program arguments中写入:stop,点击运行。即可停止tomcat。

    4.总结

    到此为止,我们的tomcat源码的两种编译方式和导入Eclipse都已经完成,这里之所以导入到Eclipse是为了方便读代码,也可以对代码进行单步调式,看看代码的执行过程,这是比较有意义的事情。好了接下来的任务就是深入代码内部了,这是一个令人激动的事情,因为tomcat是一个很优秀的项目,其代码也很有诱惑力。不说了,我开始欣赏代码了,以后的文章就会跟大家分享代码的细节了,还去有兴趣读者关注和一起讨论。由于工程比较大,远远超过2M,我就不上传了,有需要的读者可以留言,我发给大家。
    展开全文
  • tomcat源码的时候,我建议和官方的User Guide一起阅读,明白tomcat做某件事情的目的之后,看源码会容易一点。另外,debug当然是一个非常重要的工具。源码上了规模之后,如果单纯静态地看,基本是看不出什么来的...

    读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”,很容易搜索到下图,但是这是一个过时的模型

    7ac4f2e57b808e42c7e36336a621c078.png

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

    a2d0c6fa01de7195a6ea6aa2fd7f69b7.png

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

    9d5cc5b21176dbae0c0353c97769b619.png

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

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

    bf6d6455b542e3dd945efda33b3faec6.png

    至于这个模型中,各个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代码

    899e257fdc900833bb965036634b92b3.png

    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

    得到的目录结构见下图:

    1ea71081c9b895e619f7acacf8bf12a4.png

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

    四、转换成maven工程

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

    得到的目录结构见下图:

    592448e831d89f58038d545e32b33f3f.png

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

    五、导入eclipse

    901b70bb2b97188555e93c885fe1d5e7.png

    9cad5760f60f8067701aec2a8b721b95.png

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

    aebc4412e813183147fa0eb832f37546.png

    接下来就可以在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

    如图:

    6df9915415fcfb3f49d1394a3f989bad.png

    方法2:

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

    627251ce2064a1f07e2e5df80d1e2b06.png

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

    启动效果如下图:

    dc311f4bb537f70c00a851c1a39719af.png

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

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

    七、tomcat7核心架构

    acea31a3409300e41d0ce520607f80d3.png

    包名

    作用

    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结构

    e536f3c09824924fe20de56c6f25c1b9.png

    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、参考文档

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

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

    1 标准的ClassLoader体系

    43fee49b303827945e8dafc97c70d993.png

    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代码

    cf2e1763c8893427a90caf2ff8184290.png

    protected synchronized Class> loadClass(String name, boolean resolve)

    throws ClassNotFoundException

    {

    // First, check if the class has already been loaded

    Class c = findLoadedClass(name);

    if (c == null) {

    try {

    if (parent != null) {

    c = parent.loadClass(name, false);

    } else {

    c = findBootstrapClassOrNull(name);

    }

    } catch (ClassNotFoundException e) {

    // ClassNotFoundException thrown if class not found

    // from the non-null parent class loader

    }

    if (c == null) {

    // If still not found, then invoke findClass in order

    // to find the class.

    c = findClass(name);

    }

    }

    if (resolve) {

    resolveClass(c);

    }

    return c;

    }

    自定义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代码

    cf2e1763c8893427a90caf2ff8184290.png

    @Deprecated

    public class StandardClassLoader

    extends URLClassLoader

    implements StandardClassLoaderMBean {

    public StandardClassLoader(URL repositories[]) {

    super(repositories);

    }

    public StandardClassLoader(URL repositories[], ClassLoader parent) {

    super(repositories, parent);

    }

    }

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

    3.4 设置parent

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

    这就意味着只能在构造方法中,一次性地设置parent classloader。如果没有设置的话,则会默认将system classloader设置为parent,这也是在ClassLoader类中确定的:

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    protected ClassLoader(ClassLoader parent) {

    this(checkCreateClassLoader(), parent);

    }

    protected ClassLoader() {

    this(checkCreateClassLoader(), getSystemClassLoader());

    }

    4 ClassLoader隐性传递

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

    试想这样一种场景:在应用中需要加载100个类,其中70个在classpath下,默认由system来加载,这部分不需要额外处理;另外30个类,由自定义classloader加载,比如在tomcat里:

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    Class> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");

    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代码

    cf2e1763c8893427a90caf2ff8184290.png

    this.getClass().getClassLoader();

    5.2 实例

    current classloader概念的意义,主要在于它会影响Class.forName()方法的表现,贴一段代码进行说明:

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    public class Test {

    public void tryForName() {

    System.out.println("current classloader: "

    + this.getClass().getClassLoader());

    try {

    Class.forName("net.kyfxbl.test.cl.Target");

    System.out.println("load class success");

    } catch (ClassNotFoundException e) {

    e.printStackTrace();

    }

    }

    }

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

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

    7f7f7357ace3c5aec5891162f9e1ea26.png

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

    在Main中用system 加载Test,此时Test的current classloader是system,加载Target类失败

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    public static void main(String[] args) {

    Test t = new Test();

    t.tryForName();

    }

    然后,这次用自定义的classloader来加载

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    public static void main(String[] args) throws Exception {

    ClassLoader cl = createClassLoader();

    Class> startupClass = cl.loadClass("net.kyfxbl.test.cl.Test");

    Object startupInstance = startupClass.newInstance();

    String methodName = "tryForName";

    Class>[] paramTypes = new Class[0];

    Object[] paramValues = new Object[0];

    Method method = startupInstance.getClass().getMethod(methodName,

    paramTypes);

    method.invoke(startupInstance, paramValues);

    }

    private static ClassLoader createClassLoader() throws MalformedURLException {

    String filePath = "c://hehe.jar";

    File file = new File(filePath);

    URL url = file.toURI().toURL();

    URL[] urls = new URL[] { url };

    ClassLoader myClassLoader = new URLClassLoader(urls);

    return myClassLoader;

    }

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

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

    ec1eaa68c2ae0f81eb83260551d962b6.png

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

    e34b6151d189eb41e6394d447dabc199.png

    5.3 Class.forName()

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

    除此之外,Class类还定义了3个参数的forName()方法,方法签名如下:

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    public static Class> forName(String name, boolean initialize,

    ClassLoader loader)

    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代码

    cf2e1763c8893427a90caf2ff8184290.png

    public class Test {

    public void tryForName() {

    System.out.println("current classloader: "

    + getClass().getClassLoader());

    System.out.println("thread context classloader: "

    + Thread.currentThread().getContextClassLoader());

    try {

    Class.forName("net.kyfxbl.test.cl.Target");

    System.out.println("load class success");

    } catch (Exception exc) {

    exc.printStackTrace();

    }

    }

    }

    Java代码

    cf2e1763c8893427a90caf2ff8184290.png

    public static void main(String[] args) throws Exception {

    ClassLoader cl = createClassLoader();

    Class> startupClass = cl.loadClass("net.kyfxbl.test.cl.Test");

    final Object startupInstance = startupClass.newInstance();

    new Thread(new Runnable() {

    @Override

    public void run() {

    String methodName = "tryForName";

    Class>[] paramTypes = new Class[0];

    Object[] paramValues = new Object[0];

    try {

    Method method = startupInstance.getClass().getMethod(

    methodName, paramTypes);

    method.invoke(startupInstance, paramValues);

    } catch (Exception exc) {

    exc.printStackTrace();

    }

    }

    }).start();

    }

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

    18216594594deb88173003b867ad77ca.png

    可以看到,子线程继承了父线程的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的作用,所以特别重要

    展开全文
  • Tomcat源码下载地址:

    2019-01-04 16:13:06
    Tomcat源码下载地址: https://tomcat.apache.org/download-80.cgi   servlet:单实例多线程

    Tomcat源码下载地址:

    https://tomcat.apache.org/download-80.cgi

     

    servlet:单实例多线程

    展开全文
  • tomcat源码下载运行

    2016-03-31 21:56:44
    tomcat 源码下载及本地运行   Tomcat 主干分支地址: Git 地址 :https://github.com/apache/tomcat.git Svn地址:http://svn.apache.org/repos/asf/tomcat/trunk   本文用svn (svn下载的较快些,...
  • 第一步:下载Tomcat源码 下载地址:http://archive.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/ 第二部:解压zip包并用IDEA打开 新增:pom.xml以及calina_home pom.xml中的内容: <?xml ...
  • tomcat源码下载地址

    2020-09-12 22:39:05
    Tomcat官网:http://tomcat.apache.org/ Tomcat各版本源码:http://archive.apache.org/dist/tomcat/
  • Tomcat源码下载地址

    2019-04-09 16:12:18
    Tomcat官网:http://tomcat.apache.org/ Tomcat各版本源码:http://archive.apache.org/dist/tomcat/ 比如,Tomcat-5.0.28源码下载地址为:http://archive.apache.org/dist/tomcat/tomcat-5/v5.0.28/src/ ...
  • tomcat源码下载并导入eclipse

    千次阅读 2016-06-10 14:18:17
    tomcat源码下载并导入eclipse 2013-10-07 00:21 10962人阅读 评论(1) 收藏 举报  分类:   tomcat(2)  版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+]...
  • 首先我们下载Tomcat源码 源码下载地址:https://tomcat.apache.org/download-80.cgi 解压以及创建必要目录和配置 建立POM文件 需要通过Maven组织文件,因此需要在根目录下创建目录中新建pom.xml文件: ...
  • NULL 博文链接:https://theuniquegirl.iteye.com/blog/1019071
  • 安装Java下载java源码包安装的是JDK8,下载地址如下:下载链接注意,不要在服务器中使用wget来下载jdk,因为oracle会认为你是爬虫,下载的文件不是jdk,而是一个html文件。上传服务器并解压使用传输工具将jdk的源码...
  • 分析Tomcat容器,从《深入剖析Tomcat》上截取一部分介绍来回答这个问题。分析Tomcat其实就是分析一个Servlet容器,我们从 servlet容器的角度来看看 Tomcat。一个功能健全的 servlet容器对于每个 servlet 的HTTP请求...
  • tomcat\LICENSE......\NOTICE......\RELEASE-NOTES......\RUNNING.txt......\bin\bootstrap.jar......\...\catalina-tasks.xml......\...\catalina.bat......\...\catalina.sh......\...\commons-daemon.jar......\....
  • 做为一名JavaWeb开发,几乎天天要跟Tomcat打交道,但是一直以来对它的理解都是很模糊的,对于Web框架,我都只是知道大概怎么用,背后的原理也是一无所知,脑海里都没有一个完整的体系图,刚好趁武汉疫情这段时间,...
  • tomcat源码下载、编译

    2012-07-27 16:14:53
    参考资源: http://hi.baidu.com/wang_yilong163/item/ad70e2c35a28bf47bdef698b http://wallboy.iteye.com/blog/562310... 在编译时遇到问题: ...E:\tomcat6\src\build.xml:554: The following error occurred w...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,709
精华内容 1,083
关键字:

tomcat源码下载