Tomcat 基础知识
简介Tomcat 是什么Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。由于 Tomcat 本身也内含了一个 HTTP 服务器,它也可以被视作一个单独的 Web 服务器。但是,不能将 Tomcat 和 Apache HTT...
简介
Tomcat 是什么
Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。
由于 Tomcat 本身也内含了一个 HTTP 服务器,它也可以被视作一个单独的 Web 服务器。但是,不能将 Tomcat 和 Apache HTTP 服务器混淆,Apache HTTP 服务器是一个用 C 语言实现的 HTTP Web 服务器;这两个 HTTP web server 不是捆绑在一起的。Tomcat 包含了一个配置管理工具,也可以通过编辑XML格式的配置文件来进行配置。
Tomcat 重要目录
- /bin - Tomcat 脚本存放目录(如启动、关闭脚本)。
*.sh
文件用于 Unix 系统;*.bat
文件用于 Windows 系统。 - /conf - Tomcat 配置文件目录。
- /logs - Tomcat 默认日志目录。
- /webapps - webapp 运行的目录。
web 工程发布目录结构
一般 web 项目路径结构
|-- webapp # 站点根目录
|-- META-INF # META-INF 目录
| `-- MANIFEST.MF # 配置清单文件
|-- WEB-INF # WEB-INF 目录
| |-- classes # class文件目录
| | |-- *.class # 程序需要的 class 文件
| | `-- *.xml # 程序需要的 xml 文件
| |-- lib # 库文件夹
| | `-- *.jar # 程序需要的 jar 包
| `-- web.xml # Web应用程序的部署描述文件
|-- <userdir> # 自定义的目录
|-- <userfiles> # 自定义的资源文件
webapp
:工程发布文件夹。其实每个 war 包都可以视为 webapp 的压缩包。
META-INF
:META-INF 目录用于存放工程自身相关的一些信息,元文件信息,通常由开发工具,环境自动生成。
WEB-INF
:Java web应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。
/WEB-INF/classes
:存放程序所需要的所有 Java class 文件。
/WEB-INF/lib
:存放程序所需要的所有 jar 文件。
/WEB-INF/web.xml
:web 应用的部署配置文件。它是工程中最重要的配置文件,它描述了 servlet 和组成应用的其它组件,以及应用初始化参数、安全管理约束等。
安装
前提条件
Tomcat 8.5 要求 JDK 版本为 1.7 以上。
进入 Tomcat 官方下载地址 选择合适版本下载,并解压到本地。
Windows
添加环境变量 CATALINA_HOME
,值为 Tomcat 的安装路径。
进入安装目录下的 bin 目录,运行 startup.bat 文件,启动 Tomcat
Linux / Unix
下面的示例以 8.5.24 版本为例,包含了下载、解压、启动操作。
# 下载解压到本地
wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz
tar -zxf apache-tomcat-8.5.24.tar.gz
# 启动 Tomcat
./apache-tomcat-8.5.24/bin/startup.sh
配置
本节将列举一些重要、常见的配置项。详细的 Tomcat8 配置可以参考 Tomcat 8 配置官方参考文档 。
Server
Server 元素表示整个 Catalina servlet 容器。
因此,它必须是
conf/server.xml
配置文件中的根元素。它的属性代表了整个 servlet 容器的特性。
属性表
属性 | 描述 | 备注 |
---|---|---|
className | 这个类必须实现org.apache.catalina.Server接口。 | 默认 org.apache.catalina.core.StandardServer |
address | 服务器等待关机命令的TCP / IP地址。如果没有指定地址,则使用localhost。 | |
port | 服务器等待关机命令的TCP / IP端口号。设置为-1以禁用关闭端口。 | |
shutdown | 必须通过TCP / IP连接接收到指定端口号的命令字符串,以关闭Tomcat。 |
Service
Service元素表示一个或多个连接器组件的组合,这些组件共享一个用于处理传入请求的引擎组件。Server 中可以有多个 Service。
属性表
属性 | 描述 | 备注 |
---|---|---|
className | 这个类必须实现org.apache.catalina.Service 接口。 | 默认 org.apache.catalina.core.StandardService |
name | 此服务的显示名称,如果您使用标准 Catalina 组件,将包含在日志消息中。与特定服务器关联的每个服务的名称必须是唯一的。 |
实例 - conf/server.xml
配置文件示例
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8080" shutdown="SHUTDOWN">
<Service name="xxx">
...
</Service>
</Server>
Executor
Executor表示可以在Tomcat中的组件之间共享的线程池。
属性表
属性 | 描述 | 备注 |
---|---|---|
className | 这个类必须实现org.apache.catalina.Executor 接口。 | 默认 org.apache.catalina.core.StandardThreadExecutor |
name | 线程池名称。 | 要求唯一, 供Connector元素的executor属性使用 |
namePrefix | 线程名称前缀。 | |
maxThreads | 最大活跃线程数。 | 默认200 |
minSpareThreads | 最小活跃线程数。 | 默认25 |
maxIdleTime | 当前活跃线程大于minSpareThreads时,空闲线程关闭的等待最大时间。 | 默认60000ms |
maxQueueSize | 线程池满情况下的请求排队大小。 | 默认Integer.MAX_VALUE |
<Service name="xxx">
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="300" minSpareThreads="25"/>
</Service>
Connector
Connector代表连接组件。Tomcat 支持三种协议:HTTP/1.1、HTTP/2.0、AJP。
属性表
属性 | 说明 | 备注 |
---|---|---|
asyncTimeout | Servlet3.0规范中的异步请求超时 | 默认30s |
port | 请求连接的TCP Port | 设置为0,则会随机选取一个未占用的端口号 |
protocol | 协议. 一般情况下设置为 HTTP/1.1,这种情况下连接模型会在NIO和APR/native中自动根据配置选择 | |
URIEncoding | 对URI的编码方式. | 如果设置系统变量org.apache.catalina.STRICT_SERVLET_COMPLIANCE为true,使用 ISO-8859-1编码;如果未设置此系统变量且未设置此属性, 使用UTF-8编码 |
useBodyEncodingForURI | 是否采用指定的contentType而不是URIEncoding来编码URI中的请求参数 |
以下属性在标准的Connector(NIO, NIO2 和 APR/native)中有效:
属性 | 说明 | 备注 |
---|---|---|
acceptCount | 当最大请求连接maxConnections满时的最大排队大小 | 默认100,注意此属性和Executor中属性maxQueueSize的区别.这个指的是请求连接满时的堆栈大小,Executor的maxQueueSize指的是处理线程满时的堆栈大小 |
connectionTimeout | 请求连接超时 | 默认60000ms |
executor | 指定配置的线程池名称 | |
keepAliveTimeout | keeAlive超时时间 | 默认值为connectionTimeout配置值.-1表示不超时 |
maxConnections | 最大连接数 | 连接满时后续连接放入最大为acceptCount的队列中. 对 NIO和NIO2连接,默认值为10000;对 APR/native,默认值为8192 |
maxThreads | 如果指定了Executor, 此属性忽略;否则为Connector创建的内部线程池最大值 | 默认200 |
minSpareThreads | 如果指定了Executor, 此属性忽略;否则为Connector创建线程池的最小活跃线程数 | 默认10 |
processorCache | 协议处理器缓存Processor对象的大小 | -1表示不限制.当不使用servlet3.0的异步处理情况下: 如果配置Executor,配置为Executor的maxThreads;否则配置为Connnector的maxThreads. 如果使用Serlvet3.0异步处理, 取maxThreads和maxConnections的最大值 |
Context
Context元素表示一个Web应用程序,它在特定的虚拟主机中运行。每个Web应用程序都基于Web应用程序存档(WAR)文件,或者包含相应的解包内容的相应目录,如Servlet规范中所述。
属性表
属性 | 说明 | 备注 |
---|---|---|
altDDName | web.xml部署描述符路径 | 默认 /WEB-INF/web.xml |
docBase | Context的Root路径 | 和Host的appBase相结合, 可确定web应用的实际目录 |
failCtxIfServletStartFails | 同Host中的failCtxIfServletStartFails, 只对当前Context有效 | 默认为false |
logEffectiveWebXml | 是否日志打印web.xml内容(web.xml由默认的web.xml和应用中的web.xml组成) | 默认为false |
path | web应用的context path | 如果为根路径,则配置为空字符串(""), 不能不配置 |
privileged | 是否使用Tomcat提供的manager servlet | |
reloadable | /WEB-INF/classes/ 和/WEB-INF/lib/ 目录中class文件发生变化是否自动重新加载 | 默认为false |
swallowOutput | true情况下, System.out和System.err输出将被定向到web应用日志中 | 默认为false |
Engine
Engine元素表示与特定的Catalina服务相关联的整个请求处理机器。它接收并处理来自一个或多个连接器的所有请求,并将完成的响应返回给连接器,以便最终传输回客户端。
属性表
属性 | 描述 | 备注 |
---|---|---|
defaultHost | 默认主机名,用于标识将处理指向此服务器上主机名称但未在此配置文件中配置的请求的主机。 | 这个名字必须匹配其中一个嵌套的主机元素的名字属性。 |
name | 此引擎的逻辑名称,用于日志和错误消息。 | 在同一服务器中使用多个服务元素时,每个引擎必须分配一个唯一的名称。 |
Host
Host元素表示一个虚拟主机,它是一个服务器的网络名称(如“www.mycompany.com”)与运行Tomcat的特定服务器的关联。
属性表
属性 | 说明 | 备注 |
---|---|---|
name | 名称 | 用于日志输出 |
appBase | 虚拟主机对应的应用基础路径 | 可以是个绝对路径, 或${CATALINA_BASE}相对路径 |
xmlBase | 虚拟主机XML基础路径,里面应该有Context xml配置文件 | 可以是个绝对路径, 或${CATALINA_BASE}相对路径 |
createDirs | 当appBase和xmlBase不存在时,是否创建目录 | 默认为true |
autoDeploy | 是否周期性的检查appBase和xmlBase并deploy web应用和context描述符 | 默认为true |
deployIgnore | 忽略deploy的正则 | |
deployOnStartup | Tomcat启动时是否自动deploy | 默认为true |
failCtxIfServletStartFails | 配置为true情况下,任何load-on-startup >=0的servlet启动失败,则其对应的Contxt也启动失败 | 默认为false |
tomcat内存配置及配置参数详解
1、jvm内存管理机制:
1)堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。
可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,
所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try...catch捕捉。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,
-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)
上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。
XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。
说说为什么会内存益出:
(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。
(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。
这种错误常见在web服务器对JSP进行pre compile的时候。
2)JVM内存限制(最大值)
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,
这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了
2、jvm参数说明:
-server:一定要作为第一个参数,在多个CPU时性能佳
-Xms:java Heap初始大小。 默认是物理内存的1/64。
-Xmx:java heap最大值。建议均设为物理内存的一半。不可超过物理内存。
-XX:PermSize:设定内存的永久保存区初始大小,缺省值为64M。
-XX:MaxPermSize:设定内存的永久保存区最大 大小,缺省值为64M。
-XX:SurvivorRatio=2 :生还者池的大小,默认是2,如果垃圾回收变成了瓶颈,您可以尝试定制生成池设置
-XX:NewSize: 新生成的池的初始大小。 缺省值为2M。
-XX:MaxNewSize: 新生成的池的最大大小。 缺省值为32M。
如果 JVM 的堆大小大于 1GB,则应该使用值:-XX:newSize=640m -XX:MaxNewSize=640m -XX:SurvivorRatio=16,或者将堆的总大小的 50% 到 60% 分配给新生成的池。调大新对象区,减少Full GC次数。
+XX:AggressiveHeap 会使得 Xms没有意义。这个参数让jvm忽略Xmx参数,疯狂地吃完一个G物理内存,再吃尽一个G的swap。
-Xss:每个线程的Stack大小,“-Xss 15120” 这使得JBoss每增加一个线程(thread)就会立即消耗15M内存,而最佳值应该是128K,默认值好像是512k.
-verbose:gc 现实垃圾收集信息
-Xloggc:gc.log 指定垃圾收集日志文件
-Xmn:young generation的heap大小,一般设置为Xmx的3、4分之一
-XX:+UseParNewGC :缩短minor收集的时间
-XX:+UseConcMarkSweepGC :缩短major收集的时间 此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。
-XX:userParNewGC 可用来设置并行收集【多核CPU】
-XX:ParallelGCThreads 可用来增加并行度【多核CPU】
-XX:UseParallelGC 设置后可以使用并行清除收集器【多核CPU】
3、配置tomcat调用的虚拟机内存大小
(1)直接设置tomcat
Linux
修改TOMCAT_HOME/bin/catalina.sh
位置cygwin=false前。
JAVA_OPTS="-server -Xms256m -Xmx512m -XX:PermSize=64M -XX:MaxPermSize=128m" (仅做参考,具体数值根据自己的电脑内存配置)
windows
修改TOMCAT_HOME/bin/catalina.bat
第一行加上
JAVA_OPTS="-server -Xms256m -Xmx512m -XX:PermSize=64M -XX:MaxPermSize=128m"
(2)配置环境变量
环境变量中设 变量名:JAVA_OPTS 变量值:-Xms512m -Xmx512m
4、补充
1. 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?
通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:
1) 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;
2) -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,
如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。
2. 为何将上面的参数写入到eclipse.ini文件Eclipse没有执行对应的设置?
那为什么同样的参数在快捷方式或者命令行中有效而在eclipse.ini文件中是无效的呢?这是因为我们没有遵守eclipse.ini文件的设置规则:
参数形如“项 值”这种形式,中间有空格的需要换行书写,如果值中有空格的需要用双引号包括起来。比如我们使用-vm C:/Java/jre1.6.0/bin/javaw.exe参数设置虚拟机,
在eclipse.ini文件中要写成这样:
-vm
C:/Java/jre1.6.0/bin/javaw.exe
-vmargs
-Xms128M
-Xmx512M
-XX:PermSize=64M
-XX:MaxPermSize=128M
实际运行的结果可以通过Eclipse中“Help”-“About Eclipse SDK”窗口里面的“Configuration Details”按钮进行查看。
另外需要说明的是,Eclipse压缩包中自带的eclipse.ini文件内容是这样的:
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
-vmargs
-Xms40m
-Xmx256m
其中–launcher.XXMaxPermSize(注意最前面是两个连接线)跟-XX:MaxPermSize参数的含义基本是一样的,我觉得唯一的区别就是前者是eclipse.exe启动的时候设置的参数,
而后者是eclipse所使用的JVM中的参数。其实二者设置一个就可以了,所以这里可以把–launcher.XXMaxPermSize和下一行使用#注释掉。
3. 其他的启动参数。 如果你有一个双核的CPU,也许可以尝试这个参数:
-XX:+UseParallelGC
让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)
补充:
如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。
解决方法: 设置MaxPermSize大小
可以在myelipse里选中相应的服务器比如tomcat5,展开里面的JDK子项页面,来增加服务器启动的JVM参数设置:
-Xms128m
-Xmx256m
-XX:PermSize=128M
-XX:MaxNewSize=256m
-XX:MaxPermSize=256m
另外需要考虑的是Java提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。如果你把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为保证最好的性能,要把堆的大小设大,保证垃圾收集不在整个基准测试的过程中出现。 如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过 3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究 垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。
Tomcat整体结构
架构图:
各组件解释:
从顶层开始:
- Server是Tomcat的最顶层元素,是service的集合,即可包含多个service,Server控制整个Tomcat的生命周期。
- Service由一个Container和多个Connector组成(或者说由Connector,Engine和线程池[可选]组成),形成一个独立完整的处理单元,对外提供服务。
一般情况下我们并不需要配置多个Service,conf/server.xml默认配置了一个“Catalina”的<Service>
。
Tomcat将Engine,Host,Context,Wrapper统一抽象成Container。
Connector接受到请求后,会将请求交给Container,Container处理完了之后将结果返回给Connector
下面看Container的结构:
- Engine:没有父容器,一个 Engine代表一个完整的 Servlet 引擎,它接收来自Connector的请求,并决定传给哪个Host来处理,Host处理完请求后,将结果返回给Engine,Engine再将结果返回给Connector。
- Host:Engine可以包含多个Host,每个Host代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们,每个虚拟主机对应的一个域名,不同Host容器接受处理对应不同域名的请求。
- Context:Host可以包含多个Context,Context是Servlet规范的实现,它提供了Servlet的基本环境,一个Context代表一个运行在Host上的Web应用
- Wrapper: Context可以包含多个Wrapper, Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
组件包含关系
Standard*XXXX*是组件接口的默认实现类。
其它组件
Tomcat 还有其它组件,如安全组件 security、logger、session、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。
组件的生命线Lifecycle
Tomcat中很多组件具有生命周期,如初始化、启动、关闭,这些组件的生命周期具有共性,因此Tomcat中将其抽象为接口Lifecycle,来控制组件的生命周期,它通过 事件机制 实现各个容器间的内部通讯。
Lifecycle接口的方法:
继承关系图:
StandardServer,StandardService,Connector和上面4个容器等很多组件都实现了Lifecycle,组件实现这个接口就可以统一被拥有它的组件控制了,这样一层一层的直到一个 最高级的组件 就可以控制 Tomcat 中所有组件的生命周期,这个最高的组件就是 Server
。
Tomcat启动流程
在bin目录下执行了./startup.sh 或者执行 ./catalina.bat start命令时,实际调用了Bootstrap启动类的main方法,并传递了start参数。
Bootstrap#main方法的启动流程:
参考http://blog.csdn.net/Zerohuan/article/details/50752635#t6
附上别人总结的一张启动时序图:
补充如下:
- service的initInternal方法中调用 container.init() 是如何层级调用Engine,Host,Context,Wrapper的初始化方法?
- StandardService中container属性就是StandardEngine,这个init只将StandardEngine进行了初始化。Host,Context,Wrapper的初始化在哪里?在调用Host的start时,进行初始化
if (state.equals(LifecycleState.NEW)) {init();}
。
- StandardService中container属性就是StandardEngine,这个init只将StandardEngine进行了初始化。Host,Context,Wrapper的初始化在哪里?在调用Host的start时,进行初始化
- 同上,service的startInternal调用container.start,调用了Engine,Host的startInternal,Context的start在哪里调用了?
- Host启动时触发事件,HostConfig监听到STARTING事件后会调用Context.start。
- 在tomcat7中,mapperListener的初始化和启动是封装在Connector中的。
- 关于Connector初始化和启动的更多细节,可参考本人另一篇blog http://blog.csdn.net/cx520forever/article/details/52198050
Pipeline valve机制
- pipeline 管道,可以比作车间生产线,在这里可认为是容器的逻辑处理总线
- valve 阀门,可以比作生产线上的工人,负责完成各自的部分工作。
生产线(pipeline)可以配置其工人的位置。
与车间生产线不同的是,tomcat中pipeline的不同是valve拿到输入,处理完成后,需要将输出返回给调用方。
四个基本容器对象里面都有一个pipeline及valve模块,是容器类必须具有的模块,对象生成时set该属性。Pipeline就像是每个容器的逻辑总线。在pipeline上按照配置的顺序,加载各个valve。通过pipeline完成各个valve之间的调用,各个valve实现具体的应用逻辑。
tomcat组件图:
从上图中看到,在Connector接收到一次连接并转化成HttpServletRequest请求对象后,请求传递如下:
Connector–>Engine的Pipeline的ValveA中–>Engine Valve–>Host Pipeline的Error Report Valve和Host Value–>Context Valve–>Wrapper Valve中,在这里会经过一个过滤器链(Filter Chain)–>Servlet中。
Servlet处理完成后一步步返回,最后Connector拿到response。
接口中定义的方法:
一个pipeline包含多个Valve,这些阀共分为两类,一类叫基础阀(通过getBasic、setBasic方法调用),一类是普通阀(通过addValve、removeValve调用)。管道都是包含在容器中,所以有getContainer和setContainer方法。一个管道一般有一个基础阀(通过setBasic添加),可以有0到多个普通阀(通过addValve添加)。
isAsyncSupported:当管道中的所有阀门都支持异步时返回ture,否则返回false
该接口的标准实现是:org.apache.catalina.core.StandardPipeline
Engine、Host、Context及Wrapper的pipeline属性都继承自父类ContainerBase。
接口方法:
重点关注setNext、getNext、invoke这三个方法,通过setNext设置该阀的下一阀,通过getNext返回该阀的下一个阀的引用,invoke方法则执行该阀内部自定义的请求处理代码。
ValveBase:是Valve接口的基本实现
四大容器类r都有各自缺省的标准valve实现。它们分别是
- StandardEngineValve:StandardEngine中的唯一阀门,主要用于从request中选择其host映射的Host容器StandardHost。
- StandardHostValve:StandardHost中最后的阀门,主要用于从request中选择其context映射的Context容器StandardContext以及访问request中的Session以更新会话的最后访问时间。
- StandardContextValve:StandardContext中的唯一阀门,主要作用是禁止任何对WEB-INF或META-INF目录下资源的重定向访问,对应用程序热部署功能的实现,从request中获得StandardWrapper。
- StandardWrapperValve:StandardWrapper中的唯一阀门,主要作用包括调用StandardWrapper的loadServlet方法生成Servlet实例和调用ApplicationFilterFactory生成Filter链。
Valve实现了具体业务逻辑单元。可以定制化valve(实现特定接口),然后配置在server.xml里。每层容器都可以配置相应的valve,当只在其作用域内有效。例如engine容器里的valve只对其包含的所有host里的应用有效。
配置举例:
<Engine name="Catalina" defaultHost="localhost">
<Valve className="MyValve0"/>
<Valve className="MyValve1"/>
<Valve className="MyValve2"/>
……
<Host name="localhost" appBase="webapps">
</Host>
</Engine
当在server.xml文件中配置了一个定制化valve时,会调用pipeline对象的addValve方法,将valve以链表方式组织起来,代码如下;
@Override
public void addValve(Valve valve) {
// Validate that we can add this Valve
if (valve instanceof Contained)
((Contained) valve).setContainer(this.container);
// Start the new component if necessary
if (getState().isAvailable()) {
if (valve instanceof Lifecycle) {
try {
((Lifecycle) valve).start();
} catch (LifecycleException e) {
log.error("StandardPipeline.addValve: start: ", e);
}
}
}
// Add this Valve to the set associated with this Pipeline
//将配置的valve添加到链表中,并且每个容器的标准valve在链表的尾端
if (first == null) {
first = valve;
valve.setNext(basic);
} else {
Valve current = first;
while (current != null) {
if (current.getNext() == basic) {
current.setNext(valve);
valve.setNext(basic);
break;
}
current = current.getNext();
}
}
container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}
valve按照容器作用域的配置顺序来组织valve,每个valve都设置了指向下一个valve的next引用。同时,每个容器缺省的标准valve都存在于valve链表尾端,最后被调用。
Pipeline内部维护first和basic两个阀,其它相关阀通过getNext来获取。
标准valve的调用逻辑图:
从StandardEngineValve开始, 所有的基础阀的实现最后都会调用其下一级容器,所有的普通阀都会执行getNext().invoke(request, response);
,一直到StandardWrapperValve,完成请求处理过程。因为Wrapper是对一个Servlet的包装,所以它的基础阀内部调用的过滤器链的doFilter方法和Servlet的service方法。
上述机制保证了请求传递到servlet去处理。
当采用tomcat默认初始配置时,Valve链如下:
这些阀门Valve通过invoke方法彼此串联起来,最终构成的执行顺序十分类似于一个管道。
Tomcat中的设计模式
把通用的骨架步骤抽象到父类中,子类去实现特定的某些步骤。
举例:
如LifecycleBase类中init和start方法,其中的nitInternal和startInternal方法是抽象方法,所有容易都直接或间接继承了LifecycleBase,在初始化和启动时被每个容器会调用其init和start方法,这些抽象方法都是在子类中实现的。
- Tomcat中的ApplicationFilterChain实现了Filter拦截和实际Servlet的请求,是典型的责任链模式。
- Pipeline-Valve机制也是责任链模式,从Engine到Host再到Context一直到Wrapper都是通过一个链来传递请求。
Tomcat通过LifecycleListener对组件生命周期组件Lifecycle进行监听,各个组件在其生命期中会有各种行为,而这些行为都会触发相应的事件,Tomcat就是通过侦听这些事件达到对这些行为进行扩展的目的。在看组件的init和start过程中会看到大量如:fireLifecycleEvent(CONFIGURE_START_EVENT, null);
这样的代码,这就是对某一类型事件的触发,如果你想在其中加入自己的行为,就只用注册相应类型的事件即可。
更多推荐
所有评论(0)