Tomcat系列文章专栏:https://blog.csdn.net/hancoder/category_11180472.html
一、Tomcat架构
先上一张Tomcat的顶层结构图(图A),如下:
层次关系为:
Server
--Service 多个
----Connector 多个
----Engine
------Host
--------Context
----------Wrapper
容器结构总结为SSCE
引擎总结为EHCW
Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server
可以包含至少一个Service
,用于具体提供服务。
Service
主要包含两个部分:Connector
和Container
。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下:
1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;
2、Container用于封装和管理Servlet,以及具体处理Request请求;
一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container
,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接,示意图如下(Engine、Host、Context下边会说到):
多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了!所以整个 Tomcat 的生命周期由 Server 控制。
另外,上述的包含关系或者说是父子关系,都可以在tomcat/conf/server.xml
配置文件中看出(Tomcat版本为8.0)
详细的配置文件文件内容可以到Tomcat官网查看:http://tomcat.apache.org/tomcat-8.0-doc/index.html
上边的配置文件,还可以通过下边的一张结构图更清楚的理解:
Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个,Service左边的内容都属于Container的,Service下边是Connector。
1 server
前面说了service提供对外的服务,而server管理service
对应Server组件,逻辑上表示整个Tomcat,即整个Catalina Servlet容器。它处于Tomcat顶层,可以包含一个或多个Service层。Tomcat提供了该层接口的一个默认实现,所以通常不需要用户自己去实现。
server.xml
是tomcat 服务器的核心配置文件,包含了Tomcat
的 Servlet 容器(Catalina)的所有配置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。
tomcat是一个servlet容器,同时他是一个server,全局唯一
Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是org.apache.catalina.core.StandardServer
<Server port="8005" shutdown="SHUTDOWN">
...
</Server>
可选参数:
- port : Tomcat 监听的关闭服务器的端口。
- shutdown: 关闭服务器的指令字符串,调优的时候要关闭他。
Server内嵌的子元素为 Listener、GlobalNamingResources、Service。
对于监听器:默认配置的5个Listener ,实现了LifecycleListener接口
,是server的监听器
<!-- 用于以日志形式输出服务器 、操作系统、JVM的版本信息-->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 用于加载(服务器启动) 和 销毁 (服务器停止) APR。 如果找不到APR库, 则会输出日志, 并不影响Tomcat启动 -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- 用于避免JRE内存泄漏问题 -->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- 用户加载(服务器启动) 和 销毁(服务器停止) 全局命名服务 -->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<!-- 用于在Context停止时重建Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
GlobalNamingResources 中定义了全局命名服务:
<!-- Global JNDI resources
Documentation at /docs/jndi‐resources‐howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat‐users.xml" />
</GlobalNamingResources>
2 Service
对应Service组件,是包含在Server层中的一个逻辑功能层。它包含一个Engine层,以及一个或多个连接器(Connector)。
service=多个连接器+一个servlet容器
Service组件将一个或多个Connector组件绑定到Engine层上,Connector组件侦听端口,获得用户请求,并将请求交给Engine层处理,同时把处理结果发给用户,从而实现一个特定的实际功能。Tomcat提供了Service接口的默认实现,所以通常也不需要用户定制。
一个Server服务器,可以包含多个Service服务。
该元素用于创建 Service 实例,默认使用 org.apache.catalina.core.StandardService
。默认情况下,Tomcat 仅指定了Service 的名称, 值为 “Catalina
”。Service 可以内嵌的子元素为 :Listener、Executor、Connector、Engine
,其中 :
- Listener 用于为Service添加生命周期监听器,
Executor
用于配置Service 共享线程池,如果连接器没有指定自己的线程池,将使用这个线程池- Connector 用于配置Service 包含的链接器,
- Engine 用于配置Service中链接器对应的Servlet 容器引擎。
<Service name="Catalina">
...
</Service>
3 Connector
Connector 用于创建链接器实例。默认情况下,server.xml 配置了两个链接器,
- 一个支持HTTP协议,
- 一个支持AJP协议。
因此大多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进行优化。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
----------------------------------------
可以配置的参数参考如下
<Connector port="8080"
protocol="HTTP/1.1"
executor="tomcatThreadPool"
maxThreads="1000"
minSpareThreads="100"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
redirectPort="8443"
URIEncoding="UTF‐8" />
属性说明:
-
port: 端口号,Connector 用于创建服务端Socket 并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用。
-
executor:本连接器使用的线程池,指向
<executor>
标签的引用name -
protocol : 当前Connector 支持的访问协议。 默认为
HTTP/1.1
, 并采用自动切换机制选择一个基于 JAVANIO
的链接器或者基于本地APR
的链接器(根据本地是否含有Tomcat的本地库判定)。-
如果不希望采用上述自动切换的机制, 而是明确指定协议, 可以使用以下值。
-
// 对于Http协议: org.apache.coyote.http11.Http11NioProtocol // 非阻塞式 Java NIO 链接器 org.apache.coyote.http11.Http11Nio2Protocol // 非阻塞式 JAVA NIO2 链接器 org.apache.coyote.http11.Http11AprProtocol // APR 链接器 <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" /> ------------------ // 对于AJP协议 org.apache.coyote.ajp.AjpNioProtocol // 非阻塞式 Java NIO 链接器 org.apache.coyote.ajp.AjpNio2Protocol //非阻塞式 JAVA NIO2 链接器 org.apache.coyote.ajp.AjpAprProtocol //APR 链接器
-
-
connectionTimeOut : Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。
-
redirectPort:接收到一个请求,当前Connector 不支持SSL请求, 接收到了一个请求, 并且也符合security-constraint 约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。
-
executor
: 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads等属性配置内部线程池。 -
URIEncoding : 用于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 ,
- Tomcat7.x版本默认为ISO-8859-1。
3.2 Executor
线程池在service中没有executor标签的话就使用的默认的线程池,多个连接器共享一个线程池。
默认情况下,Service 并未添加共享线程池配置。 如果我们想添加一个线程池, 可以在下添加如下配置:
<!--在service标签中配置线程池-->
<Executor name="tomcatThreadPool"
namePrefix="catalina‐exec‐"
maxThreads="200"
minSpareThreads="100"
maxIdleTime="60000"
maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false"
threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
然后在下面的connector标签中指定
jconsole
工具,找到本地线程的bootstrap,可以在线程里看到http-nio-exec-ID
ajp-nio-exec-ID
线程(原有的)。如果我们自己命名了<Executor name,那么就能看到我们自己的name-exec-线程id然后我们在下面的connector标签中配置好后,再查看的话会发现比如nio-exec的名字变了
属性 | 含义 |
---|---|
name | 线程池名称,用于 Connector中指定。 |
namePrefix | 所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber。 |
maxThreads | 池中最大线程数。 |
minSpareThreads | 活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。 |
maxIdleTime | 线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒。 |
maxQueueSize | 在被执行前最大线程排队数目,默认为Int的最大值,也就是广义的无限。除非特殊情况,这个值不需要更改,否则会有请求不会被处理的情况发生。 |
prestartminSpareThreads | 启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动。 |
threadPriority | 线程池中线程优先级,默认值为5,值从1到10。 |
className | 线程池实现类,未指定情况下,默认实现类为 org.apache.catalina.core.StandardThreadExecutor。如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。 |
如果不配置共享线程池,那么Catalina 各组件在用到线程池时会独立创建。
4 container四大容器
在后门我们将上面的组件称为E、H、C、W
4.0 管道
你可能听说过了tomcat有4大容器,而且每个容器有自己的管道。我把四大容器称为EHCW
,他们是==父子容器==的关系。
Tomcat里还有个组件pipeline,他有个list属性walve
阀门。walve实现类有
实现类为:
StandardEngineValve
、StandardHostValve
、StandardContextValve
、StandardWrapperValve
。
可以实现
RequestFilterWalve
接口自定义阀门。可以记录日志
Container处理请求是使用Pipeline-Valve管道
来处理的!(Valve是阀门之意,通过这个阀门后才进入下一个阀门)
Pipeline-Valve是责任链模式
,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后,在函数体的最后一句交给下一个处理着继续处理。(转入下一个pipeline处理)
在源码中体现为,
请求来了之后new Request,request.setHost,req.setContext,req.setWarpper,
// service的getPipeline().getFirst().invoke()方法
connector.getService().getContainer().getPipeline().getFirst().invoke();
// 在invoke()里最后一句又调用host的方法
host.getPipeline().getFirst().invoke();
// 在他的最后一句又调用context的方法
context.getPipeline().getFirst().invoke();
// 在invoke()里最后一句又调用wrapper的方法
wrapper.getPipeline().getFirst().invoke();
// 然后在wrapper里行过滤链FilterChain,在过滤链的doFilter()里追踪到`Servlet`的`service()`方法
wrapper.allocate()得到servlet,
4.1 Engine管理多个主机
对应Engine组件,该层是请求分发处理层,可以连接多个Connector。它从Connector接收请求后,解析出可以完成用户请求的URL,根据该URL可以把请求匹配到正确的Host上,当Host处理完用户请求后,Engine层把结果返回给适合的连接器,再由连接器传输给用户。该层的接口一般不需要用户来定制,特殊情况下,用户可以通过实现该接口来提供自定义的引擎。
实现类:StandardEngine
Engine 作为Servlet 引擎的顶级元素,内部可以嵌入: Cluster、Listener、Realm、Valve和Host
。
<Engine name="Catalina"
defaultHost="localhost">
...
</Engine>
- name
- defaultHost:默认的host。主机输入localhost的时候默认找的host
engine四大组件:
- Cluster: 实现tomcat集群,例如session共享等功能,通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的。其实现方式是基于pipeline+valve模式的,有时间会专门整理一个pipeline+valve模式应用系列;
- 在调优的文章里我们会看到他可以解决session共享问题,原理是复制,明显不好,所以很少用
- Realm: 实现用户权限管理模块,例如用户登录,访问控制等,通过通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的;
- Pipeline: 每个容器对象都有一个pipeline,它不是通过server.xml配置产生的,是必须有的。它就是容器对象实现逻辑操作的骨架,在pipeline上配置不同的valve,当需要调用此容器实现逻辑时,就会按照顺序将此pipeline上的所有valve调用一遍,这里可以参考责任链模式;
- Valve: 实现具体业务逻辑单元。可以定制化valve(实现特定接口),然后配置在server.xml里。对其包含的所有host里的应用有效。定制化的valve是可选的,但是每个容器有一个缺省的valve,例如engine的StandardEngineValve,是在StandardEngine里自带的,它主要实现了对其子host对象的StandardHostValve的调用,以此类推。
4.2 Host虚拟主机
对应Host组件,该层表示一个虚拟主机,一个Engine层可以包含多个Host层,每个Host层可以包含一个或多个Context层,对应不同的Web应用。因为Tomcat给出的Host接口的实现(类StandardHost)提供了重要的附加功能,所以用户通常不需要定制Host。
实现类StandardHost
每个host对应一个webapps目录,记住是webapps,不是下面的项目。因为tomcat里原来只配置了一个localhost,所以可以理解为host就是webapps,而context是我们的项目应用。
Host 元素用于配置一个虚拟主机, 它支持以下嵌入元素:Alias、Cluster、Listener、Valve、Realm、Context。如果在Engine下配置Realm, 那么此配置将在当前Engine下的所有Host中共享。 同样,如果在Host中配置Realm , 则在当前Host下的所有Context中共享。Context中的Realm优先级 >Host 的Realm优先级 >Engine中的Realm优先级。
<Host name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="true">
...
</Host>
属性说明:
- 1)
name
: 当前Host通用的网络名称, 必须与DNS服务器上的注册信息一致。 Engine中包含的Host必须存在一个名称与Engine的defaultHost设置一致。 - 2)
appBase
: 当前Host的应用基础目录, 当前Host上部署的Web应用均在该目录下(可以是绝对目录,相对路径)。默认为webapps
。这个选项对我们理解很重要 - 3)
unpackWARs
: 设置为true, Host在启动时会将appBase目录下war包解压为目录。设置为false, Host将直接从war文件启动。 - 4)
autoDeploy
: 控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用。热部署
怎么理解host,难道我们一台主机还能有多个ip?的确可以,一方面我们可以安装多个网卡设置多个ip,另一方面可以修改/etc/hosts文件,让不同的域名解析DNS都映射到本机的ip,这叫虚拟主机。下面我们假设增加一个host标签
通过给Host添加别名,我们可以实现同一个Host拥有多个网络名称,配置如下:
<Host name="www.AAA.com"
appBase="webapps"
unpackWARs="true"
autoDeploy="true">
<Alias>www.web2.com</Alias>
</Host>
我们就可以通过www.AAA.com访问那个host了,而不只是localhost
这个时候,我们就可以通过两个域名访问当前Host下的应用(需要确保DNS或hosts中添加了域名的映射配置)。
浏览器请求–>去etc/hosts下进行域名解析,拿到你自己配置的,转到tomcat里
这样就实现了虚拟主机,虽然www.aaa.com 和www.bbb.com不同,但都转到了tomcat下
4.3 Context应用
对应Context组件,该层代表某个虚拟主机上的实际目录或一个WAR,即单个Web应用程序,它运行在特定的虚拟主机中,使用最为频繁。一个Host层包含多个Context层,每一个Context都有唯一的路径,Host层接到请求后,根据用户请求的URL,将请求定位到Context层。
部署应用的方式:war,文件夹,server.xml,conf。war包解压后就是文件夹,文件夹下必须有WEB-INF/子文件夹,WEB-INF/classes存放class,servlet可以放到这里。类上注解@WebServlet可以指定拦截地址。所以我们得去找dispatcherServlet类。tomcat中有实现类StandardContext
- 描述符的方式可以指定不放在webapps下的文件夹
- 配置文件里有unpackWARs属性指定默认解压war包
- Jar包里只有class文件,需要我们配置其他目录
- War那个会负责遍历.war结尾的文件,不会管.jar
我们在webapps下可以看到root、examples等目录,这些都是项目,root是指如果只是知道了www主机名,没有指定要访问哪个项目,默认访问的就是那个下面,比如www.baidu.com ,只是指定了host,没有指定context,那么访问的就是root那个项目。
Context 用于配置一个Web应用,默认的配置如下:
<Context docBase="myApp"
path="/App">
....
</Context>
属性描述:
- 1)
docBase
:Web应用目录或者War包的部署物理路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径。 - 2)
path
:Web应用的Context 访问路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/App。
它支持的内嵌元素为:CookieProcessor, Loader, Manager,Realm,Resources,WatchedResource,JarScanner,Valve。
<Host name="www.tomcat.com" appBase="webapps" unpackWARs="true"
autoDeploy="true">
<Context docBase="D:\servlet_project03" path="/myApp"></Context>
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log"
suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
</Host>
4.4 Servlet视图
Servlet你可以理解为Wrapper
Wrapper:Wrapper是context下面的,httpServlet实现类只有一个实例,共用的话请求过来都调用同一个,会并发不安全。为了解决安全,可以实现singleThreadModel接口,单线程模型,此时每个请求都是一个实例。tomcat最多20实例。多出来的请求阻塞。重用会重新初始化。
tomcat中有实现类StandardWrapper
- 同一个context下可能有多个类型servlet实例,所以要用wrapper对servlet分类
四个容器的关系:阀门
Tomcat里还有个组件pipeline,他有个list属性walve
阀门。模型像责任链一样。4大容器下面都有管道。比如记录返回日志的阀门,访问指定层次的容器就会经过阀门,可以实现RequestFilterWalve
接口自定义阀门。
请求来了之后新建请求和设置对应的HCW
new Request,request.setHost,req.setContext,req.setWarpper,
比如 在engine级别处理完了之后就会调用getEngine.getPipeline.getFirstWalve.invoke(req)
最后一个servlet可能重用可能new
四大容器都有默认的阀门,负责继续调用下层的getEngine.getPipeline.getFirstWalve.invoke(req)
类似的代码。
Wrapper的最后一个阀门交给servlet实例调用方法
class Tomcat{
Connector connector;
List<Servlet> servlet;
}
public class HostConfig implements LifecycleListener {
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 部署xml,Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// 部署war包,Deploy WARs
deployWARs(appBase, filteredAppPaths);
// 部署文件夹,Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
数据,操作系统,通过socket过去数据,endpoint,解析数据,解析成req
tomcat7默认是BIO
5 tomcat-users.xml
该配置文件中,主要配置的是Tomcat的用户,角色等信息,用来控制Tomcat中manager, host-manager的访问权限。
二、Tomcat顶层架构小结:
- (1)
Tomcat
中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container; - (2)
Server
掌管着整个Tomcat的生死大权; - (4)
Service
是对外暴露端口,提供服务;- k8s中的service也是暴露端口的
- (5)
Connector
用于接受请求并将请求封装成Request和Response来具体处理; - (6)
Container
用于封装和管理Servlet,以及具体处理request请求;
知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用,对于绝大多数的开发人员来说Server和Service对我们来说确实很远,而我们开发中绝大部分进行配置的内容是属于Connector和Container的,所以接下来介绍一下Connector和Container。
三、Connector和Container的微妙关系
由上述内容我们大致可以知道一个请求发送到Tomcat之后,首先经过Service然后会交给我们的Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!
Connector最底层使用的是Socket
来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议!
Tomcat既然处理请求,那么肯定需要先接收到这个请求,接收请求这个东西我们首先就需要看一下Connector!
四、Connector连接器
Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。
因此,我们可以把Connector分为四个方面进行理解:
(1)Connector如何接受请求的?
(2)如何将请求封装成Request和Response的?
(3)封装完之后的Request和Response如何交给Container进行处理的?
(4)Container处理完之后如何交给Connector并返回给客户端的?
首先看一下Connector的结构图(图B),如下所示:
Connector就是使用ProtocolHandler(协议处理器)来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol
使用的是普通Socket
来连接的,Http11NioProtocol
使用的是NioSocket
来连接的。
其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。
Endpoint
用来处理底层Socket的网络连接,因此Endpoint
是用来实现TCP/IP
协议的Processor
用于将Endpoint接收到的Socket封装成Request,Processor
用来实现HTTP
协议的Adapter
用于将Request交给Container进行具体的处理(将请求适配到Servlet容器)。
此外Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。
Acceptor
用于监听请求,AsyncTimeout
用于检查异步Request的超时,Handler
用于处理接收到的Socket,在内部调用Processor进行处理。
至此,我们应该很轻松的回答(1)(2)(3)的问题了,但是(4)还是不知道,那么我们就来看一下Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的?
五、Container架构分析
Container用于封装和管理Servlet,以及具体处理Request请求,在Connector
内部包含了4个子容器,结构图如下(图C):
4个子容器的作用分别是:
- (1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
- (2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
- (3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
- (4)Wrapper:每一Wrapper封装着一个Servlet;
下面找一个Tomcat的文件目录对照一下,如下图所示:
Context和Host的区别是Context表示一个应用,我们的Tomcat中默认的配置下webapps
下的每一个文件夹目录都是一个Context
,其中ROOT目录中存放着主应用,其他目录存放着子应用,而整个webapps就是一个Host站点。
我们访问应用Context的时候,如果是ROOT下的则直接使用域名就可以访问,例如:www.ledouit.com,如果是Host(webapps)下的其他应用,则可以使用www.ledouit.com/docs进行访问,当然默认指定的根应用(ROOT)是可以进行设定的,只不过Host站点下默认的主营用是ROOT目录下的。
看到这里我们知道Container是什么,但是还是不知道Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的?别急!下边就开始探讨一下Container是如何进行处理的!
六、Container如何处理请求的
Container处理请求是使用Pipeline-Valve管道
来处理的!(Valve是阀门之意)
Pipeline-Valve是责任链模式
,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。
但是!Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点:
(1)每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做BaseValve
,BaseValve是不可删除的;
(2)在上层容器的管道的BaseValve中会调用下层容器的管道。
我们知道Container包含四个子容器,而这四个子容器对应的BaseValve分别在:StandardEngineValve
、StandardHostValve
、StandardContextValve
、StandardWrapperValve
。
Pipeline的处理流程图如下(图D):
(1)Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器
的Pipeline就是EnginePipeline
(Engine的管道);
(2)在Engine
的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve
,在StandardEngineValve中会调用Host管道,然后再依次执行Host
的HostValve1、HostValve2等,最后在执行StandardHostValve
,然后再依次调用Context
的管道和Wrapper
的管道,最后执行到StandardWrapperValve
。
(3)当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain
,并调用其doFilter()
方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter
的doFilter()
方法和Servlet
的service()
方法,这样请求就得到了处理!
(4)当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。
项目部署的知识:
一个web容器可以运行多个web应用程序,每个web应用程序都有一个唯一的上下文根,上下文根如何部署是和具体的web容器相关的。
%CATALINA_HOME%\webapps
目录下的每一个子目录都是一个独立的Web应用,子目录的名字就是该Web应用的上下文根。
例如:应用程序A和应用程序B分别位于%CATALINA_HOME%\webapps\a
和%CATALINA_HOME%\webapps\b
,则a是A应用的,b是B应用的
当tomcat启动时,会自动加载webapps目录下的Web应用程序,所以在这个目录下的Web应用程序不需要进行其他的配置就可以直接访问了。
但是我们只在部署的时候才这么做,开发的适合在其他目录下开发,然后配置虚拟目录。配置可以在xml配置文件中通过元素完成。
Context元素属性 | 描述 | |
---|---|---|
backgroundProcessorDelay | 这个值代表在context及其子容器(包括所有的wrappers)上调用backgroundProcess方法的延时,以秒为单位。如果延时值非负,子容器不会被调用,也就是说子容器使用自己的处理线程。如果该值为正,会创建一个新的线程。在等待指定的时间以后,该线程在主机及其 子容器上调用backgroundProcess方法。context利用后台处理session过期,监测类的变化用于重新载入。如果没有指定,该属性的缺省值是-1,说明context依赖其所属的Host的后台处理。 | |
className | 实现的Java类名。该类必须实现org.apache.catalina.Context接口。如果没有指定,使用标准实现(在下面定义)。 | |
cookies | 如果想利用cookies来传递session identifier(需要客户端支持cookies),设为ture。否则为false,这种情况下只能依靠URL Rewriting传递session identifier。 | |
crossContext | 如果想在应用内调用ServletContext.getContext()来返回在该虚拟主机上运行的其他web application的request dispatcher,设为true。在安全性很重要的环境中,设为false,使得getContext()总是返回null。缺省值为false。 | |
docBase | 该web应用的文档基准目录(Document Base,也称为Context Root),或者是WAR文件的路径。可以使用绝对路径,也可以使用相对于context所属的Host的appBase路径。 | |
override | 如果想利用该Context元素中的设置覆盖DefaultContext中相应的设置,设为true。缺省情况下使用DefaultContext中的设置。 | |
privileged | 设为true,允许context使用container servlets,比如manager servlet。 | |
path | web应用的context路径。catalina将每个URL的起始和context path进行比较,选择合适的web应用处理该请求。特定Host下的context path必须是惟一的。如果context path为空字符串(""),这个context是所属Host的缺省web应用,用来处理不能匹配任何context path的请求。 | |
reloadable | 如果希望Catalina监视/WEB-INF/classes/和/WEB-INF/lib下面的类是否发生变化,在发生变化的时候自动重载web application,设为true。这个特征在开发阶段很有用,但也大大增加了服务器的开销。因此,在发布以后,不推荐使用。但是,你可以使用Manager应用在必要的时候触发应用的重载。 | |
wrapperClass | org.apache.catalina.Wrapper实现类的名称,用于该Context管理的servlets。如果没有指定,使用标准的缺省值。 |
元素是元素的子元素,可以在conf/server.xml中设置Context元素。还可以把放在下列位置的文件中
- conf/context.xml,将被所有的web应用程序加载
- conf/[enginename]/[hostname]/context.xml.default。该路径下的信息将被属于该虚拟主机的所有web应用程序所加载。
- enginename表示在server.xml文件中设置的元素name属性的值(一般Catalina)
- hostname表示在server.xml文件中设置的元素name属性的值(一般localhost)
- conf/[enginename]/[hostname]/xxx.xml文件,在这个文件中,元素的docBase属性通常是web应用程序的绝对路径名,或者是web应用程序归档文件的绝对路径名
- 在web应用程序的目录下增加META-INF/context.xml文件。
在tomcat5.5开始,在conf/[enginename]/[hostname]/xxx.xml中配置,tomcat将以xml文件名作为web应用程序的上下文路径,而不管你再元素中配置的path是什么,
webapps目录下的web应用程序,如果没有在任何文件中配置元素,那么Tomcat将为这个web应用程序自动生成元素。自动生成的元素的上下文路径将以/开始,后面紧跟web应用程序所在目录的名字。如果目录的名字是ROOT,那么上下文路径僵尸一个空字符串""(将作为虚拟主机的默认web应用程序)。所以webapps目录下的web应用程序可以不经配置而直接使用
如果想将开发的目录直接配置成web应用程序运行的目录,而不是放在webapps下。可以在两个地方配置。可以编辑conf/server.xml、设置<Context>
元素。
<Host>
<Context path="虚拟" docBase="绝对路径" reloadable="true"/>
reloadable代表更改后tomcat会重新加载。就不需要频繁重启tomcat了。
也可以在conf/Catalina/localhost下创建虚拟路径.xml
,编辑内容为
<Context path="虚拟" docBase="绝对路径" reloadable="true"/>
此时注意把server.xml中之前配置的删去。