Bootstrap类

当使用startup.sh脚本启动时,startup.sh会调用catalina.sh脚本进行启动,在catalina.sh脚本里会执行org.apache.catalina.startup.Bootstrap类,参数为”start”.

源码org.apache.catalina.startup.Bootstrap类中有一个main方法,该方法执行流程如下图:

Tomcat启动过程 - 图1

  • bootstrap.init():负责初始化catalina.home、catalina.base的值,以及类加载器,以及org.apache.catalina.startup.Catalina类的实例-catalinaDaemon
  • bootstrap.load():负责调用catalinaDaemon.load()方法
  • bootstrap.start():负责调用catalinaDaemon.start()方法

catalina.home表示Tomcat的安装目录 catalina.base表示Tomcat的Server实例的工作目录,Server在初始化的过程中从这个目录下寻找配置文件

可以看出Tomcat在启动过程中,核心类是org.apache.catalina.startup.Catalina。

Catalina类

load方法

org.apache.catalina.startup.Catalina类中的load方法执行流程:

Tomcat启动过程 - 图2

  • Catalina.initDirs():如果catalinaHome和catalinaBase是相对路径,那么在这里会转化为绝对路径
  • Catalina.initNaming():初始化命名空间相关系统属性catalina.useNaming
  • Digester digester = createStartDigester():初始化server.xml文件解析器
  • File file = configFile():在catalina.base目录下的conf/server.xml文件
  • InputSource inputSource = new InputSource(file.toURI().toURL().toString()):将file转化为xml解析器需要的inputsource
  • digester.push(this):将当前catalina对象设置为server.xml文件解析器的顶层对象,在解析server.xml时会用到
  • digester.parse(inputSource):开始解析server.xml文件
  • getServer().setCatalina(this):获取server对象,并将当前catalina对象set进入,server对象是在上面解析server.xml文件时构造出来的
  • getServer().init():初始化server对象

可以看出Catalina的load方法最后就是调用server的初始化方法,而server的初始化后面涉及到很多其他子组件的初始化,流程很长,后面单独说。

start方法

org.apache.catalina.startup.Catalina类中的start方法执行流程:

Tomcat启动过程 - 图3

  • getServer().start():获取server对象,并启动,后面涉及到很多其他子组件的启动,流程很长,后面单独说
  • Runtime.getRuntime().addShutdownHook(shutdownHook):增加jvm的shutdown钩子,在关闭jvm之前会调用这个钩子,执行相应逻辑
  • await():该方法将调用getServer().await()方法,内部会监听发送给8005端口的shutdown命令,一旦接受到shutdown命令,那么await()执行完成,继续向下执行stop()方法
  • stop():该方法需要等awati()方法执行完成才会执行,主要功能是调用server的stop与destroy方法完成停止和销毁

Server接口

Server表示服务器,负责接收、处理,响应请求,是Tomcat内部架构中的最外层,是所有其他组件的根节点。

Server接口继承Lifecycle接口,Lifecycle表示生命周期,所以Server也具有生命周期,上面所提到的init()、start()、stop()、destroy()方法都是Lifecycle中的方法。

StandardServer类

在解析server.xml时,会为Server接口构造一个StandardServer的实现类,也是Server接口的默认实现类。该类继承LifecycleMBeanBase类。

LifecycleMBeanBase类是一个生命周期+JMX的管理类,通过继承该类可以使得StandardServer拥有生命周期,同时也可以通过该类中的register和unregister方法进行JMX的注册与注销。

initInternal方法

initInternal()方法是StandardServer的初始化方法,是在Lifecycle中的init方法中执行的,是实际上的Server初始化方法。

initInternal()方法的执行流程为: Tomcat启动过程 - 图4

  • super.initInternal():调用父类LifecycleMBeanBase的initInternal()方法,该方法将本StandardServer对象注册到JMX中,ObjectName为”type=Server”
  • register(new StringCache(), “type=StringCache”):将StringCache注册到JMX中
  • register(factory, “type=MBeanFactory”):将MBeanFactory注册到JMX中
  • globalNamingResources.init():命名空间初始化
  • services.init():services是一个数组,Service是Server的子节点,一个Server下可以有多个Service节点

startInternal方法

startInternal方法是StandardServer的启动方法,核心流程就两步:

  1. globalNamingResources.start():命名空间的启动
  2. services[i].start():service启动

总结

Server是Tomcat架构中最顶层元素,在它的初始化和启动过程中,它主要负责一些全局共享的东西进行初始化和启动,以及让它的子节点Service进行相应初始化和启动即可。

Service接口

Service接口也继承了Lifecycle接口,也具有生命周期,它对应的默认实现类是StandardService类。

Service表示一个服务,是Server下的子节点,表示一个Server下可以有多个服务,每个服务可以有不同的实现与构造。

StandardService

initInternal方法

initInternal方法是StandardService的初始化方法,核心执行流程为:

Tomcat启动过程 - 图5

  • super.initInternal():和StandardServer中类似,将自己注册到JMX中,ObjectName为”type=Service”。
  • container.init():container实际上是StandardEngine,是在解析server.xml中以及初始化好的
  • executors.init():一个Service下可以有多个Executor,Executor表示线程池,也就是一个Service可以提供多个线程池供下层节点使用
  • connectors.init():一个Service下可以有多个Connector,Connector表示请求接收器,复制接收客户端的请求

所以Service下包含非常重要的三个组件:Executor、Connector、Engine。下文会分别进行分析

startInternal方法

该方法不复杂,主要功能就是调用Executor、Connector、Engine的start方法。

Executor接口

org.apache.catalina.Executor接口继承java.util.concurrent.Executor和Lifecycle接口。

Executor表示线程池,而线程池肯定是提供给请求处理用的,具体请求处理时是怎么利用线程池的,后文再说。我们这里直说初始化和启动。

线程池可以在server.xml文件中进行配置,也可以不配置。
如果没有配置,那么在Service初始化和启动时,Executor都不会有动作。
如果配置了,那么在解析server.xml文件是,会构造一个StandardThreadExecutor的实例,StandardThreadExecutor类就是Executor接口的默认实现,在构造StandardThreadExecutor实例时,就会将server.xml中的配置信息set到StandardThreadExecutor实例相应的属性中去,比如name、maxThreads等等

StandardThreadExecutor类

initInternal方法

非常简单,只调用了super.initInternal(),完成JMX的注册。ObjectName为——“type=Executor,name=”+线程池的名字
**

startInternal方法

对于线程池的启动和其他组件的启动不太一样,线程池是资源,资源只要初始化好,可以哪来用就可以了。
所以在StandardThreadExecutor的startInternal()方法中,它的核心步骤是这样的:

  1. new一个taskqueue队列
  2. new一个TaskThreadFactory线程工厂
  3. new一个ThreadPoolExecutor线程池

非常简单,以后有任务需要StandardThreadExecutor来执行,直接调用StandardThreadExecutor的execute方法即可。

Connector类

Connector不是一个接口,而是一个类。

Connector表示请求接收器,请求是一个抽象概念,有Http请求,有Ftp请求,有其他基于各种各样协议所对应的请求。

server.xml文件中,可以对Connector节点配置protocol属性值,可以配置为”HTTP/1.1”、”AJP/1.3”或一个类的全限定名。

在Connector类的setProtocol方法中有如下判断:

  1. if ("HTTP/1.1".equals(protocol)) {
  2. setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
  3. } else if ("AJP/1.3".equals(protocol)) {
  4. setProtocolHandlerClassName("org.apache.coyote.ajp.AjpProtocol");
  5. } else if (protocol != null) {
  6. setProtocolHandlerClassName(protocol);
  7. }
  1. public void setProtocolHandlerClassName(String protocolHandlerClassName) {
  2. this.protocolHandlerClassName = protocolHandlerClassName;
  3. }

protocolHandlerClassName是Connector的一个属性。

从上面的代码可以看出来,Connector是一个指定协议的请求接收器,一个Connector接收到请求后,交给具体的ProtocolHandler进行处理。

initInternal方法

initInternal方法的核心流程: Tomcat启动过程 - 图6

  • super.initInternal():仍然还是注册JMX
  • adapter = new CoyoteAdapter(this):针对当前Connector初始化一个Adapter
  • setParseBodyMethods(parseBodyMethodsSet):设置解析请求体的请求方法,默认情况下POST请求我们才会去解析请求body,而这一点在Tomcat中是可以配置的,配置参数为parseBodyMethods,我们完全可以配置在处理GET请求时也去解析请求body。
  • protocolHandler.init():Connector对应的ProtocolHandler进行初始化
  • mapperListener.init():MapperListener进行初始化

关于CoyoteAdapter和MapperListener我们后文在解析,这里先重点关注ProtocolHandler。

startInternal方法

startInternal方法的核心流程:

Tomcat启动过程 - 图7 这里也先重点关注protocolHandler。

MapperListener启动

MapperListener中有一个重要的属性为Mapper mapper,Mapper中有以下几个重要的属性:

  1. Host[] hosts
  2. ContextVersion context
  3. String defaultHostName

Host有以下几个重要的属性:

  1. ContextList contextList,表示该Host下关联了哪些Context
  2. Host realHost,真正的Host信息,有name以及容器Host对象两个属性
  3. List aliases,当前这个Host对应的其他别名Host

ContextList有以下几个重要属性:

  1. Context[] contexts,表示Host下关联的Context列表
  2. nesting

Context有以下几个重要属性:

  1. ContextVersion[] versions,表示每个Context下不同版本的列表

ContextVersion有以下几个重要属性,每个ContextVersion就表示一个WebApp了:

  1. String path,表示Context Path,也就是应用的名字
  2. Wrapper defaultWrapper,当前应用处理请求默认的Servlet,匹配规则为urlPattern等于(“/“)
  3. Wrapper[] exactWrappers,使用精确匹配的Servlet,urlPattern不符合其他情况
  4. Wrapper[] wildcardWrappers,匹配规则为urlPattern是以(“/*”)结尾的Servlet
  5. Wrapper[] extensionWrappers,匹配规则为urlPattern是以(“*.”)开始的Servlet


ProtocolHandler接口

ProtocolHandler是一个接口,表示某种协议的处理器,比如如果是HTTP/1.1协议,那么对应的就是org.apache.coyote.http11.Http11Protocol类。

所以我们这里着重来分析Http11Protocol类,其他协议类似。

Http11Protocol类

Http11Protocol类表示HTTP/1.1协议,而Tomcat实际上一个服务端,所以Http11Protocol类只要实现处理请求并响应的逻辑即可。

在Http11Protocol类中,包含两个重要的属性:

  1. AbstractEndpoint endpoint
  2. Http11ConnectionHandler cHandler

AbstractEndpoint表示端点,当一个服务器接收到网络请求后,操作系统会将请求包装为Socket,而AbstractEndpoint实际上就是对ServerSocket的一种包装,负责接收底层socket请求。

Http11ConnectionHandler是真正的请求处理器。

Http11Protocol的构造方法实现:

  1. public Http11Protocol() {
  2. endpoint = new JIoEndpoint();
  3. cHandler = new Http11ConnectionHandler(this);
  4. ((JIoEndpoint) endpoint).setHandler(cHandler);
  5. setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
  6. setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
  7. setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
  8. }

可以看到AbstractEndpoint的实现类是JIoEndpoint,表示以BIO的形式接收Socket请求。

另外Http11Protocol继承AbstractHttp11JsseProtocol, AbstractHttp11JsseProtocol继承AbstractHttp11Protocol, AbstractHttp11Protocol继承AbstractProtocol,AbstractProtocol最终实现ProtocolHandler。

上文中的protocolHandler.init()方法实际上调用的是AbstractProtocol.init()方法,protocolHandler.start()方法实际上调用的是AbstractProtocol.start()方法。

而这两个方法核心就是调用endpoint的init()或start()。

那么Http11ConnectionHandler什么时候会用到呢?它是请求处理器,自然在真正接收到请求之后才被用到。

AbstractEndpoint抽象类

AbstractEndpoint表示一个接收请求的终端,上文分析到当使用HTTP/1.1协议时,用的JioEndpoint,基于Bio实现的。

JIoEndpoint类

JioEndpoint继承AbstractEndpoint类

AbstractEndpoint.init方法

AbstractEndpoint类需要基于Socket去绑定端口,绑定端口这一步可以在初始化阶段去做,Tomcat提供了一个配置项,可以控制需不需要在初始化阶段去绑定端口,配置项为bindOnInit,默认为true,表示在初始化阶段就会进行端口绑定,绑定使用bind()方法,该方法的实现在具体的实现类JIoEndpoint中。

AbstractEndpoint.start方法

如果bindOnInit为false,那么会在启动节点去绑定端口,端口绑定完成之后,会调用JIoEndpoint.startInternal()。

JIoEndpoint.startInternal方法

该方法主要负责以下两件事:

  1. 初始化一个LimitLatch,LimitLatch是用来进行请求数量控制的。
  2. 开启Acceptor线程,Acceptor是用来接收请求并进行处理的,是JIoEndpoint的一个内部类,一个Acceptor就是一个线程

总结

一旦Acceptor初始化完成,Tomcat启动过程基本结束,从解析配置文件,到初始化各个组件,到绑定端口,这就是Tomcat启动流程。