百科知识

打造下一个独角兽,从你的奇思妙想开始,我们帮你实现梦想!

Tomcat的启动机制与普通的Java应用程序在本质上是相通的,它们都依赖于一个main方法来启动整个运行流程,并通过多个异步线程持续循环监听,确保主线程不会退出。在Tomcat中,org.apache.catalina.startup.Bootstrap扮演着核心启动类的角色,其main方法不仅能够接收如start、stop等控制指令,还可以通过-config等参数指定配置路径。如果未传递任何参数,Bootstrap将默认执行start操作,并从catalina.base目录中获取配置信息,例如catalina.base/conf/server.xml和catalina.base/conf/context.xml等文件。

1、二者区别和作用范围

catalina.home和catalina.base是Tomcat中两个至关重要的路径:

catalina.home指向Tomcat的安装目录,通常包含bin和lib两个核心目录。bin目录存放着控制Tomcat运行的可执行脚本(如catalin.sh)以及Bootstrap.jar启动类文件;lib目录则包含了Tomcat启动所需的所有jar包(对于安装版Tomcat,源代码被拆分为多个独立的jar文件)。catalina.base则作为Web项目的部署和工作目录,内含conf、logs、webapps等重要子目录。conf目录中存放着默认配置文件,包括server.xml、context.xml、logging.properties以及全局的web.xml等;logs目录用于存储Web应用程序运行时产生的日志文件,其路径和文件名可以在server.xml中配置;webapps目录是Web项目部署的地方,通过server.xml中的<Host>节点可以配置appBase路径,默认值为webapps。webapps目录下的每个Web项目实际上都是一个context,它们默认根据conf/context.xml解析context中的容器和组件,但也可以通过在Web项目目录下放置META-INF/context.xml文件来单独指定。Tomcat在启动时会尝试设置catalina.home路径,如果系统环境变量或vm options中没有指定,则会根据用户当前的工作目录(user.dir)来确定合适的路径。对于catalina.base,如果未被指定,则会直接将catalina.home的值赋给它。2、bin/catalina.sh中设置catalina.home和catalina.base

启动Tomcat主要有两种方式:一种是通过命令脚本(适用于安装包方式),另一种是直接运行Bootstrap.main(适用于源码方式)。尽管这两种方式的启动过程有所不同,但它们的原理是相通的,都是通过vm options和Program arguments传递启动参数,然后调用Bootstrap.main方法。

bin/catalina.sh是一个用于控制Tomcat运行过程的脚本,能够接受run、start、stop、debug等指令来管理Tomcat的启动和运行状态。同时,还提供了两个便捷脚本startup.sh和shutdown.sh,它们分别调用catalina.sh脚本传递start和stop指令。

catalina.sh脚本的主要功能是生成一个包含所有vm options和Program arguments的命令,并将这些参数传递给Bootstrap启动类。(由于脚本源码较为庞大,这里仅展示关键部分)

如何确定catalina.home和catalina.base的值呢?

如果在系统环境变量中没有设置Tomcat的安装目录(CATALINA_HOME),则将catalina.sh脚本所在目录(/bin)的上一级目录设置为CATALINA_HOME。如果在系统环境变量中没有设置工作目录(CATALINA_BASE),则将CATALINA_HOME的值赋给CATALINA_BASE。3、Bootstrap中确定catalina.home和catalina.base

无论是通过catalina.sh脚本启动Tomcat还是直接运行Bootstrap.main方法,都可以在vm options中设置-Dcatalina.home和-Dcatalina.base参数。

Bootstrap的static块会对catalinaBaseFile和catalinaHomeFile进行初始化赋值:

首先获取用户当前的工作目录,这通常是项目的根路径。获取-Dcatalina.home的值,如果不为空,则创建一个File对象,并调用getCanonicalFile方法获取其规范路径形式。(getCanonicalFile能够根据操作系统解析出规范的唯一路径名,例如能够正确处理../和./等相对路径,而getAbsoluteFile则无法做到这一点。)如果-Dcatalina.home为空,则检查用户工作目录下是否存在bootstrap.jar文件,如果存在,则将用户工作目录的上一级目录设置为catalina.home。如果-Dcatalina.home为空,且用户工作目录下也没有bootstrap.jar文件,则将catalina.home设置为用户工作目录本身。获取-Dcatalina.base的值并设置catalina.base,如果为空,则将catalina.home的值赋给catalina.base。org.apache.catalina.startup.Bootstrap是一个启动引导类,其主要职责是加载并指挥org.apache.catalina.startup.Catalina类。值得注意的是,Catalina是通过Tomcat自定义的类加载器加载并实例化的,Bootstrap通过反射调用Catalina的start、stop等方法。这种设计方式的目的是:

The purpose of this roundabout approach is to keep the Catalina internal classes (and any other classes they depend on, such as an XML parser) out of the system class path and therefore not visible to application level classes.

这种迂回方法的目的是将Catalina内部类(以及它们所依赖的任何其他类,如XML解析器)排除在系统类路径之外,从而确保它们对应用程序级类不可见。

个人理解:首先保证了Catalina和Bootstrap的隔离和实现细节不可见,其次二者可以单独打包,像组件一样进行组装和拆除。

1、Bootstrap的初始化

main方法首先对Bootstrap进行实例化,主要初始化了三个自定义类加载器,并使用catalinaLoader加载并实例化Catalina。

这三个自定义类加载器非常重要,分别为commonLoader、catalinaLoader以及sharedLoader,它们都是由org.apache.catalina.startup.ClassLoaderFactory创建的URLClassLoader。它们的区别在于加载类的路径不同,每种类加载器都有其特定的职责范围,其加载路径配置默认在conf/catalina.properties文件中。(涉及到Tomcat的类加载机制,较为复杂,后续会单独拿出来讨论)初始化类加载器后,立即将当前线程的ContextClassLoader设置为catalinaLoader,确保后续Tomcat的类能够使用正确的类加载器进行加载,这是对双亲委派模型的一种突破。最后,使用catalinaLoader加载并实例化org.apache.catalina.startup.Catalina类。2、Bootstrap对Catalina发号施令

Bootstrap解析指令,并通过反射调用对应的Catalina方法。例如,对于start指令,会依次调用Catalina的setAwait、load、start方法。可以大致看出,首先解析参数,然后启动Tomcat,至于setAwait的作用以及参数解析的细节,还需要进一步查看Catalina的实现。

1、加载并解析参数

load方法的主要作用是解析args参数,获取配置文件路径(arguments),默认路径为catalina.base/conf/server.xml,然后解析配置并初始化Server容器。

(1)arguments解析args,首先找到-config,然后获取其后的配置文件路径,如果没有设置-config,则默认使用conf/server.xml

(2)解析server.xml并初始化Server。(解析server.xml的过程较为复杂,后续会单独进行研究)

load方法中有一个有趣的操作,即将标准输出流和异常流重定向到内存流ByteArrayOutputStream中,使用的方式是先调用SystemLogHandler.startCapture()开启捕获,最后调用SystemLogHandler.stopCapture()停止捕获并获取流内容。在没有调用stopCapture之前,System.out和异常抛出都会被重定向到ByteArrayOutputStream。

2、启动和停止

调用Server的start方法可以逐步启动Server容器下的所有子容器和组件。(Tomcat如何实现一键启停,较为复杂,涉及到Tomcat的生命周期设计,后续会单独研究)

还记得setAwait设置await=true,其目的是调用Server的await()方法开启一个线程,循环监听网络(默认端口8005)中是否传来SHUTDOWN指令,如果接收到SHUTDOWN指令,则退出循环,并调用Server的stop和destroy方法,停止并销毁Server下的所有子容器和组件。

Bootstrap作为一个启动引导类,通过加载org.apache.catalina.startup.Catalina,对Catalina发号施令,其目的是保证Catalina和Bootstrap的隔离和实现细节不可见,其次二者可以单独打包,像组件一样进行组装和拆除。catalina.home是Tomcat的安装目录,包含bin和lib等核心目录;catalina.base是Web项目的部署目录,包含conf、logs、webapps等与Web相关的目录。如果catalina.base未被指定,则与catalina.home相同。在Tomcat的初始化和启动过程中,涉及到许多重要的知识点,如自定义类加载器机制、server.xml的解析、Server的一键初始化和一键启停(涉及到Tomcat的生命周期管理),这些都值得深入研究和探讨。本篇内容较为基础,源码也相对简单,主要目的是帮助读者了解Tomcat的启动过程以及如何获取默认配置。