Tomcat知多少 -- 01. Tomcat的启动概述

0. 写在前面

本文讲述的Tomcat相关内容,基于Tomcat9.0.34版本。
学习Tomcat源码时本人使用Idea工具,导入如下依赖来查看源码:

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.34</version>
</dependency>

1. 始于一条Shell语句

于Linux系统下启动Tomcat,只需要一条shell语句>$ startup.sh,该shell脚本位于apache-tomcat-9.0.34in目录下,该脚本最后一句话最为重要:

exec "$PRGDIR"/"$EXECUTABLE" start "$@"

若将其中的变量替换掉就是:

exec catalina.sh start [参数]

所以实际的启动脚本是同目录下的catalina.sh,在该脚本中,找到 $1 = start的分支,启动Tomcat的shell基本如下:

eval $_NOHUP ""$_RUNJAVA"" ""$CATALINA_LOGGING_CONFIG"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" 
  -D$ENDORSED_PROP=""$JAVA_ENDORSED_DIRS"" 
  -classpath ""$CLASSPATH"" 
  -Dcatalina.base=""$CATALINA_BASE"" 
  -Dcatalina.home=""$CATALINA_HOME"" 
  -Djava.io.tmpdir=""$CATALINA_TMPDIR"" 
  org.apache.catalina.startup.Bootstrap "$@" start 
  >> "$CATALINA_OUT" 2>&1 "&"

上述脚本看起来很复杂,其实就是java org.apache.catalina.startup.Bootstrap start加上了一些启动参数。

所以,Tomcat的启动入口就是 org.apache.catalina.startup.Bootstrap类。

2. Bootstrap类

org.apache.catalina.startup.Bootstrap类是一个拥有public static void main(String args[])方法的类。在启动Tomcat时,main方法所传入的参数是start

对main方法做简化,其主要过程如下:

public static void main(String args[]) {
    // 1. 生成bootstrap实例
    Bootstrap bootstrap = new Bootstrap();
    // 2. 调用init()方法
    bootstrap.init();
    // 3. 调用load()方法
    bootstrap.load(args);
    // 4. 调用start()方法
    bootstrap.start();
    .....
}

注意,main方法中处理了其他命令行参数的情况,上述简化后的过程,只针对启动时相关的代码。

init()方法主要内容如下:

public void init() throws Exception {
    // 初始化类加载器
    initClassLoaders();
    // 设置当前线程的ClassLoader为catalinaLoader
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    // 通过反射,生成Catalina实例
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
    // 通过反射,设置Catalina实例的ClassLoader
    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;
}

为什么需要初始化类加载器呢?实际上,Tomcat自定义了类加载器,以方便隔离不同Web应用所使用的类。这里先不做进一步说明,等介绍到加载一个Web应用时再详细阐述。

init()方法调用完后,load()start()方法实际上就是通过反射调用了Catalina的实例方法load()start(),因此,实际的启动动作是在Catalina类中完成的

3. Catalina类

Catalina类是Tomcat启动各种组件的主要场所,本文在讲述启动过程时,只关注重要组件的启动过程,而诸如JNDI、JMX或者监听器等则不予关注。

既然是启动各种组件,那么在看代码之前,有必要对Tomcat的主要组件做些介绍。

3.1 Tomcat的主要组件

有关Tomcat组件的介绍目前网络上文章很多,这里不再赘述,挑选两篇作为参考。
官方参考
不错的中文介绍

3.2 组件的生命周期

Tomcat的各个组件都遵循Tomcat生命周期定义,而作为顶层组件的Server实例管控着大多数情况下的组件生命周期。因此,想要顺畅的浏览Tomcat源码,需要对生命周期的状态流转有一定了解。
Lifecycle生命周期状态图

上图是Tomcat生命周期状态转移图,有了对生命周期的认识,我们只需要关注重要组件的initInternal()startInternal()stopInternal()destroyInternal()等方法,就可以快速地分析组件的行为了。

3.3 Digester工具类

使用过Tomcat的开发者应该对server.xml文件不陌生,该文件配置了Tomcat重要组件的一些参数,比如常见的监听端口,使用的字符集等。

在Tomcat启动时,会调用Catalina类的实例方法createStartDigester()来解析server.xml并生成各种组件实例,而完成这一过程使用的是Digester工具类。

Digester是一个类似于SAX(Simple API for XML Parsing)的基于事件驱动的xml文件解析工具。所谓事件驱动,就是当遇到某个元素节点就会做什么,而不必要将整个xml文件全部读取并构建dom树后再解析。

如何做到事件驱动呢?Digester使用Rule来实现。Rule就是当遇到某个元素(或者说标签)时所要遵守的规则,如创建对象、设置属性、调用某个方法等。总体而言,Digester在解析xml文件时,每当预先设定的规则匹配到某个xml元素时,就会执行规则定义的行为。

Rule有四个重要的方法需要子类实现:

  • begin() 在元素匹配时调用;
  • body() 当遇到匹配元素的嵌套内容时调用;
  • end() 当遇到匹配元素的关闭标签时调用;
  • finish() 当parse过程结束时调用,用来清理每个规则的使用的资源。

更为详细的Digester介绍请参考: Digester官网。Tomcat使用的Digester与组件形式的Digester稍有不同,但是总体上使用方式一致。

下面举例说明Digester是如何解析server.xml文件的:

digester.addObjectCreate("Server",
                         "org.apache.catalina.core.StandardServer",
                         "className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
                    "setServer",    // 方法名
                    "org.apache.catalina.Server");

digester.addObjectCreate()方法意味着解析xml文档时,如果遇到了<Server> </Server>中的前一个,就会创建一个org.apache.catalina.core.StandardServer类的实例。默认的,Digester内部会维护一个Java对象栈,每当创建对象时,就会将对象压入栈顶。所以,创建的StandardServer对象目前处于栈顶。

digester.addSetProperties("Server")表示会解析xml标签中遇到的Server元素的属性标签,并调用栈顶元素的对应setter()方法来设置属性。

digester.addSetNext("Server","setServer","org.apache.catalina.Server")表示遇到Server标签时,会调用栈顶的下一个元素setServer()方法,并将栈顶元素作为参数传入。一般这个方法用途与addChild()类似。在上述例子中,(解析开始之前)会先将Catalina的实例压入栈,而后Server的实例入栈,这样就完成了catalina.setServer()的调用。

3.4 Catalina类的主要作用

让我们说回Catalina类。启动过程中,Catalina类中的两个方法依次被调用:load() --> start()

load()方法中,最主要的动作是createStartDigester() 也就是通过Digester解析server.xml生成各个组件的实例。之后,在load()中会调用getServer().init(),因为Server包含着其他的所有组件,因此,该调用会完成所有组件的初始化操作。

start()方法调用时,所有组件都已经初始化完毕,因此直接调用getServer().start()来启动所有的组件。

-------------------------------------
吾生也有涯,而知也无涯。
原文地址:https://www.cnblogs.com/SanjiApollo/p/12762767.html