Tomcat 5.5.26源代码分析——启动过程(二)
Tomcat每个运行实例需要使用自己的conf、logs、temp、webapps、work和shared目录,因此CATALINA_BASE就指向这些目录。 而其他目录主要包括了Tomcat的二进制文件和脚本,CATALINA_HOME就指向这些目录。
如果我们希望再运行另一个Tomcat实例,那么我们可以建立一个目录,把conf、logs、temp、webapps、work和shared拷贝到该目录下,然后让CATALINA_BASE指向该目录即可。
下面,我们看看Bootstrap是如何设置CATALINA_HOME和CATALINA_BASE。
?
我们可以通过修改catalina.bat中CATALINA_HOME和CATALINA_BASE的值,来设置catalina.home和catalina.base这两个系统变量。
初始化类加载器体系顾名思义,initClassLoaders() 负责初始化类加载器。看代码之前,我们先看看Tomcat的类加载器体系。
Tomcat的类加载器体系各种组件容器(Tomcat、JBoss、GlassFish、Geronimo等),都会有自己的类加载器体系。这主要是为了要把开发者编写的各种组件应用(WAR、EAR等)部署到容器中,并实现组件应用之间的隔离。
Tomcat也实现了自己的类加载器体系。这个在Tomcat的官方文档中有详细介绍,详见http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html。这里做点简单介绍。
Tomcat的类加载器体系如下图所示:
???? Bootstrap
???? ? ? |
?????? System
???????? |
?????? Common
??? ? ? / \
??? Catalina Shared
? ? ? ? ?? ?? / \
??????? Webapp1 Webapp2 ...
BootStrap就是JVM的启动类加载器,负责加载Java核心类库和系统扩展类库(%JAVA_HOME%/jre/lib/ext下的jar文件)。有的JVM实现提供了两个类加载器,分别加载核心类库和系统扩展类库。我们这里仍用一个Bootstrap类加载器表示,不影响理解。
System 是JVM的系统类加载器,负责加载CLASSPATH下的jar文件,这些文件包括:其中,1和2是在catalina.bat中指定的,3-6是在bootstrap.jar的META-INF/MANIFEST.MF文件中指定的。
- %CATALINA_HOME%/bin/bootstrap.jar
- %JAVA_HOME%/lib/tools.jar
- %CATALINA_HOME%/bin/commons-logging-api-x.y.z.jar
- %CATALINA_HOME%/bin/tomcat-juli.jar
- %CATALINA_HOME%/bin/tomcat-daemon.jar%CATALINA_HOME%/bin/jmx.jar(即之前提到的JDK 1.4兼容包)
Common 是公共类加载器,负责加载Tomcat内部和Web应用程序都可以看到的类。%CATALINA_HOME%/conf/catalina.properties文件中指定了这些类:
common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
可见,Common加载的是%CATALINA_HOME%/common目录下的jar文件。Catalina负责加载Tomcat内部使用的类,这些类对于Web应用程序不可见。同样,%CATALINA_HOME%/conf/catalina.properties文件中指定了这些类:
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
可见,Catalina加载的是%CATALINA_HOME%/server目录下的jar文件。
Shared负责加载仅在Web应用程序之间共享的类,这些类对于Tomcat内部是不可见的。同样,%CATALINA_HOME%/conf/catalina.properties文件中指定了这些类:
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
可见,Catalina加载的是%CATALINA_BASE%/shared目录下的jar文件。注意,这里是CATALINA_BASE。shared属于Tomcat的工作目录。
Webapp负责加载Web应用程序的类,这些类只对本Web应用程序可见。很显然,每个Web应用程序都有一个独立的Webapp类加载器。它们加载的类包括WAR包中/WEB-INF/classes和/WEB-INF/lib目录下的类。
需要注意的是,Webapp并没有遵循类加载器委派模型。Webapp优先从自己的搜索路径中加载类,而不是委派给父亲Shared。这样做的原因应该是保证Web应用程序优先使用自己的类。但是,System负责加载的类不应该被覆盖,因此,Webapp会首先委派给System,然后自己加载,接着才会委派给父亲Shared。这个可以参考org.apache.catalina.loader.WebappClassLoader的loadClass方法。?
PS:Tomcat 7的类加载体系有所简化,如下图所示:
???? Bootstrap
???? ? ? |
?????? System
???????? |
?????? Common
??? ? ? / \
? Webapp1 Webapp2 ...?
在这种体系下,Tomcat内部实现类对Web应用是可见的。如果担心安全风险,可以以-security方式启动Tomcat。
?
创建类加载器,必须要知道类搜索路径是什么。前面提到,Tomcat各类加载器的类搜索路径都定义在%CATALINA_HOME%/conf/catalina.properties中。该配置文件的主要内容如下:
?
可以看出,Catalina的load方法有两个版本:有参数和无参数。
有参数版本,首先调用了arguments方法来处理参数。如果处理成功,则再调用无参数版本。因此,load的核心逻辑在无参数版本中。
?
Catalina加载的主要流程参见上述代码中的中文注释,应该比较清楚,这里就不一一赘述了。
需要注意的是Digester,它是Apache基金会的另一个项目,主要负责解析XML并执行一定的操作。其主要原理是,为XML的每个元素配置特定的规则,规则描述了Digester遇到该元素时需要执行的操作。
Tomcat使用Digester来解析配置文件(默认是conf/server.xml),并根据配置创建各种组件,并建立组件之间的关联。创建和关联,都是通过自定义的规则来实现的。我们以conf/server.xml的部分内容和部分规则为例,解释一下Digester的原理。
配置文件的部分内容:?
其中,每个XML元素代表一个组件对象,元素中属性对应了组件的成员变量。例如,<Server>就代表Server组件,该组件对象有两个成员变量port和shutdown。不难猜到,它们其实是之前提到的SHUTDOWN端口和SHUTDOWN命令。
Server组件包含了Listener对象和Service组件。Service组件又包含了Connetor组件和Engine组件。Engine组件又包含了Host组件。Host组件也包含了...这就是组件之间的关联。
至于这些组件的作用是什么,关联关系为什么是这样的,我们会在后面的文章中看到。
下面我们在看看Digester规则。规则以方法调用的定义。Tomcat启动相关的规则定义在createStartDigester方法中,部分代码如下:?
start方法执行之前,需要确保load方法已经执行。如果load方法已经执行,那么成员变量server肯定被赋值。因此,start方法首先判断成员变量server是否为null,如果是,则调用load方法。
await状态setAwait方法
接着,调用server的start方法,启动Catalina。启动阶段的工作和加载阶段还是不同的。例如,Connector组件负责网络通信,加载阶段只是创建组件对象,启动阶段才会监听端口。
然后,Catalina的主线程会进入await状态,如果成员变量await被设置成true的话。《Tomcat 5.5.26源代码分析——启动过程(一)》中也提到,如果await被设置成true,那么Tomcat的主线程将监听SHUTDOWN端口,等待SHUTDOWN命令,从而我们可以在Tomcat进程外部通过网络停止Tomcat的运行。Tomcat收到SHUTDOWN命令之后,主线程就会退出await状态,await方法也执行结束,从而stop方法被调用,Tomcat停止运行。
这里我们需要注意一下server instanceof Lifecycle的代码。Lifecycle是一个生命周期接口,定义了各种生命周期方法start和stop。只有实现了该接口的组件才能拥有生命周期方法。生命周期方法的调用是嵌套的,父组件的生命周期方法会调用子组件的同名方法。这样,只需调用顶层组件的start方法,就可以启动所有子孙组件。stop也一样。因此,生命周期方法和组件关联关系,使得Tomcat很容易管理各个组件的启动和停止。
OK,start方法也介绍完了。各组件的start方法被调用之后,Tomcat已经处于就绪状态,等待请求的到来。
本文的最后,详细讨论一下await状态的实现细节。Bootstrap类的main方法中,处理start启动参数时,会调用setAwait方法。
public void await() { // port是SHUTDOWN端口。如果值为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现await状态。 // 如果port为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现 await状态。 // 如果port为-1,则表示通过简单循环的方式来实现 await状态。此方法适合嵌入式Tomcat。 // 如果port为其他值,则表示通过监听网络端口的方式来实现 await状态。 // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { while( true ) { try { Thread.sleep( 100000 ); } catch( InterruptedException ex ) { }1 楼 lkjust08 2010-03-27 lz写的很详细,可能是小弟知识不足,具体的内容还是看不懂,看来,小弟还是要加强学习呀。 2 楼 sw1982 2010-03-27 分析的很精辟啊,而且你推荐的那本书确实牛,看了一节就很大收益 3 楼 jackdown 2010-03-31 分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。 4 楼 jarfield 2010-03-31 jackdown 写道分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。
《How Tomcat Works》,你可以上百度文库搜一下,里面有的。 5 楼 jackdown 2010-04-06 jarfield 写道jackdown 写道分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。
《How Tomcat Works》,你可以上百度文库搜一下,里面有的。
嗯,看到了,感觉不错,谢谢。 6 楼 jakehuu 2011-02-24 佩服楼主 现在正在看 《how tomcat works》 ,楼主能够提供这本书里面的例子程序啊?我的邮箱 huqiao86@gmail.com