现在的位置: 首页 > 综合 > 正文

Tomcat 5.5.26源代码分析——启动过程(二)

2013年12月27日 ⁄ 综合 ⁄ 共 21873字 ⁄ 字号 评论关闭
文章目录
上一篇文章主要分析了Bootstrap main
方法的总体流程,并讨论
了JDK兼容性和启动参数。本篇开始深入细节。

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;

}

 

方法的主要工作依次是:

  1. 设置Catalina
    Tomcat Servlet
    容器的代号)的路径:CATALINA_HOME
    CATALINA_BASE

  2. 始化Tomcat
    的类加载器体系
  3. 创建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"));

}

}

 
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"));

}

 
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%

 
我们可以通过修改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. %CATALINA_HOME%/bin/bootstrap.jar
  2. %JAVA_HOME%/lib/tools.jar
  3. %CATALINA_HOME%/bin/commons-logging-api-x.y.z.jar
  4. %CATALINA_HOME%/bin/tomcat-juli.jar
  5. %CATALINA_HOME%/bin/tomcat-daemon.jar
  6. %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

这样做的原因应该是保证Web
应用程序优先使用自己的类。但是,System
负责加载的类不应该被覆盖,因此,Webapp
会首先委派给System

然后自己加载,接着才会委派给父亲Shared
。这个可以参考org.apache.catalina.loader.WebappClassLoader
loadClass
方法。

了解Tomcat
类加载器体系之后,我们来看看initClassLoaders

法的代码。

initClassLoaders
的代码

 private void initClassLoaders() {

try {

commonLoader = createClassLoader("common", null);

if( commonLoader == null ) {

// no config file, default to this loader - we might be in a 'single' env.

commonLoader=this.getClass().getClassLoader();

}

catalinaLoader = createClassLoader("server", commonLoader);

sharedLoader = createClassLoader("shared", commonLoader);

} catch (Throwable t) {

log.error("Class loader creation threw exception", t);

System.exit(1);

}

}

 

可见,该方法以通过createClassLoader

次创建了common
server
shared
类加载器。这三者就
是前面提到的Common
Catalina
Shared
类加载器。如果%CATALINA_HOME%/conf/catalina.properties
中没有指定Common
的搜索路径,那么就是用当前类的类加载器——系统类加载器作为Common

下面,我们看看createClassLoader
的代码:

private ClassLoader createClassLoader(String name, ClassLoader parent)

throws Exception {

// CatalinaProperties类读取并封装了catalina.properties中的配置信息

String value = CatalinaProperties.getProperty(name + ".loader");

if ((value == null) || (value.equals("")))

return parent;



// 解析catalina.properties中配置的类搜索路径

// 将类路径中的${catalina.home}替换成CATALINA_HOME的值,将${catalina.base}替换成CATALINA_BASE的值

ArrayList repositoryLocations = new ArrayList();

ArrayList repositoryTypes = new ArrayList();

int i;



StringTokenizer tokenizer = new StringTokenizer(value, ",");

while (tokenizer.hasMoreElements()) {

String repository = tokenizer.nextToken();



// Local repository

boolean replace = false;

String before = repository;

while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {

replace=true;

if (i>0) {

repository = repository.substring(0,i) + getCatalinaHome()

+ repository.substring(i+CATALINA_HOME_TOKEN.length());

} else {

repository = getCatalinaHome()

+ repository.substring(CATALINA_HOME_TOKEN.length());

}

}

while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {

replace=true;

if (i>0) {

repository = repository.substring(0,i) + getCatalinaBase()

+ repository.substring(i+CATALINA_BASE_TOKEN.length());

} else {

repository = getCatalinaBase()

+ repository.substring(CATALINA_BASE_TOKEN.length());

}

}

if (replace && log.isDebugEnabled())

log.debug("Expanded " + before + " to " + replace);



// 区分四种类型的路径

// Check for a JAR URL repository

try {

URL url=new URL(repository);

repositoryLocations.add(repository);

repositoryTypes.add(ClassLoaderFactory.IS_URL);

continue;

} catch (MalformedURLException e) {

// Ignore

}



if (repository.endsWith("*.jar")) {

repository = repository.substring

(0, repository.length() - "*.jar".length());

repositoryLocations.add(repository);

repositoryTypes.add(ClassLoaderFactory.IS_GLOB);

} else if (repository.endsWith(".jar")) {

repositoryLocations.add(repository);

repositoryTypes.add(ClassLoaderFactory.IS_JAR);

} else {

repositoryLocations.add(repository);

repositoryTypes.add(ClassLoaderFactory.IS_DIR);

}

}



String[] locations = (String[]) repositoryLocations.toArray(new String[0]);

Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);



// 创建类加载器对象

ClassLoader classLoader = ClassLoaderFactory.createClassLoader

(locations, types, parent);



// 类加载器被注册成MBean

// Retrieving MBean server

MBeanServer mBeanServer = null;

if (MBeanServerFactory.findMBeanServer(null).size() > 0) {

mBeanServer =

(MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);

} else {

mBeanServer = MBeanServerFactory.createMBeanServer();

}



// Register the server classloader

ObjectName objectName =

new ObjectName("Catalina:type=ServerClassLoader,name=" + name);

mBeanServer.registerMBean(classLoader, objectName);



return classLoader;

}

 

创建类加载器,必须要知道类搜索路径是什么。前面提到,Tomcat
各类加载器的类搜索路径都定义在%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

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar

shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

 


置文件内容的读取是由CatalinaProperties类
完成的。另外,CatalinaProperties
类还提供了额外的功能:

  1. 配置文件
    的位置可以通过系统变量catalina.config
    进行自定义

  2. 果没有定义配置文件,或配置文件不存在,那么将使用bootstrap.jar
    中的org/apache/catalina/startup/catalina.properties

CatalinaProperties
在最大程度上保证了配置文件总是存在的。


述配置的${catalina.home}
${catalina.base}
还需要解析,替换成真实的值。例如,将${catalina.home}/common/classes

换成D:/ProgramFiles/apache-tomcat-5.5.26/common/classes


搜搜路径被分为四种:URL
*.jar
jar
和目录。ClassLoaderFactory
类在创建类加载器时,会分别处理这四种类型,具体就不细看
了。

类加载器创建之后,将被注册成MBean
ObjectName
Catalina:type=ServerClassLoader,name=
XXX
,其中XXX
表示类加
载器的名称(即common
catalina
shared
)。这样,我们
就可以JMX
观察每个类加载器的信息,比如加载了哪些类。


个疑问

至此,类加载器体系基本建立。但是,init
方法又做了两件事件:

  1. Catalina
    类加载器被设置成线程的上下文类加载器。
  2. SecurityClassLoad
    类,使用Catalian

    加载器预先加载了一些类

1
件事情不足
为奇,SPI
机制经常依赖上下文类加载器。但是,Tomcat
哪里使用了上下文类加载器,还没有找到。

2
件事情就想不通了,虽然代码注释告诉我们,这是为了在启用安全管理器的情况下避免defineClassInPackage
权限错误。但是,我没有理解。

望各位
不吝赐教!

Catalina
对象

init
方法接下来的工作就很简单了:

  1. 通过反射机制,创建一个org.apache.catalina.startup.Catalina
    对象

  2. 通过反射机制,调用该Catalina
    对象的setParentClassLoader
    方法,将Shared

    加载器设置成其parentClassLoader


许你会奇怪,parentClassLoader
是什么用呢?其实这还是和前面提到的Webapp
类加载器有关。Catalina
对象的parentClassLoader

其实是Webapp

父亲,即Shared
类加载器。

我们知道,initClassLoader
方法指定了Common
Catalina
Shared
之间的父子关系,那么谁来指定Shared
Webapp
之间的父子关系呢?
显然应该是Webapp
的创建者。Webapp
的创建者是org.apache.catalina.loader.Webapploader

象,它从Catalina
对象获取Webapp
的父亲类加载器。

另外,关于Catalina

象的另一个话题就是:Bootstrap
Catalina
之间的关系。

仔细阅读Bootstrap

的代码,发现init
之后的工作(load
start
stop
等),
都是委派给Catalina
类的同名方法。也就是说,后续的启动和停止,都是Catalina
类完成的。

那么,Tomcat
的启动过程为什么要放在Bootstrap
Catalina
两个类中呢?而且,Bootstrap

是通过反射机制调用Catalina
,感觉上是在走弯路。

《How Tomcat Works》
17
章的开头提到这个问题。

This chapter
focuses on Tomcat startup using two classes in the
org.apache.catalina.startup package, Catalina and Bootstrap. The
Catalina class is used to start and stop a Server object as well as
parse the Tomcat configuration file, server.xml. The Bootstrap class is
the entry point that creates an instance of Catalina and calls its
process method. In theory, these two classes could have been merged.
However, to support more than one mode of running Tomcat, a number of
bootstrap classes are provided. For example, the aforementioned
Bootstrap class is used for running Tomcat as a stand-alone application.
Another class, org.apache.catalina.startup.BootstrapService, is used to
run Tomcat as a Windows NT service.

理论上,这两个类是可以合并的。但是,为了支持以多种方式
启动,Tomcat
将启动的核心逻辑(即Catalina类)和不同启动方式
(比如 Bootstrap类)分开,并且通过提供多种Bootstrap来实现不同的启动方式。

目前,Tomcat
共支持三种启动方式:

  1. 作为独立的程序,从命令行启动
  2. 作为嵌入式程序,从其他进程中启动
  3. 作为Windows
    服务,自动启动

Tomcat 5.5
中:

  1. 第一种启动方式就是由Bootstrap类和Catalina类实现
  2. 第二种启动
    方式是由org.apache.catalina.startup.Embedded

    实现的(其实Catalina
    类就是Embedded的子类)
  3. 第三
    种启动方式仍然由Bootstrap类和Catalina类实现


过在Tomcat 4.x
中,第三种启动方式由专门的Bootstrap
Service类和Catalina
Service
类来实现的。

可见,Tomcat
的发展历史中,还是体现了“Bootstrap
Catalina

离,Bootstrap
封装不同启动方式”的策略。但是,现在的代码看,这个分离已经
不是很清楚。

OK
init
方法的讨论到此结束。下面我们看看load
start
方法。Bootstrap

这两个方法,其实都是直接调用Catalina
类的同名方法,因此,我们主要分析的其
实是Catalina
类的相关代码。

load
方法

 private void load(String[] arguments)

throws Exception {

// Call the load() method

String methodName = "load";

Object param[];

Class paramTypes[];

if (arguments==null || arguments.length==0) {

paramTypes = null;

param = null;

} else {

paramTypes = new Class[1];

paramTypes[0] = arguments.getClass();

param = new Object[1];

param[0] = arguments;

}

Method method =

catalinaDaemon.getClass().getMethod(methodName, paramTypes);

if (log.isDebugEnabled())

log.debug("Calling startup class " + method);

method.invoke(catalinaDaemon, param);

}

 

可以看出,Catalina的load方法有两个版本:有参数和无参
数。有参数版本,首先调用了arguments方法来处理参数。如果处理成功,则再调用无参数版本。因此,load的核心逻辑在无参数版本中。

    public void load(String args[]) {



try {

if (arguments(args))

load();

} catch (Exception e) {

e.printStackTrace(System.out);

}

}

 

无参数版本的load方法做的工作,主要是根据Tomcat的配置文件,创建各个组件,并建立组件之间的关联。比如,创建核心的Server
组件、Service

件、Manager
组件、 Loader

件等。

这个细节我们等会讨论,首先简单看看arguments
方法。arguments
方法对理解启动过程不是很关键,如果不感兴趣可以跳过。

Catalina
类的命令行参数

Catalina.bat
脚本的参数,其实是直接传递到Catalina
类的。arguments

法正是处理这些参数的。

Catalina
类可以识别的参数包括:

  1. -config {pathname}
    设置配置文件的路径。如果是相对路径,则是相对于CATALINA_HOME
    。默认值是conf/server.xml
  2. -noaming
    不启用命名服务
  3. -help
    打印帮助信息
  4. -start
    启动当前的Catalina

  5. -stop
    停止当前的Catalina
    实例

arguments
的代码比较简单。

 protected boolean arguments(String args[]) {



boolean isConfig = false;



if (args.length < 1) {

usage();

return (false);

}



for (int i = 0; i < args.length; i++) {

if (isConfig) {

configFile = args[i];

isConfig = false;

} else if (args[i].equals("-config")) {

isConfig = true;

} else if (args[i].equals("-nonaming")) {

setUseNaming( false );

} else if (args[i].equals("-help")) {

usage();

return (false);

} else if (args[i].equals("start")) {

starting = true;

stopping = false;

} else if (args[i].equals("stop")) {

starting = false;

stopping = true;

} else {

usage();

return (false);

}

}



return (true);

}

 


Catalina
类的成员变量starting
stopping
分别表示要启动和停止Tomcat


载过程

下面我们看看load方法的代码。 

 public void load() {



// 初始化CATALINA_HOME、CATALINA_BASE和临时目录,该方法在Embedded类中。

initDirs();



// 初始化命名服务的基本配置,包括java.naming.factory.url.pkgs和java.naming.factory.initial

// java.naming.factory.url.pkgs的默认值为 org.apache.naming

// java.naming.factory.initial 的默认值为org.apache.naming.java.javaURLContextFactory



// Before digester - it may be needed

initNaming();



// 创建Digester对象。该对象被用来解析配置文件(默认为conf/server.xml)

// Create and execute our Digester

Digester digester = createStartDigester();

long t1 = System.currentTimeMillis();



Exception ex = null;

InputSource inputSource = null;

InputStream inputStream = null;

File file = null;

try {

// 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml

file = configFile();

inputStream = new FileInputStream(file);

inputSource = new InputSource("file://" + file.getAbsolutePath());

} catch (Exception e) {

;

}



// 如果配置文件不存在,则在类路径中加载

if (inputStream == null) {

try {

inputStream = getClass().getClassLoader()

.getResourceAsStream(getConfigFile());

inputSource = new InputSource

(getClass().getClassLoader()

.getResource(getConfigFile()).toString());

} catch (Exception e) {

;

}

}



// 如果类路径中也找不到,则加载server-embed.xml

// This should be included in catalina.jar

// Alternative: don't bother with xml, just create it manually.

if( inputStream==null ) {

try {

inputStream = getClass().getClassLoader()

.getResourceAsStream("server-embed.xml");

inputSource = new InputSource

(getClass().getClassLoader()

.getResource("server-embed.xml").toString());

} catch (Exception e) {

;

}

}





// 如果没能加载配置文件,则报错,中断启动

if ((inputStream == null) && (file != null)) {

log.warn("Can't load server.xml from " + file.getAbsolutePath());

return;

}



// 使用Digester对象解析配置文件,解析的过程中会创建各种组件,包括Server组件。

try {

inputSource.setByteStream(inputStream);

digester.push(this);

digester.parse(inputSource);

inputStream.close();

} catch (Exception e) {

log.warn("Catalina.start using "

+ getConfigFile() + ": " , e);

return;

}



// 将系统标准输出(System.out)和系统错误输出(System.err)重定向到定制的SystemLogHandler。

// SystemLogHandler可以将每个线程的输出隔离到不同的输出流中。

// Stream redirection

initStreams();



// 初始化Server组件。成员变量server就代表Server组件

// Start the new server

if (server instanceof Lifecycle) {

try {

server.initialize();

} catch (LifecycleException e) {

log.error("Catalina.start", e);

}

}



long t2 = System.currentTimeMillis();

if(log.isInfoEnabled())

log.info("Initialization processed in " + (t2 - t1) + " ms");



}

 

Catalina
加载的主要流程参见上述代码中的中文注释,应该比较清楚,这里就不一一赘述了。


要注意的是Digester
,它是Apache
基金会的另一个项目,主要负责解析XML

执行一定的操作。其主要原理是,为XML
的每个元素配置特定的规则,规则描述
Digester
遇到该元素时需要执行的操作。

Tomcat
使用Digester

解析配置文件(默认是conf/server.xml
),并根据配置创建各种组件,并
建立组件之间的关联。创建和关联,都是通过自定义的规则来实现的。我们以conf/server.xml的部分内容和部分规则为例,解释一下
Digester的原理。

配置文件的部分内容:

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">



<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />

<Service name="Catalina">

<Connector port="8080" maxHttpHeaderSize="8192"

maxThreads="150" minSpareThreads="25" maxSpareThreads="75"

enableLookups="false" redirectPort="8443" acceptCount="100"

connectionTimeout="20000" disableUploadTimeout="true" />

<Engine name="Catalina" defaultHost="localhost">

<Host name="localhost" appBase="webapps"

unpackWARs="true" autoDeploy="true"

xmlValidation="false" xmlNamespaceAware="false">

...

</Host>

</Engine>

</Service>

</Server>

 

其中,
每个XML元素代表一个组件对象,元素中属性对应了组件的成员变量。例如,<Server>就代表Server组件,该组件对象有两个成员变
量port和shutdown。不难猜到,它们其实是之前提到的SHUTDOWN端口和SHUTDOWN命令。

Server组件包含了
Listener对象和Service组件。Service组件又包含了Connetor组件和Engine组件。Enging组件又包含了Host组
件。Host组件也包含了...这就是组件之间的关联。

至于这些组件的作用是什么,关联关系为什么是这样的,我们会在后面的文章中看到。


面我们在看看Digester规则。规则以方法调用的定义。Tomcat启动相关的规则定义在createStartDigester方法中,部分代码如
下:

 protected Digester createStartDigester() {



// ...



// Configure the actions we will be using



// 创建Server组件,即StandardServer对象

digester.addObjectCreate("Server",

"org.apache.catalina.core.StandardServer",

"className");

// 设置StandardServer的成员变量port和shutdown

digester.addSetProperties("Server");

// 建立Catalina对象和StandardServer对象之间的关联,前者包含后者

digester.addSetNext("Server",

"setServer",

"org.apache.catalina.Server");



//...

// 创建Listener对象,类由className属性决定

digester.addObjectCreate("Server/Listener",

null, // MUST be specified in the element

"className");

// 设置Listener对象的成员变量

digester.addSetProperties("Server/Listener");

// 建立StandardServer对象和Listener对象之间的关联,前者包含后者

digester.addSetNext("Server/Listener",

"addLifecycleListener",

"org.apache.catalina.LifecycleListener");



// 创建Service组件,即StandardService对象

digester.addObjectCreate("Server/Service",

"org.apache.catalina.core.StandardService",

"className");

// 设置StandardService的成员变量 name

digester.addSetProperties("Server/Service");

// 建立StandardServer对象和StandardService对象之间的关联,前者包含后者

digester.addSetNext("Server/Service",

"addService",

"org.apache.catalina.Service");



// ...

return (digester);



}

 

概括地
说,addObjectCreate

表示创建对象,addSetProperties

示设置对象的属性,addSetNext
表示设置对象的包含对象。这都是
Digester的常用规则。当然,Tomcat
也定制了一些规则,以执行更
加复杂的操作。

OK,load方法就算看完了。总之,该方法结束后,Tomcat的运行时视图已经被建立,各大组件及关联关系均以建立。
下一步,就是启动Catalina了。

start
方法

public void start()

throws Exception {

if( catalinaDaemon==null ) init();



Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);

method.invoke(catalinaDaemon, (Object [])null);

}

 

该方法其实仅仅是调用了Catalina
类的start
方法,因此我们重点看Catalina
类的start
方法。

 public void start() {



//确保load方法已经被调用。load方法会创建StandardServer 实例,并赋值给成员变量server。

if (server == null) {

load();

}



long t1 = System.currentTimeMillis();



//调用server的生命周期方法start

// Start the new server

if (server instanceof Lifecycle) {

try {

((Lifecycle) server).start();

} catch (LifecycleException e) {

log.error("Catalina.start: ", e);

}

}



long t2 = System.currentTimeMillis();

if(log.isInfoEnabled())

log.info("Server startup in " + (t2 - t1) + " ms");



//注册JVM的shutdown钩子

try {

// Register shutdown hook

if (useShutdownHook) {

if (shutdownHook == null) {

shutdownHook = new CatalinaShutdownHook();

}

Runtime.getRuntime().addShutdownHook(shutdownHook);

}

} catch (Throwable t) {

// This will fail on JDK 1.2. Ignoring, as Tomcat can run

// fine without the shutdown hook.

}



//如果await设置成true,则进入await状态

if (await) {

await();

//退出await状态后,就停止Tomcat

stop();

}

}

 

start
方法执行之前,需要确保load

法已经执行。如果load
方法已经执行,那么成员变量server
肯定被赋值。因此,start

法首先判断成员变量
server


否为null
,如果是,则调用load

法。

接着,调用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状态的实现细节。

await
状态

setAwait方法

Bootstrap类的main方法中,处理start启动参数时,会调用
setAwait方法。

 public void setAwait(boolean await)

throws Exception {

Class paramTypes[] = new Class[1];

paramTypes[0] = Boolean.TYPE;

Object paramValues[] = new Object[1];

paramValues[0] = new Boolean(await);

Method method =

catalinaDaemon.getClass().getMethod("setAwait", paramTypes);

method.invoke(catalinaDaemon, paramValues);

}


述代码其实调用了Catalina
类的setAwait
方法。Catalina
setAwait
方法定义在于其父类Embedded
主要就是设置成员变量await的值

    public void setAwait(boolean b) {

await = b;

}

 

await变成true之后,Tomcat就进入await状态,这一点在start方法中已经分析
过。

await方法

如果成员变量await为true,那么Catalina类的await方法就会被调用。

    public void await() {



//直接调用StandardServer的await方法

server.await();

}

 

下面我们看看StandardServer类的await方法。

 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 ) {

}

if( stopAwait ) return;

}

}

// 创建SHUTDOWN端口的ServerSocket

// Set up a server socket to wait on

ServerSocket serverSocket = null;

try {

serverSocket =

new ServerSocket(port, 1,

InetAddress.getByName("127.0.0.1"));

} catch (IOException e) {

log.error("StandardServer.await: create[" + port

+ "]: ", e);

System.exit(1);

}

// 在SHUTDOWN端口上等待SHUTDOWN命令

// Loop waiting for a connection and a valid command

while (true) {

// Wait for the next con

【上篇】
【下篇】

抱歉!评论已关闭.