Tomcat 的运行时视图,简单地看,其实就是一些相互关联的组件。这些组件相互协作,完成一定的任务(比如部署Web 应用、处理到HTTP 请求等)。Tomcat 启动过程中所做的主要工作,也就是创建这些组件,并建立组件之间的关联。当然,要创建哪些组件,组件之间怎么关联,这是根据配置文件来定制的。
服务器程序的启动过程一般都有“三段式”,Tomcat 也不例外,它的三段式分别是init 、load 和start 。
init 方法
我们先看看Bootstrap 的init 方法。
- public void init() throws Exception
- {
- // Set Catalina path
- setCatalinaHome();
- setCatalinaBase();
- initClassLoaders();
- Thread.currentThread().setContextClassLoader(catalinaLoader);
- SecurityClassLoad.securityClassLoad(catalinaLoader);
- // Load our startup class and call its process() method
- if (log.isDebugEnabled())
- log.debug("Loading startup class");
- Class startupClass =
- catalinaLoader.loadClass
- ("org.apache.catalina.startup.Catalina");
- Object startupInstance = startupClass.newInstance();
- // Set the shared extensions class loader
- if (log.isDebugEnabled())
- log.debug("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);
- catalinaDaemon = startupInstance;
- }
public void init() throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("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);
catalinaDaemon = startupInstance;
}
该方法的主要工作依次是:
- 设置Catalina (Tomcat Servlet 容器的代号)的路径:CATALINA_HOME 和CATALINA_BASE
- 初始化Tomcat 的类加载器体系
- 创建org.apache.catalina.startup.Catalina 对象(启动阶段剩余的工作由Catalina类 完成)
Catalina_Home 和Catalina_Base
首先,我们看看这两个路径有何区别。Tomcat的启动脚本已经设置了CATALINA_HOME 和CATALINA_BASE 的值,而且两者的值是相同的,都是Tomcat的根目录。那么为什么还要设置这两个变量呢?
我们可以从Tomcat 5.5 的配置文档(http://tomcat.apache.org/tomcat-5.5-doc/config/host.html )中找到答案:
The description below uses the variable name $CATALINA_HOME to refer to the directory into which you have installed Tomcat 5, and is the base directory against which most relative paths are resolved. However, if you have configured Tomcat 5 for multiple instances by setting a CATALINA_BASE directory, you should use $CATALINA_BASE instead of $CATALINA_HOME for each of these references.
从这段描述可以看出CATALINA_HOME 和CATALINA_BASE 的区别。简单的说,CATALINA_HOME 是Tomcat 的安装目录,CATALINA_BASE 是Tomcat 的工作目录。如果我们想要运行Tomcat 的多个实例,但是不想安装多个Tomcat 软件副本。那么我们可以配置多个工作目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。
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。
- private void setCatalinaHome() {
- if (System.getProperty("catalina.home") != null)
- return;
- File bootstrapJar =
- new File(System.getProperty("user.dir"), "bootstrap.jar");
- if (bootstrapJar.exists()) {
- try {
- System.setProperty
- ("catalina.home",
- (new File(System.getProperty("user.dir"), ".."))
- .getCanonicalPath());
- } catch (Exception e) {
- // Ignore
- System.setProperty("catalina.home",
- System.getProperty("user.dir"));
- }
- } else {
- System.setProperty("catalina.home",
- System.getProperty("user.dir"));
- }
- }
private void setCatalinaHome() {
if (System.getProperty("catalina.home") != null)
return;
File bootstrapJar =
new File(System.getProperty("user.dir"), "bootstrap.jar");
if (bootstrapJar.exists()) {
try {
System.setProperty
("catalina.home",
(new File(System.getProperty("user.dir"), ".."))
.getCanonicalPath());
} catch (Exception e) {
// Ignore
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
} else {
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
}
CATALINA_HOME 保存在系统变量catalina.home 中。setCatalinaHome 方法首先检查catalina.home 系统变量是否设置。如果已经设置,则直接返回;否则,就检查Tomcat 的启动目录(系统变量user.dir)。如果启动目录是bin,那么启动目录下就存在bootstrap.jar ,故CATALINA_HOME 就是bin 的上级目录;如果启动目录下没有bootstrap.jar ,那么就假定启动目录就是CATALINA_HOME 。
- private void setCatalinaBase() {
- if (System.getProperty("catalina.base") != null)
- return;
- if (System.getProperty("catalina.home") != null)
- System.setProperty("catalina.base",
- System.getProperty("catalina.home"));
- else
- System.setProperty("catalina.base",
- System.getProperty("user.dir"));
- }
private void setCatalinaBase() {
if (System.getProperty("catalina.base") != null)
return;
if (System.getProperty("catalina.home") != null)
System.setProperty("catalina.base",
System.getProperty("catalina.home"));
else
System.setProperty("catalina.base",
System.getProperty("user.dir"));
}
CATALINA_BASE 保存在系统变量catalina.base 中。setCatalinaBase 方法首先检查catalina.base 系统变量是否设置,如果已经设置,就直接返回。否则,就检查catalina.home 系统变量是否设置。如果已经设置,则以CATALINA_HOME 作为CATALINA_BASE 。否则,就以Tomcat 的启动目录(系统变量user.dir )作为CATALINA_BASE 。
catalina.bat 中已经设置了catalin.home 和catalina.base 的值,详见下面代码:
- %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
我们可以通过修改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 文件,这些文件包括:
- %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 兼容包)
其中,1 和2 是在catalina.bat 中指定的,3-6 是在bootstrap.jar 的META-INF/MANIFEST.MF 文件中指定的。
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 。这样做的原因应该是保证