Tomcat源码阅读

2015/10/25

Tomcat

Server

Server是Tomcat中最顶层的组件,它可以包含多个Service组件。

在Tomcat源码中Server组件对应源码中的org.apache.catalina.core.StandardServer

StandardServer的继承关系图如下图所示:

Service

Service组件相当于Connector和Engine组件的包装器,它将一个或者多个Connector组件和一个Engine建立关联。

在缺省的配置文件中,定义了一个叫Catalina的Service,并将Http,AJP这两个Connector关联到了一个名为Catalina的Engine。

Service组件对应Tomcat源码中的org.apache.catalina.core.StandardService

StandardService的继承关系图如下图所示:

Connector

Connector是Tomcat中监听TCP网络连接的组件,一个Connector会监听一个独立的端口来处理来自客户端的连接。

缺省的情况下Tomcat提供了如下两个Connector:

  • HTTP/1.1

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    

    上面定义了一个Connector,它缺省监听端口8080,这个端口我们可以根据具体情况进行改动。connectionTimeout定义了连接超时时间,单位是毫秒,redirectPort定义了ssl的重定向接口,根据缺省的配置,Connector会将ssl请求重定向到8443端口。

  • AJP/1.3

    AJP表示Apache Jserv Protocol,此连接器将处理Tomcat和Aapache http服务器之间的交互,这个连机器是用来处理我们将Tomcat和Apache http服务器结合使用的情况。假如在同样的一台物理Server上面部署了一台Apache http服务器和多台Tomcat服务器,通过Apache服务器来处理静态资源以及负载均衡的时候,针对不同的Tomcat实例需要AJP监听不同的端口。

Connector对应源代码中的org.apache.catalina.connector.Connector

它的继承关系图如下所示:

Engine

Tomcat中有一个容器的概念,而Engine,Host,Context都属于Container。

一个Engine可以包含一个或者多个Host,也就是说我们一个Tomcat的实例可以配置多个虚拟主机。

缺省的情况下<Engine name="Catalina" defaultHost="localhost">定义了一个名称为Cataline的Engine。

Engine对应源代码中的org.apache.catalina.core.StandardEngine

它的继承关系图如下图所示:

Host

Host定义了一个虚拟主机,一个虚拟主机可以有多个Context。

缺省的配置为<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">….</Host>

其中appBase为webapps,也就是<CATALINA_HOME>\webapps目录

unpackingWARS属性指定在appBase指定的目录中的war包都自动的解压,缺省配置为true

autoDeploy属性指定是否对加入到appBase目录的war包进行自动的部署,缺省为true。

Host对应源代码中的org.apache.catalina.core.StandardHost,它的继承关系图如下所示:

Context

在Tomcat中,每一个运行的webapp其实最终都是以Context的形成存在,每个Context都有一个根路径和请求URL路径。

Context对应源代码中的org.apache.catalina.core.StandardContext

它的继承关系图如下图所示:

在Tomcat中我们通常采用如下的两种方式创建一个Context

这样的话,我们就可以通过http://host:port/mypath访问上面配置的context了。

Valve

Valve是Tomcat中责任链模式的实现,通过链接多个Valve对请求进行处理。

其中Valve可以定义在任何的Container(Engine,Host,Context)中。

默认定义了一个名为org.apache.catalina.valves.AccessLogValve的Valve,这个Valve负责拦截每个请求,然后记录一条访问日志。

通过上面的分析,我们发现Server,Service,Engine,Host,Context都实现了org.apache.catalina.Lifecycle接口,通过这个接口管理了这些核心组件的生命周期。

生命周期方法的总体的骨架,如果用伪代码来表示可以简化为如下:
org.apache.catalina.util.LifecycleBase#lifeCycleMethod

public final synchronized void lfieCycleMethod() throws LifecycleException {
    stateCheck();//状态检查
    //设置为进入相应的生命周期之前的状态
    setStateInternal(LifecycleState.BEFORE_STATE, null, false);
    lfieCycleMethodInternal();//钩子方法
    //进入相应的生命周期之后的状态
    setStateInternal(LifecycleState.AFTER_STATE, null, false);
}

通用调用过程:

org.apache.catalina.core.StandardServer#init
->org.apache.catalina.core.StandardService#init
-->org.apache.catalina.connector.Connector#init
-->org.apache.catalina.core.StandardEngine#init

StandardEngine#init方法的时候,发现并没有进行StandardHost的初始化

StarndardHost的init方法是在调用start方法的时候被初始化

org.apache.catalina.startup.Bootstrap#start
->org.apache.catalina.startup.Catalina#start 通过反射调用
-->org.apache.catalina.core.StandardServer#start
--->org.apache.catalina.core.StandardService#start
---->org.apache.catalina.core.StandardEngine#start
---->org.apache.catalina.Executor#start
---->org.apache.catalina.connector.Connector#start


org.apache.catalina.startup.Bootstrap#main
->org.apache.catalina.startup.Bootstrap#init
->org.apache.catalina.startup.Bootstrap#load
-->org.apache.catalina.startup.Catalina#load
--->org.apache.catalina.core.StandardServer#init
---->org.apache.catalina.core.StandardService#init
----->org.apache.catalina.connector.Connector#init
----->org.apache.catalina.core.StandardEngine#init
->org.apache.catalina.startup.Bootstrap#start
-->org.apache.catalina.startup.Catalina#start 通过反射调用
--->org.apache.catalina.core.StandardServer#start
---->org.apache.catalina.core.StandardService#start
----->org.apache.catalina.core.StandardEngine#start
----->org.apache.catalina.Executor#start
----->org.apache.catalina.connector.Connector#start

StandardEngine初始化的时候,也是初始化了一个线程池;而StandardHost也初始化了一个线程池。他们的不同点在与创建线程的工厂方法不同,在采用缺省配置的情况下,StandardEngine的线程池中的线程是以Catalina-startStop的形式命名的,而StandardHost是以localhost-startStop的方式进行命名的。

对于StandardEngineStandardHost的启动,父容器在init的时候创建一个启动和停止子容器的线程池,然后父容器启动的时候首先通过异步的方式将子容器的启动通过org.apache.catalina.core.ContainerBase.StartChild提交到父容器中对应的线程池中进行启动,而子容器启动的时候首先会初始化,然后再启动。

webapps目录的应用又是如何启动的呢?webapps目录下面的应用其实是属于Context的,而Context对应Tomcat中的StandardContext类。

webapps目录中应用的启动在StandardHost#start的时候,通过Lifecycle.START_EVENT这个事件的监听器HostConfig进行进一步的启动。

Dameon线程

Dameon线程又叫后台或者守护线程,它负责在程序运行期提供一种通用服务的线程,比如垃圾收集线程。

非Dameon线程Dameon线程的区别就在于当程序中所有的非Daemon线程都终止的时候,Jvm会杀死余下的Dameon线程,然后退出。

Tomcat启动以后,会启动6条线程,他们分别如下:
"ajp-bio-8009-AsyncTimeout" daemon prio=5 tid=7f8738afe000 nid=0x115ad6000 waiting on condition [115ad5000]

"ajp-bio-8009-Acceptor-0" daemon prio=5 tid=7f8738b05800 nid=0x1159d3000 runnable [1159d2000]

"http-bio-8080-AsyncTimeout" daemon prio=5 tid=7f8735acb800 nid=0x1158d0000 waiting on condition [1158cf000]

"http-bio-8080-Acceptor-0" daemon prio=5 tid=7f8735acd000 nid=0x1157cd000 runnable [1157cc000]

"ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=5 tid=7f8732850800 nid=0x111203000 waiting on condition [111202000]

"main" prio=5 tid=7f8735000800 nid=0x10843e000 runnable [10843c000]

其中5条是Dameon线程,而对于Java程序来说,当所有非Dameon程序都终止的时候,Jvm就会退出,因此要想终止Tomcat就只需要将main这一条非Dameon线程终止了即可。

Tomcat启动的时候的主线程会在8005端口(默认配置,可以更改)上建立socket监听,当关闭的时候,最终其实就是新起了一个进程然后向Tomcat主线程监听的8005端口发送了一个SHUTDOWN字符串,这样主线程就会结束了,主线程结束了以后,因为其它的线程都是dameon线程,这样依赖Jvm就会退出了。

当用户请求服务器的时候,Connector会接受请求,从Socket连接中根据http协议解析出对应的数据,构造RequestResponse对象,然后传递给后面的容器处理,顶层容器是StandardEngineStandardEngine处理请求其实是通过容器的Pipeline进行的,而Pipeline其实最终是通过管道上的各个阀门进行的,当请求到达StandardEngineValve的时候,此阀门会将请求转发给对应StandardHost的Pipeline的第一个阀门处理,然后以此最终到达StandardHostValve阀门,它又会将请求转发StandardContext的Pipeline的第一个阀门,这样以此类推,最后到达StandardWrapperValve,此阀门会根据Request来构建对应的Servelt,并将请求转发给对应的HttpServlet处理。

HTTP请求行和请求头

GET /contextpath/querystring HTTP/1.1

Host: 127.0.0.1:8080

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:23.0) Gecko/20100101 Firefox/23.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Cookie: JSESSIONID=9F5897FEF3CDBCB234C050C132DCAE52; __atuvc=384%7C39; __utma=96992031.358732763.1380383869.1381468490.1381554710.38; __utmz=96992031.1380383869.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Hm_lvt_21e144d0df165d6556d664e2836dadfe=1381330561,1381368826,1381395666,1381554711

Connection: keep-alive

Cache-Control: max-age=0

其中请求行就是第一行,GET /contextpath/querystring HTTP/1.1,余下的都是请求头。

请求行末尾必须是CRLF,而请求行与请求头,以及请求头之间必须用空行隔开,而空行也必须只包含CRLF。

一个请求解析流程:

org.apache.tomcat.util.net.JIoEndpoint.Acceptor#run
->org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor#run(请求处理线程池中运行)
-->org.apache.coyote.AbstractProtocol.AbstractConnectionHandler#process
--->org.apache.coyote.http11.AbstractHttp11Processor#process
---->org.apache.coyote.http11.InternalInputBuffer#parseRequestLine
---->org.apache.coyote.http11.InternalInputBuffer#parseHeaders
---->org.apache.catalina.connector.CoyoteAdapter#service

对于StandardEngine来说有一个与之对应的StandardEngineValve,对于StandardHost有一个StandardHostValve与之对应,StandardContext有一个StandardContextValve与之对应,StandardWrapper与StandardWrapperValve对应

调用链

->org.apache.catalina.core.StandardEngineValve#invoke
-->org.apache.catalina.valves.AccessLogValve#invoke
--->org.apache.catalina.valves.ErrorReportValve#invoke
---->org.apache.catalina.core.StandardHostValve#invoke
----->org.apache.catalina.authenticator.AuthenticatorBase#invoke
------>org.apache.catalina.core.StandardContextValve#invoke
------->org.apache.catalina.core.StandardWrapperValve#invoke

最后会调用到StandardWrapperValve,它其实也是最终调用Servlet的地方

一个请求过来以后,Tomcat是如何一步步处理的:

  • 用户浏览器发送请求,请求会发送到对应的Connector监听的Socket端口。
  • Connector从Socket流中获取数据,然后根据Http协议将其解析为Request和Reponse对象
  • 找到Request对象对应的Host,Context,Wrapper
  • 调用最终的Servelt的service进行处理。
Tomcat中ClassLoader的总体结构,

总结如下:

  • 在Tomcat存在common,cataina,shared三个公共的classloader,

    默认情况下,这三个classloader其实是同一个,都是common classloader,

  • 而针对每个webapp,也就是context(对应代码中的StandardContext类),都有自己的WebappClassLoader来加载每个应用自己的类。

Tomcat在加载webapp级别的类的时候,默认是不遵守parent-first的,这样做的好处是更好的实现了应用的隔离,但是坏处就是加大了内存浪费,同样的类库要在不同的app中都要加载一份。

们可以看出每一个StandardContext会关联一个Manager,默认情况下Manager的实现类是StandardManager,而StandardManager内部会聚合多个Session,其中StandardSession是Session的默认实现类,当我们调用Request.getSession的时候,Tomcat通过StandardSessionFacade这个外观类将StandardSession包装以后返回。

如果采用LAST_ACCESS_AT_START的时候,那么请求本身的处理时间将不算在内。比如一个请求处理开始的时候是10:00,请求处理花了1分钟,那么如果LAST_ACCESS_AT_STARTtrue,则算是否超期的时候,是从10:00算起,而不是10:01

每一个容器都有一个pipeline,而一个pipeline又有多个Valve阀门,其中StandardEngine对应的阀门是StandardEngineValveStandardHost对应的阀门是StandardHostValveStandardContext对应的阀门是StandardContextValveStandardWrapper对应的阀门是StandardWrapperValve

对于阀门来说有两种,一种阀门在处理完自己的事情以后,只需要将工作委托给下一个和自己在同一管道的阀门,第二种阀门是负责衔接各个管道的,它负责将请求传递给下个管道的第一个阀门处理,这种阀门叫Basic阀门,它是每个管道中最后一个阀门,上面的Standard*Valve都属于第二种阀门。

Reference:

http://imtiger.net/blog/2013/10/16/tomcat-architecture/