Bootstrap类
当使用startup.sh脚本启动时,startup.sh会调用catalina.sh脚本进行启动,在catalina.sh脚本里会执行org.apache.catalina.startup.Bootstrap类,参数为”start”.
源码org.apache.catalina.startup.Bootstrap类中有一个main方法,该方法执行流程如下图:
- 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方法执行流程:
- 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方法执行流程:
- 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()方法的执行流程为:
- 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的启动方法,核心流程就两步:
- globalNamingResources.start():命名空间的启动
- services[i].start():service启动
总结
Server是Tomcat架构中最顶层元素,在它的初始化和启动过程中,它主要负责一些全局共享的东西进行初始化和启动,以及让它的子节点Service进行相应初始化和启动即可。
Service接口
Service接口也继承了Lifecycle接口,也具有生命周期,它对应的默认实现类是StandardService类。
Service表示一个服务,是Server下的子节点,表示一个Server下可以有多个服务,每个服务可以有不同的实现与构造。
StandardService
initInternal方法
initInternal方法是StandardService的初始化方法,核心执行流程为:
- 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()方法中,它的核心步骤是这样的:
- new一个taskqueue队列
- new一个TaskThreadFactory线程工厂
- new一个ThreadPoolExecutor线程池
非常简单,以后有任务需要StandardThreadExecutor来执行,直接调用StandardThreadExecutor的execute方法即可。
Connector类
Connector不是一个接口,而是一个类。
Connector表示请求接收器,请求是一个抽象概念,有Http请求,有Ftp请求,有其他基于各种各样协议所对应的请求。
server.xml文件中,可以对Connector节点配置protocol属性值,可以配置为”HTTP/1.1”、”AJP/1.3”或一个类的全限定名。
在Connector类的setProtocol方法中有如下判断:
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
public void setProtocolHandlerClassName(String protocolHandlerClassName) {
this.protocolHandlerClassName = protocolHandlerClassName;
}
protocolHandlerClassName是Connector的一个属性。
从上面的代码可以看出来,Connector是一个指定协议的请求接收器,一个Connector接收到请求后,交给具体的ProtocolHandler进行处理。
initInternal方法
initInternal方法的核心流程:
- 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方法的核心流程:
这里也先重点关注protocolHandler。
MapperListener启动
MapperListener中有一个重要的属性为Mapper mapper,Mapper中有以下几个重要的属性:
- Host[] hosts
- ContextVersion context
- String defaultHostName
Host有以下几个重要的属性:
- ContextList contextList,表示该Host下关联了哪些Context
- Host realHost,真正的Host信息,有name以及容器Host对象两个属性
- List
aliases,当前这个Host对应的其他别名Host
ContextList有以下几个重要属性:
- Context[] contexts,表示Host下关联的Context列表
- nesting
Context有以下几个重要属性:
- ContextVersion[] versions,表示每个Context下不同版本的列表
ContextVersion有以下几个重要属性,每个ContextVersion就表示一个WebApp了:
- String path,表示Context Path,也就是应用的名字
- Wrapper defaultWrapper,当前应用处理请求默认的Servlet,匹配规则为urlPattern等于(“/“)
- Wrapper[] exactWrappers,使用精确匹配的Servlet,urlPattern不符合其他情况
- Wrapper[] wildcardWrappers,匹配规则为urlPattern是以(“/*”)结尾的Servlet
- Wrapper[] extensionWrappers,匹配规则为urlPattern是以(“*.”)开始的Servlet
ProtocolHandler接口
ProtocolHandler是一个接口,表示某种协议的处理器,比如如果是HTTP/1.1协议,那么对应的就是org.apache.coyote.http11.Http11Protocol类。
所以我们这里着重来分析Http11Protocol类,其他协议类似。
Http11Protocol类
Http11Protocol类表示HTTP/1.1协议,而Tomcat实际上一个服务端,所以Http11Protocol类只要实现处理请求并响应的逻辑即可。
在Http11Protocol类中,包含两个重要的属性:
- AbstractEndpoint
endpoint Http11ConnectionHandler cHandler
AbstractEndpoint表示端点,当一个服务器接收到网络请求后,操作系统会将请求包装为Socket,而AbstractEndpoint实际上就是对ServerSocket的一种包装,负责接收底层socket请求。
Http11ConnectionHandler是真正的请求处理器。
Http11Protocol的构造方法实现:
public Http11Protocol() {
endpoint = new JIoEndpoint();
cHandler = new Http11ConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
可以看到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方法
该方法主要负责以下两件事:
- 初始化一个LimitLatch,LimitLatch是用来进行请求数量控制的。
- 开启Acceptor线程,Acceptor是用来接收请求并进行处理的,是JIoEndpoint的一个内部类,一个Acceptor就是一个线程
总结
一旦Acceptor初始化完成,Tomcat启动过程基本结束,从解析配置文件,到初始化各个组件,到绑定端口,这就是Tomcat启动流程。