Tomcat相关
一、server.xml
1.文件加载
(1)Bootstrap类的load方法使用反射机制调用Catalina类的load方法加载server.xml;
(2)而catalina的load方法使用了digester(用createStartDigester创建,创建的时候addRule),digester有一系列的Rule具体映射到server.xml;load使用FileInputStream获取conf/server.xml,并封装为InputStream;再调用digester的parse方法进行解析;最后initStream和server init。
2.文件解析
(1)Tomcat将server.xml中的所有元素的属性都抽象成Rule,然后对应到实现类上,比如server元素最后就会对应到Server类。
注解:SAX,相对DOM来说,SAX是一个更高效的XML文件解析方法。它是逐行扫描,边扫描边解析,边触发事件。SAX是基于事件驱动的,不必解析完整个文档,就可判断事件触发。事件源XMLReader,事件处理器:
| --------------------- | ------------------------------- | ---------------------------------------------------
|
| 事件处理器 | 事件处理器处理的事件 | XMLReader 注册方法 |
| --------------------- | ------------------------------- | --------------------------------------------------- |
|ContentHander
| XML 文档的开始与结束 | setContentHandler(ContentHandler h) |
| --------------------- | ------------------------------- | --------------------------------------------------- |
| DTDHander | 处理 DTD 解析 | setDTDHandler(DTDHandler h) |
| --------------------- | ------------------------------- | --------------------------------------------------- |
| ErrorHandler | 处理 XML 时产生的错误 | setErrorHandler(ErrorHandler h) |
| --------------------- | ------------------------------- | --------------------------------------------------- |
| EntityResolver | 处理外部实体 | setEntityResolver(EntityResolver e) |
| --------------------- | ------------------------------- | --------------------------------------------------- |
(2)使用SAX的ContentHandler解析
使用
SAX 解析 XML 文件一般有以下几个步骤:
1 、创建一个 SAXParserFactory 对象;
2 、调用 SAXParserFactory 中的 newSAXParser 方法创建一个 SAXParser 对象;
3 、然后在调用 SAXParser 中的 getXMLReader 方法获取一个 XMLReader 对象;
4 、实例化一个 DefaultHandler 对象;
5 、连接事件源对象 XMLReader 到事件处理类 DefaultHandler 中;
6 、调用 XMLReader 的 parse 方法从输入源中获取到的 xml 数据;
7 、通过 DefaultHandler 返回我们需要的数据集合。
digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
digester.addRuleSet(newNamingRuleSet("Server/Service/Engine/Host/Context/"));
Digester继承了DefaultHandler,而DefaultHandler默认实现了ContentHander、DTDHander、ErrorHandler及EntityResolver 这4个接口,代码如下:public class DefaultHandler implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
Digester解析XML的入口是其parse方法,其处理步骤如下:1.创建XMLReader ;中间通过SAXParser得到;
2.使用XMLReader解析XML。
XMLReader解析XML时,会生成事件,回调Digester的startDocument方法,解析的第一个元素是Server,此时回调Digester的startElement方法。
startElement方法的处理步骤如下:
1.match刚开始为空字符串,拼接Server后变为Server。
2.调用RulesBase的match方法,返回cache中按照键值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。
3.循环列表依次遍历ObjectCreateRule、SetPropertiesRule及SetNextRule,并调用它们的begin方法。
ObjectCreateRule的begin方法将生成Server的实例(默认为"org.apache.catalina.core.StandardServer",用户可以通过给Server标签指定className使用其它Server实现),最后将Server的实例压入Digester的栈中。
SetPropertiesRule的begin方法首先将刚才压入栈中的Server实例出栈,然后给Server实例设置各个属性值,如port、shutdown等。
二、生命周期管理
1.Tomcat生命周期管理类接口设计
- Lifecycle:定义了容器生命周期、容器状态转换及容器状态迁移事件的监听器注册和移除等主要接口;
- LifecycleBase:作为Lifecycle接口的抽象实现类,运用抽象模板模式将所有容器的生命周期及状态转换衔接起来,此外还提供了生成LifecycleEvent事件的接口;
- LifecycleSupport:提供有关LifecycleEvent事件的监听器注册、移除,并且使用经典的监听器模式,实现事件生成后触打监听器的实现;
- MBeanRegistration:Java jmx框架提供的注册MBean的接口,引入此接口是为了便于使用JMX提供的管理功能;
-
LifecycleMBeanBase:Tomcat提供的对MBeanRegistration的抽象实现类,运用抽象模板模式将所有容器统一注册到JMX;
每个容器由于继承自LifecycleBase,当容器状态发生变化时,都会调用fireLifecycleEvent方法,生成LifecycleEvent,并且交由此容器的事件监听器处理。
LifecycleBase的fireLifecycleEvent方法会调用LifecycleSupport的fireLifecycleEvent方法,LifecycleSupport的fireLifecycleEvent方法将事件通知给所有监听当前容器的生命周期监听器LifecycleListener,并调用LifecycleListener的lifecycleEvent方法。每个容器都维护这一个监听器缓存。
3.容器的生命周期
所有容器在构造的过程中,都会首先对父类LifecycleBase进行构造。初始化过程如下图:
1.
调用方调用容器父类LifecycleBase的init方法,LifecycleBase的init方法主要完成一些所有容器公共抽象出来的动作;
2. LifecycleBase的init方法调用具体容器的initInternal方法实现,此initInternal方法用于对容器本身真正的初始化;
3. 具体容器的initInternal方法调用父类LifecycleMBeanBase的initInternal方法实现,此initInternal方法用于将容器托管到JMX,便于运维管理;
4. LifecycleMBeanBase的initInternal方法调用自身的register方法,将容器作为MBean注册到MBeanServer;
5. 容器如果有子容器,会调用子容器的init方法;
6. 容器初始化完毕,LifecycleBase会将容器的状态更改为初始化完毕,即LifecycleState.INITIALIZED。
LifecycleBase的register方法(见代码清单9)会为当前容器创建对应的注册名称,以StandardServer为例,getDomain默认返回Catalina,因此StandardServer的JMX注册名称默认为Catalina:type=Server,真正的注册在registerComponent方法中实现。
Registry的registerComponent方法会为当前容器(如StandardServer)创建DynamicMBean,并且注册到MBeanServer。
4.容器启动
1.调用方调用容器父类LifecycleBase的start方法,LifecycleBase的start方法主要完成一些所有容器公共抽象出来的动作;
2.LifecycleBase的start方法先将容器状态改为LifecycleState.STARTING_PREP,然后调用具体容器的startInternal方法实现,此startInternal方法用于对容器本身真正的初始化;
3.具体容器的startInternal方法会将容器状态改为LifecycleState.STARTING,容器如果有子容器,会调用子容器的start方法启动子容器;
4.容器启动完毕,LifecycleBase会将容器的状态更改为启动完毕,即LifecycleState.STARTED。
LifecycleBase.init()->startInternal()->services.start();
三、启动服务和停止服务
1.startup.sh
PRGDIR:当前shell脚本所在的路径;
EXECUTABLE:脚本catalina.sh。
根据最后一行代码:exec "EXECUTABLE"
start "[email protected]",我们知道执行了shell脚本catalina.sh,并且传递参数start。
2.catalina.sh
最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。
3.Bootstrap的init方法的执行步骤如下:
(1.设置Catalina路径,默认为Tomcat的根目录;
(2.初始化Tomcat的类加载器,并设置线程上下文类加载器(具体实现细节,读者可以参考《TOMCAT源码分析——类加载体系》一文);
(3.用反射实例化org.apache.catalina.startup.Catalina对象,并且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外)。
4.当传递参数start的时候,会调用Bootstrap的load方法,其作用是用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件。当传递参数start的时候,调用Bootstrap的load方法之后会接着调用start方法启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法。
5.Catalina的start方法的执行步骤如下:
(1.验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer。
(2.启动Server容器;
(3.设置关闭钩子。这么说可能有些不好理解,那就换个说法。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要在退出的时候做一些清理工作,比如:内存清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递给Runtime的addShutdownHook方法即可。
(4.最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令。
(5.如果Tomcat运行正常且没有收到shutdown命令,是不会向下执行stop方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat。
Catalina的await方法实际只是代理执行了Server容器的await方法。
以Server的默认实现StandardServer为例,其await方法的执行步骤如下:
1.创建socket连接的服务端对象ServerSocket;
2.循环等待接收客户端发出的命令,如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待。
6.停止服务
Catalina的stopServer方法(见代码清单13)的执行步骤如下:
创建Digester解析server.xml文件(此处只解析标签),以构造出Server容器(此时Server容器的子容器没有被实例化);从实例化的Server容器获取Server的socket监听端口和地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令。根据代码清单9的内容,ServerSocket循环等待接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat。
我们看看Catalina的stop方法的实现,其执行步骤如下:将启动过程中添加的关闭钩子移除。Tomcat启动过程辛辛苦苦添加的关闭钩子为什么又要去掉呢?因为关闭钩子是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以不再需要这个钩子了。
停止Server容器。
通过对Tomcat源码的分析我们了解到Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了socket的通信。
1.Connector
我们知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是与HTTP请求处理相关的容器。Service是Server的子容器,而Connector又是Service的子容器。那么这三个容器的初始化顺序为:Server->Service->Connector。Connector的实现分为以下几种:
- Http Connector:基于HTTP协议,负责建立HTTP连接。它又分为BIO Http Connector与NIO Http Connector两种,后者提供非阻塞IO与长连接Comet支持。 AJP Connector:基于AJP协议,AJP是专门设计用于Tomcat与HTTP服务器通信定制的协议,能提供较高的通信速度和效率。如与Apache服务器集成时,采用这个协议。 APR HTTP Connector:用C实现,通过JNI调用的。主要提升对静态资源(如HTML、图片、CSS、JS等)的访问性能。现在这个库已独立出来可用在任何项目中。由于APR性能较前两类有很大提升,所以目前是Tomcat的默认Connector。
ProtocolHandler类继承体系:
有关ProtocolHandler的实现类都在org.apache.coyote包中
。前面所说的BIO Http Connector实际就是Http11Protocol,NIO Http Connector实际就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外还有一个MemoryProtocolHandler。
BIO
Http Connector的ProtocolHandler(即Http11Protocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的注册名为Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8009。AJP
Connector的MapperListener的注册名为Catalina:type=Mapper,port=8009。
(2)ProtocolHandler
Connector的startInternal方法的执行顺序如下:
- 将Connector容器的状态更改为启动中(LifecycleState.STARTING);
- 初始化ProtocolHandler;
- 启动ProtocolHandler;
- 初始化MapperListener。 ##初始化PROTOCOLHANDLER
Connector.startInternal()->ProtocolHandler.init()->JIoEndpoint的名称默认为http-8080,这里的JIoEndpoint是在调用Http11Protocol的构造器时创建的,Http11Protocol的构造器中还设置了socket的延迟关闭选项soLingerOn、socket的延时关闭秒数soLingerTime、socket连接超时时间soTimeout、提高socket性能的tcpNoDelay等选项。
Connector.startInternal()->ProtocolHandler.init()->生成ServerSocketFactory有三种方式:
如果在server.xml中配置Connector时指定了SSLEnabled="true"的属性,那么创建带有SSL(Secure Sockets Layer 安全套接层)的ServerSocketFactory;如果Http11Protocol指定了socketFactoryName,则使用socketFactoryName反射生成ServerSocketFactory实例;如果不满足以上2个条件,那么JIoEndpoint的init方法(见代码清单9)将创建ServerSocketFactory。当SSLEnabled="true"时,JIoEndpoint的init方法还会给ServerSocketFactory设置一些SSL相关的属性。最后使用此ServerSocketFactory创建serverSocket。此外,acceptorThreadCount属性用于指定接受连接的线程数,可以通过给Connector设置acceptorThreadCount属性进行调整,默认值为1。
JIoEndpoint以Catalina:type=ThreadPool,name=http-8080注册到JMX,cHandler.global(Http11ConnectionHandler的对象属性,类型为RequestGroupInfo)以Catalina:type=GlobalRequestProcessor,name=http-8080注册到JMX。最后调用JIoEndpoint的start方法接受请求的创建线程池并创建一定数量的接收请求线程。
(3)MapperListener
StandardService的子容器包括:StandardEngine、Connector和Executor。MapperListener本身会持有Connector,所以可以通过各个容器的父子关系,找到Connector的同级容器StandardEngine。StandardHost是StandardEngine的子容器。
findDefaultHost方法可以获取默认Host,Engine元素的defaultHost属性值必须要与配置的某个Host元素的name属性值相同。如果defaultHost的属性值配置无误,则会添加为MapperListener的Mapper对象属性的defaultHostName。
步骤二
将Host及其子容器Context,Context的子容器Wrapper注册到MapperListener的Mapper对象。
我们知道Mapper中维护着一个Host数组,每个Host中有一个ContextList,这个ContextList中维护着一个Context数组。每个Context维护着一个defaultWrapper,三个Wrapper数组(exactWrappers、wildcardWrappers、extensionWrappers)。下面对Host、Context及Wrapper进行功能上的介绍:
Host:代表一个虚拟主机,各Host的name不能相同,appBase代表各虚拟主机的应用发布位置;
Context:代表一个应用,Context可以根据应用的/WEB-INF/web.xml文件中定义的servlet来处理请求。一个Host下可以有多个Context;
Wrapper: 代表一个Servlet或者jsp,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。
以我本地为例,注册到Mapper中的Host及其子容器如图所示。
2.请求处理框架
Acceptor:负责从ServerSocket中接收新的连接,并将Socket转交给SocketProcessor处理。Acceptor是JIoEndpoint的内部类。Acceptor线程的默认大小为1,我们可以在server.xml的Connector配置中增加acceptorThreadCount的大小。
SocketProcessor:负责对Acceptor转交的Socket进行处理,包括给Socket设置属性、读取请求行和请求头等,最终将处理交给Engine的Pipeline处理。
ThreadPool:此线程池默认的最小线程数minSpareThreads等于10,最大线程数maxThreads等于200,我们可以在server.xml的Connector配置中调整它们的大小。
Pipeline:SocketProcessor线程最后会将请求进一步交给Engine容器的Pipeline,管道Pipeline包括一系列的valve,如:StandardEngineValve、AccessLogValve、ErrorReportValve、StandardHostValve、
StandardContextValve、 StandardWrapperValve,它们就像地下水管中的一个个阀门,每一个都会对请求数据做不同的处理。
FilterChain:管道Pipeline的最后一个valve是StandardWrapperValve,它会负责生成Servlet和Filter实例,并将它们组织成对请求处理的链条,这里真是Tomcat与J2EE规范相结合的部分。
Acceptor作为后台线程不断循环,每次循环都会sleep大约1秒钟(由于是线程级别的,所以并不保证准确),然后接收来自浏览器的Socket连接(用户在浏览器输入HTTP请求地址后,浏览器底层实际使用Socket通信的),最后将Socket交给外部类JIoEndpoint的processSocket方法处理。
JIoEndpoint的processSocket方法的处理步骤如下:
- 将Socket封装为SocketWrapper;
- 给SocketWrapper设置连接保持时间keepAliveLeft。这个值是通过调用父类AbstractEndpoint的getMaxKeepAliveRequests方法(见代码清单2)获得的;
- 创建SocketProcessor(此类也是JIoEndpoint的内部类,而且也实现了Runnable接口),并使用线程池执行。
SocketProcessor线程专门用于处理Acceptor转交的Socket,其执行步骤如下:
- 调用setSocketOptions方法给Socket设置属性,从中可以看到设置属性用到了SocketProperties的setProperties方法,状态更改为初始化完毕;
- 调用handler的process方法处理请求。当处理Http11Protocol协议时,handler默认为Http11Protocol的内部类Http11ConnectionHandler;
- 请求处理完毕后,如果state等于SocketState.CLOSED,则关闭Socket;如果state等于SocketState.OPEN,则保持连接;如果state等于SocketState.LONG,则会作为长连接对待。
Http11ConnectionHandler的process方法的处理步骤如下:
- 从Socket的连接缓存connections中获取依然Socket对应的Http11Processor;如果连接缓存connections中不存在Socket对应的Http11Processor,则从可以循环使用的recycledProcessors(类型为ConcurrentLinkedQueue)中获取;如果recycledProcessors中也没有可以使用的Http11Processor,则调用createProcessor方法创建Http11Processor;
- 如果当前Connector配置了指定了SSLEnabled="true",那么还需要给Http11Processor设置SSL相关的属性;
- 如果Socket是异步的,则调用Http11Processor的asyncDispatch方法,否则调用Http11Processor的process方法;
- 请求处理完毕,如果Socket是长连接的,则将Socket和Http11Processor一起放入connections缓存,否则从connections缓存中移除Socket和Http11Processor。
Http11Processor的process方法用于同步处理:
- InternalInputBuffer的parseRequestLine方法用于读取请求行;
- InternalInputBuffer的parseHeaders方法用于读取请求头;
- prepareRequest用于在正式处理请求之前,做一些准备工作,如根据请求头获取请求的版本号是HTTP/1.1还是HTTP/0.9、keepAlive是否为true等,还会设置一些输入过滤器用于标记请求、压缩等;
- 调用CoyoteAdapter的service方法处理请求。
CoyoteAdapter的service方法的执行步骤如下:
- 创建Request与Response对象并且关联起来;
- 调用postParseRequest方法对请求进行解析;
- 将真正的请求处理交给Engine的Pipeline去处理,代码:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
getContainer方法获取到的实际是StandardService中的StandardEngine容器,我们知道StandardEngine继承自ContainerBase,所以这里的getPipeline方法实际是ContainerBase实现的protected Pipeline pipeline = CatalinaFactory.getFactory().createPipeline(this);,这里的CatalinaFactory采用单例模式实现,要获取CatalinaFactory实例,只能通过调用getFactory方法。createPipeline方法中创建了StandardPipeline,StandardPipeline是Pipeline的标准实现。
随后调用了StandardPipeline的getFirst方法用来获取管道中的第一个Valve ,由于Tomcat并没有为StandardEngine的StandardPipeline设置first,因此将返回StandardPipeline的basic。basic的类型是StandardEngineValve,那么它是何时添加到StandardEngine的StandardPipeline中的呢?还记得ObjectCreateRule?在执行ObjectCreateRule的begin方法时,会反射调用StandardEngine的构造器生成StandardEngine的实例,StandardEngine的构造器中就会给其StandardPipeline设置basic为StandardEngineValve。
最后调用了StandardEngineValve的invoke方法正式将请求交给管道处理。request已经被映射到相对应的Context容器(比如/manager)。此处首先调用request的getHost方法(实质是通过request映射的Context容器获取父容器得到)获取Host容器,然后调用Host容器的Pipeline的getFirst方法获得AccessLogValve。AccessLogValve的invoke方法,从中可以看出调用了getNext方法获取Host容器的Pipeline的下一个Valve,并调用其invoke方法。
postParseRequest方法的执行步骤如下:
- 解析请求url中的参数;
- URI decoding的转换;
- 调用normalize方法判断请求路径中是否存在"\", "//", "/./"和"/../",如果存在则处理结束;
- 调用convertURI方法将字节转换为字符;
- 调用checkNormalize方法判断uri是否存在"\", "//", "/./"和"/../",如果存在则处理结束;
- 调用Connector的getMapper方法获取Mapper,然后调用Mapper的map方法对host和context进行匹配(比如http://localhost:8080/manager/status会匹配host:localhost,context:/manager),其实质是调用internalMap方法;
- 使用ApplicationSessionCookieConfig.getSessionUriParamName获取sessionid的key,然后获取sessionid;
- 调用parseSessionCookiesId和parseSessionSslId方法查找cookie或者SSL中的sessionid。
在Tomcat中管道Pipeline是一个接口,定义了使得一组阀门Valve按照顺序执行的规范,Pipeline中定义的接口如下:
- getBasic:获取管道的基础阀门;
- setBasic:设置管道的基础阀门;
- addValve:添加阀门;
- getValves:获取阀门集合;
- removeValve:移除阀门;
- getFirst:获取第一个阀门;
- isAsyncSupported:当管道中的所有阀门都支持异步时返回ture,否则返回false;
- getContainer:获取管道相关联的容器,比如StandardEngine;
- setContainer:设置管道相关联的容器。 Engine、Host、Context及Wrapper等容器都定义了自身的Pipeline,每个Pipeline都包含一到多个Valve。Valve定义了各个阀门的接口规范,其类继承体系如图1所示。
- -------------------------------------------
- Valve:定义了管道中阀门的接口规范,getNext和setNext分别用于获取或者设置当前阀门的下游阀门,invoke方法用来应用当前阀门的操作。
- ValveBase:Valve接口的基本实现,ValveBase与Valve的具体实现采用抽象模板模式将管道中的阀门串联起来。
- StandardEngineValve:StandardEngine中的唯一阀门,主要用于从request中选择其host映射的Host容器StandardHost。
- AccessLogValve:StandardHost中的第一个阀门,主要用于管道执行结束之后记录日志信息。
- ErrorReportValve:StandardHost中紧跟AccessLogValve的阀门,主要用于管道执行结束后,从request对象中获取异常信息,并封装到response中以便将问题展现给访问者。
- StandardHostValve:StandardHost中最后的阀门,主要用于从request中选择其context映射的Context容器StandardContext以及访问request中的Session以更新会话的最后访问时间。
- StandardContextValve:StandardContext中的唯一阀门,主要作用是禁止任何对WEB-INF或META-INF目录下资源的重定向访问,对应用程序热部署功能的实现,从request中获得StandardWrapper。
- StandardWrapperValve:StandardWrapper中的唯一阀门,主要作用包括调用StandardWrapper的loadServlet方法生成Servlet实例和调用ApplicationFilterFactory生成Filter链。 有了以上对Tomcat的管道设计的讲述,我们下面详细剖析其实现。
StandardWrapperValve的invoke方法的执行步骤如下:
- 调用StandardWrapper的allocate方法分配org.apache.catalina.servlets.DefaultServlet的实例处理访问包括*.html、*.htm、*.gif、*.jpg、*.jpeg等资源的request,分配org.apache.jasper.servlet.JspServlet的实例处理访问*.jpg页面的request。简单提下这些Servlet实例是在StandardContext启动的时候调用StandardWrapper的load方法用反射生成的。
- 确认当前request是否是Comet的,由于默认的DefaultServlet并未实现CometProcessor接口,所以不会作为Comet的请求处理。顺便简单提下,Comet 指的是一种 Web 应用程序的架构。在这种架构中,客户端程序(通常是浏览器)不需要显式的向服务器端发出请求,服务器端会在其数据发生变化的时候主动的将数据异步的发送给客户端,从而使得客户端能够及时的更新用户界面以反映服务器端数据的变化。
- 向客户端发送确认。
- 给request对象设置请求类型和请求路径属性。
- 获取ApplicationFilterFactory(单例模式实现),并调用其createFilterChain方法创建ApplicationFilterChain。
- 调用ApplicationFilterChain的doFilter方法,执行ApplicationFilterChain中维护的Filter职责链。
- 调用ApplicationFilterChain的release方法清空对Servlet、Filter的引用。
- 调用StandardWrapper的deallocate方法释放为其分配的Servlet。 注意:如果接收请求的Servlet实现了SingleThreadModel接口,那么singleThreadModel属性为true,则Tomcat的StandardWrapper中只有一个Servlet实例,否则会创建一个Servlet实例池。
- 从request中获取请求的类型(Tomcat目前提供的请求类型有REQUEST、FORWARD、INCLUDE、ASYNC及ERROR五种)与路径;
- 创建ApplicationFilterChain并设置给当前request;
- 给ApplicationFilterChain设置Servlet,即DefaultServlet;
- 从StandardContext中获取当前Context的filterMaps;
- 如果filterMaps为空,则说明当前Context没有配置Filter,否则会将filterMaps中的Filter全部添加到ApplicationFilterChain中的Filter职责链中。
执行过程进行介绍:
- StandardWrapperValve的invoke方法在创建完ApplicationFilterChain后,第一次调用ApplicationFilterChain的doFilter方法;
- 如果ApplicationFilterChain自身维护的Filter数组中还有没有执行的Filter,则取出此Filter并执行Filter的doFilter方法(即第3步),否则执行Servlet的service方法处理请求(即第4步);
- 每个Filter首先执行自身的过滤功能,最后在执行结束前会回调ApplicationFilterChain的doFilter方法,此时会将执行流程交给第2步;
- Servlet的service实际会调用自身的doGet、doHead、doPost、doPut、doDelete等方法。 第2步对应了图3中M.N这个标记的M部分,第3步则对应N的部分。
ApplicationFilterChain最后会执行Servlet的service方法,此service方法实际是所有Servlet的父类HttpServlet实现的,service方法调用重载的service方法,后者通过判断HttpServletRequest对象的HTTP
Method,调用不同的方法,如GET、DELETE、POST等。为什么doGet方法的实现只是返回了400和405错误呢?因为这是抽象类HttpServlet的默认实现,用户必须实现自身的Servlet或者使用默认的DefaultServlet。
五、session管理
omcat内部定义了Session和HttpSession这两个会话相关的接口,其类继承体系如图1所示。
StandardSession:标准的HTTP Session实现,本文将以此实现为例展开。
在部署Tomcat集群时,需要使集群中各个节点的会话状态保持同步,目前Tomcat提供了两种同步策略:
- ReplicatedSession:每次都把整个会话对象同步给集群中的其他节点,其他节点然后更新整个会话对象。这种实现比较简单方便,但会造成大量无效信息的传输。
- DeltaSession:对会话中增量修改的属性进行同步。这种方式由于是增量的,所以会大大降低网络I/O的开销,但是实现上会比较复杂因为涉及到对会话属性操作过程的管理。
ManagerBase:封装了Manager接口通用实现的抽象类,未提供对load()/unload()等方法的实现,需要具体子类去实现。所有的Session管理器都继承自ManagerBase。ClusterManager:在Manager接口的基础上增加了集群部署下的一些接口,所有实现集群下Session管理的管理器都需要实现此接口。PersistentManagerBase:提供了对于Session持久化的基本实现。PersistentManager:继承自PersistentManagerBase,可以在Server.xml的元素下通过配置元素来使用。PersistentManager可以将内存中的Session信息备份到文件或数据库中。当备份一个Session对象时,该Session对象会被复制到存储器(文件或者数据库)中,而原对象仍然留在内存中。因此即便服务器宕机,仍然可以从存储器中获取活动的Session对象。如果活动的Session对象超过了上限值或者Session对象闲置了的时间过长,那么Session会被换出到存储器中以节省内存空间。StandardManager:不用配置元素,当Tomcat正常关闭,重启或Web应用重新加载时,它会将内存中的Session序列化到Tomcat目录下的/work/Catalina/host_name/webapp_name/SESSIONS.ser文件中。当Tomcat重启或应用加载完成后,Tomcat会将文件中的Session重新还原到内存中。如果突然终止该服务器,则所有Session都将丢失,因为StandardManager没有机会实现存盘处理。ClusterManagerBase:提供了对于Session的集群管理实现。DeltaManager:继承自ClusterManagerBase。此Session管理器是Tomcat在集群部署下的默认管理器,当集群中的某一节点生成或修改Session后,DeltaManager将会把这些修改增量复制到其他节点。BackupManager:没有继承ClusterManagerBase,而是直接实现了ClusterManager接口。是Tomcat在集群部署下的可选的Session管理器,集群中的所有Session都被全量复制到一个备份节点。集群中的所有节点都可以访问此备份节点,达到Session在集群下的备份效果。
1.StandardManager初始化
本文以StandardManager为例讲解Session的管理。StandardManager是StandardContext的子组件,用来管理当前Context的所有Session的创建和维护。当StandardContext正式启动,也就是StandardContext的startInternal方法被调用时,StandardContext还会启动StandardManager。StandardContext的startInternal方法中涉及Session管理的执行步骤如下:
- 创建StandardManager;
- 如果Tomcat结合Apache做了分布式部署,会将当前StandardManager注册到集群中;
- 启动StandardManager; StandardManager的start方法用于启动StandardManager
启动StandardManager的步骤如下:
- 调用init方法初始化StandardManager;
-
调用startInternal方法启动StandardManager;
ManagerBase的initInternal方法的执行步骤:
- 将容器自身即StandardManager注册到JMX;
- 从父容器StandardContext中获取当前Tomcat是否是集群部署,并设置为ManagerBase的布尔属性distributable;
- 调用getRandomBytes方法从随机数文件/dev/urandom中获取随机数字节数组,如果不存在此文件则通过反射生成java.security.SecureRandom的实例,用它生成随机数字节数组。 注意:此处调用getRandomBytes方法生成的随机数字节数组并不会被使用,之所以在这里调用实际是为了完成对随机数生成器的初始化,以便将来分配Session ID时使用。
2.StandardManager启动
调用StandardManager的startInternal方法用于启动StandardManager,启动StandardManager的步骤如下:
步骤一 调用generateSessionId方法(见代码清单8)强制初始化随机数生成器;
注意:此处调用generateSessionId方法的目的不是为了生成Session
ID,而是为了强制初始化随机数生成器。
步骤二 加载持久化的Session信息。为什么Session需要持久化?由于在StandardManager中,所有的Session都维护在一个ConcurrentHashMap中,因此服务器重启或者宕机会造成这些Session信息丢失或失效,为了解决这个问题,Tomcat将这些Session通过持久化的方式来保证不会丢失。如果需要安全机制是打开的并且包保护模式打开,会通过创建PrivilegedDoLoad来加载持久化的Session。实际负责加载的方法是doLoad。
StandardManager的doLoad方法的执行步骤如下:
- 清空sessions缓存维护的Session信息;
- 调用file方法返回当前Context下的Session持久化文件,比如:D:\workspace\Tomcat7.0\work\Catalina\localhost\host-manager\SESSIONS.ser;
- 打开Session持久化文件的输入流,并封装为CustomObjectInputStream;
- 从Session持久化文件读入持久化的Session的数量,然后逐个读取Session信息并放入sessions缓存中。
Request的getSession方法用于获取当前请求对应的会话信息,如果没有则创建一个新的Session。getSession调用doGetSession方法,doGetSession方法整个获取Session的步骤如下:
- 判断当前Request对象是否已经存在有效的Session信息,如果存在则返回此Session,否则进入下一步;
- 获取Session管理器,比如StandardManager;
- 从StandardManager的Session缓存中获取Session,如果有则返回此Session,否则进入下一步;
- 创建Session;
- 创建保存Session ID的Cookie;
- 通过调用Session的access方法更新Session的访问时间以及访问次数。我们来着重阅读ManagerBase实现的createSession方法。
Tomcat追踪Session主要借助其ID,因此在接收到请求后应该需要拿到此请求对应的会话ID,这样才能够和StandardManager的缓存中维护的Session相匹配,达到Session追踪的效果。
CoyoteAdapter的service方法时调用的postParseRequest方法,postParseRequest方法的执行步骤如下:
- 如果开启了会话跟踪(session tracking),则需要从缓存中获取维护的Session ID;
- 从请求所带的Cookie中获取Session ID;
- 如果Cookie没有携带Session ID,但是开启了会话跟踪(session tracking),则可以从SSL中获取Session ID;
postParseRequest方法首先调用getSessionUriParamName方法获取Session的参数名称。getSessionUriParamName方法首先调用getConfiguredSessionCookieName方法获取Session的Cookie名称,如果没有则默认为jsessionid(常量DEFAULT_SESSION_PARAMETER_NAME的值)。回头看代码清单1中会以getSessionUriParamName方法返回的值作为request.getPathParameter的参数查询Session
ID。
(2)从请求所带的Cookie中获取Session ID
调用的parseSessionCookiesId方法用来从Cookie中获取Session ID。
(3)从SSL中获取Session ID
调用的parseSessionSslId方法用来从SSL中获取Session
ID。
4.Session销毁
StandardEngine作为容器,其启动过程中也会调用startInternal方法,
1.安装Tomcat8,注意配置环境变量:
CATALINA_BASE:D:\worktools\apache-tomcat-8.5.20
CATALINA_HOME:D:\worktools\apache-tomcat-8.5.20
PATH:%CATALINA_HOME%\lib;%CATALINA_HOME%\bin
2.使用Java visualVM查看,先给visualVM安装MBeans插件,