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
在CATALINA-HOME\webapps目录中创建一个目录,这个时候将自动创建一个context,默认context的访问url为http://host:port/dirname,你也可以通过在ContextRoot\META-INF中创建一个context.xml的文件,其中包含如下的内容来指定应用的访问路径。
<Context path="/yourUrlPath" />
conf\server.xml文件中增加context元素。 第二种创建context的方法,我们可以选择在server.xml文件的
<Host>
元素,比如我们在server.xml文件中增加如下内容:...... ...... <Context path="/mypath" docBase="/Users/tiger/develop/xxx" reloadable="true"> </Context> </Host> </Engine> </Service> </Server>
这样的话,我们就可以通过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
的方式进行命名的。
对于StandardEngine
,StandardHost
的启动,父容器在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协议解析出对应的数据,构造Request
和Response
对象,然后传递给后面的容器处理,顶层容器是StandardEngine
,StandardEngine
处理请求其实是通过容器的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_START
为true
,则算是否超期的时候,是从10:00算起,而不是10:01
每一个容器都有一个pipeline,而一个pipeline又有多个Valve阀门,其中StandardEngine
对应的阀门是StandardEngineValve
,StandardHost
对应的阀门是StandardHostValve
,StandardContext
对应的阀门是StandardContextValve
,StandardWrapper
对应的阀门是StandardWrapperValve
。
对于阀门来说有两种,一种阀门在处理完自己的事情以后,只需要将工作委托给下一个和自己在同一管道的阀门,第二种阀门是负责衔接各个管道的,它负责将请求传递给下个管道的第一个阀门处理,这种阀门叫Basic阀门
,它是每个管道中最后一个阀门,上面的Standard*Valve
都属于第二种阀门。