Tomcat结构与原理
一、组成
Server
tomcat的实例,支持多个Service
Service
web服务,主要包含Connector(多个)和Container(一个)
Connector
连接器(支持多个)作用是协议(如http)通信,负责监听端口来接收消息请求,并传递给Container进行业务处理,再将结果响应会客户端。
过程:网络通信- 应用层协议解析-Request/Response与ServeletRequest/ServeletResponse转化
属性 | 含义 |
---|---|
protocol | 监听的协议,默认是HTTP/1.1(7.0:org.apache.coyote.http11.Http11Protocol,8.0:org.apache.coyote.http11.Http11NioProtocol) |
port | 监听的端口号 |
minThread | 服务器启动时创建的处理请求的线程数 |
maxThread | 最大可以创建的处理请求的线程数 |
enableLookups | 为true表示可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名;否则不进行DNS查询,而是返回其ip地址 |
acceptCount | 当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理 |
connectionTimeout | 超时的时间数(以毫秒为单位) |
redirectPort | 当前Connector不支持SSL,收到了一个SSL传输请求时重定向的端口号 |
SSLEnabled | 是否开启 sll 验证,在Https 访问时需要开启 |
URIEncoding | URL字符编码 |
更多见源码:org.apache.catalina.connector.Connector
ProtocolHandler
协议处理器,将不同协议和通信方式组合封装。本身就是顶层接口。
举例过程:org.apache.coyote.http11.Http11NioProtocol --> org.apache.tomcat.util.net.AbstractEndpoint(start --> startAcceptorThreads) --> org.apache.coyote.AbstractProtocol
Endpoint
端点,负责socket的接收和发送。顶层抽象类是org.apache.tomcat.util.net.AbstractEndpoint。
Acceptor
AbstractEndpoint子类的内部类,继承AbstractEndpoint的抽象内部类Acceptor,具体逻辑实现不同。
作用就是用于监听 Socket 连接请求。
Handler
AbstractEndpoint子类的内部类,继承AbstractEndpoint的内部接口Handler,供具体ProtocolHandler的内部类包装。
作用就是与Processor交互。
SocketProcessor
AbstractEndpoint子类的内部类,实现Runnable,具体逻辑实现不同。
作用就是通过run方法执行handler与Processor交互。
Processor
处理器,负责构建(填充数据)Request和Response对象
Adaptor
适配器,负责Request/Response与ServeletRequest/ServeletResponse转化。
如org.apache.catalina.connector.CoyoteAdapter
Container
一个Service中仅有一个,负责业务处理。包含Engine、Host、Context、Wrapper
Engine
引擎,用于处理连接的执行器。一个Service只能配置一个Engine
属性 | 含义 |
---|---|
name | 名称 |
defaultHost | 默认的Host虚拟机域名,如localhost |
更多见源码:org.apache.catalina.core.StandardEngine
Host
虚拟机,基于域名匹配至指定虚拟机,类似于nginx 当中的server
属性 | 含义 |
---|---|
name | 域名,必须配置,如localhost,至少有一个Host的name与Engine的defaultHost一致 |
appBase | 应用的根路径,支持相对路径(相对于tomcat安装目录根目录),默认webapps |
unpackWARs | 是否自动解压war,默认true |
autoDeploy | 是否热部署war:替换到整个Context环境(即包含session之类),默认true |
更多见源码:org.apache.catalina.core.StandardHost
Context
应用上下文,隔离各个web应用,一个Context对应一个WebappClassLoader。一个Host下支持配置多个Context
属性 | 含义 |
---|---|
docBase | 应用物理路径,支持相对路径(相对于Host的appBase),默认不配置 |
path | 应用上下文路径,默认不配置 |
reloadable | 是否热加载class:替换掉WebappClassLoader。正式环境不用,默认false |
更多见源码:org.apache.catalina.core.StandardContext
Wrapper
Pipeline结构
org.apache.catalina.core.ContainerBase
org.apache.catalina.core.StandardPipeline
源码过程
org.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun
org.apache.coyote.AbstractProtocol.AbstractConnectionHandler#process
org.apache.coyote.http11.AbstractHttp11Processor#process
// this.adapter.service(this.request, this.response);
org.apache.catalina.connector.CoyoteAdapter#service
// this.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
org.apache.catalina.core.StandardEngineValve#invoke
// host.getPipeline().getFirst().invoke(request, response);
org.apache.catalina.core.StandardContextValve#invoke
// wrapper.getPipeline().getFirst().invoke(request, response);
org.apache.catalina.core.StandardWrapperValve#invoke
// filterChain.doFilter(request.getRequest(), response.getResponse());
org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
// this.servlet.service(request, response);
简易流程
EngineValue --> HostValue --> ContextValue --> WrapperValue --> FilterChain --> Servelet
二、运行
org.apache.catalina.startup.Bootstrap#main
1、初始化
org.apache.catalina.startup.Bootstrap#load
org.apache.catalina.startup.Catalina#load
1.1、加载配置初始化
org.apache.tomcat.util.digester.Digester#parse
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanStartElement
com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser#emptyElement
org.apache.tomcat.util.digester.Digester#startElement
调用各种rule
org.apache.tomcat.util.digester.ObjectCreateRule#begin
realClassName
org.apache.catalina.core.StandardServer
org.apache.catalina.core.StandardService
org.apache.catalina.core.StandardEngine
org.apache.catalina.core.StandardHost
org.apache.catalina.startup.ConnectorCreateRule#begin
org.apache.catalina.connector.Connector
# HTTP/1.1。其他协(见org.apache.catalina.connector.Connector#setProtocol)类似
org.apache.coyote.http11.Http11AprProtocol
org.apache.tomcat.util.net.AprEndpoint
org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler(包装Processor)
1.2、init方法初始化
org.apache.catalina.core.StandardServer#initInternal
org.apache.catalina.core.StandardService#initInternal
org.apache.catalina.core.StandardEngine#initInternal
org.apache.catalina.connector.Connector#initInternal
org.apache.catalina.mbeans.MBeanFactory#createStandardContext
创建org.apache.catalina.connector.CoyoteAdapter并设置到org.apache.coyote.ProtocolHandler(如org.apache.coyote.http11.Http11AprProtocol)
|--> org.apache.coyote.AbstractProtocol#init
|--> org.apache.tomcat.util.net.AbstractEndpoint#init
|--> org.apache.tomcat.util.net.AbstractEndpoint#bind(有具体实现类实现,目的就是创建如socket的通信)
2、启动
org.apache.catalina.startup.Bootstrap#start
org.apache.catalina.startup.Catalina#start
org.apache.catalina.core.StandardServer#startInternal
org.apache.catalina.core.StandardService#startInternal
org.apache.catalina.core.StandardEngine#startInternal
org.apache.catalina.connector.Connector#startInternal
|--> org.apache.coyote.AbstractProtocol#start
|--> org.apache.tomcat.util.net.AbstractEndpoint#start
|--> 相应的Endpoint实现类的内部Poller类#init + start
tomcat类加载器
三、热部署
jsp
替换JasperLoader,重新加载类
org.apache.jasper.servlet.JspServletWrapper#service
// this.ctxt.compile();
// 判断是否做修改,修改则重新生成java和class文件,并设置jspLoader加载器为null,reload标志为true
org.apache.jasper.JspCompilationContext#compile
// this.jspLoader = null;
// this.jspCompiler.compile();
// this.jsw.setReload(true);
org.apache.jasper.compiler.Compiler#compile
org.apache.jasper.compiler.JDTCompiler#generateClass
// servlet = this.getServlet();
// 如果reload为true,则销毁旧servelet对象(是否卸载相关类取决于GC能够回收)、生成新的对象以及初始化
org.apache.jasper.servlet.JspServletWrapper#getServlet
// this.destroy();
// servlet = (Servlet)instanceManager.newInstance(this.ctxt.getFQCN(), this.ctxt.getJspLoader());
// servlet.init(this.config);
类
替换org.apache.catalina.loader.WebappClassLoader,重新加载类
热加载class
org.apache.catalina.core.ContainerBase#backgroundProcess
// loader.backgroundProcess();
org.apache.catalina.loader.WebappLoader#backgroundProcess
// this.reloadable && this.modified() 为true就就行reload,其中this.modified() 是遍历了当前应用所有资源是否更改
org.apache.catalina.core.StandardContext#reload
// this.stop();
// this.start();
war
重新初始化Context上下文环境,就是重新启动一个应用(断点过程一直无法执行到)。
代码调试过程,修改war展开后的应用根目录,虽然会被监听到,但因为是目录跳过了后续流程(没有reload、deployed或host存在该应用名称的缓存),相当于不处理(除非删除应用根目录之后再添加) 对war包更新、新增 和 应用移除(war包展开目录)有效
热部署war
org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor#processChildren
org.apache.catalina.core.StandardContext#backgroundProcess
org.apache.catalina.core.ContainerBase#backgroundProcess
org.apache.catalina.util.LifecycleSupport#fireLifecycleEvent
// 通过listener来通知
org.apache.catalina.startup.HostConfig#lifecycleEvent
// this.check();
// 判断资源是否更改,更改则同样执行org.apache.catalina.core.StandardContext#reload
org.apache.catalina.startup.HostConfig#checkResources
// 条件符合情况下,重新初始化context
org.apache.catalina.startup.HostConfig#deployApps()
org.apache.catalina.startup.HostConfig#deployDescriptor
参考 tomcat9调优3:Tomcat类加载机制及其热部署热加载原理剖析
四、其他
请求头数据
org.apache.coyote.http11.Http11InputBuffer.parseHeader方法负责从【流】中解析数据并设置【org.apache.coyote.Request的headers】属性中
1、获取请求头数据,字段名都是小写的,原理见tomcat:中【this.chr >= 65 && this.chr <= 90】这部分代码逻辑。说明:仅对字段名处理成全小写,字段的值不会处理。
2、在【this.headerData.headerValue = this.headers.addValue(this.byteBuffer.array(), this.headerData.start, pos - this.headerData.start);】和【this.headerData.headerValue.setBytes(this.byteBuffer.array(), this.headerData.start, this.headerData.lastSignificantChar - this.headerData.start);】这两处代码实现从流中提取请求头数据到this.headers
如何保持请求头字段名:
a、通用就是自己重写Http11InputBuffer的parseHeader(新建相同包名的Http11InputBuffer类,复制tomcat的Http11InputBuffer类内容,将parseHeader方法中【this.chr >= 65 && this.chr <= 90】这部分代码逻辑注释掉)。
有可能会影响到其他代码(自己项目或其他依赖的)的风险
。
b、手动处理有请求头字段名大小写敏感的业务1、取值:通过 request.getHeader(“字段名”) 直接获取值。说明getHeader方法是不区分字段名大小写的
2、填充:先获取请求头全部数据给一个变量,再将需要保持字段名大小写名称的字段,填充到变量中(通过方式1先获取值再设置)。这个并不会改变请求头原有数据