分析Tomcat的⼯作原理
SpringBoot 就像⼀条巨蟒,慢慢缠绕着我们,使我们⿇痹。不得不承认,使⽤了 SpringBoot 确实提⾼了⼯作效率,但同时也让我们遗忘了很多技能。刚⼊社会的时候,我还是通过 Tomcat ⼿动部署 JavaWeb 项⽬,还经常对 Tomcat 进⾏性能调优。除此之外,还需要⾃⼰理清楚各 Jar 之间的关系,以避免 Jar 丢失和各版本冲突导致服务启动异常的问题。到如今,这些繁琐⽽⼜重复的⼯作已经统统交给 SpringBoot 处理,我们可以把更多的精⼒放在业务逻辑上。但是,清楚 Tomcat 的⼯作原理和处理请求流程和分析 Spring 框架源码⼀样的重要。⾄少⾯试官特别喜欢问这些底层原理和设计思路。希望这篇⽂章能给你⼀些帮助。
Tomcat 整体架构
Tomcat 是⼀个免费的、开源的、轻量级的 Web 应⽤服务器。适合在并发量不是很⾼的中⼩企业项⽬中使⽤。
⽂件⽬录结构
以下是 Tomcat 8 主要⽬录结构
⽬录功能说明
bin存放可执⾏的⽂件,如 startup 和 shutdown
conf存放配置⽂件,如核⼼配置⽂件 l 和应⽤默认的部署描述⽂件 l
lib存放 Tomcat 运⾏需要的jar包
logs存放运⾏的⽇志⽂件
webapps存放默认的 web 应⽤部署⽬录
work存放 web 应⽤代码⽣成和编译⽂件的临时⽬录
功能组件结构
Tomcat 的核⼼功能有两个,分别是负责接收和反馈外部请求的连接器 Connector,和负责处理请求的容器 Container。其中连接器和容器相辅相成,⼀起构成了基本的 web 服务 Service。每个 Tomcat 服务器可以管理多个 Service。
组件功能
Connector负责对外接收反馈请求。它是 Tomcat 与外界的交通枢纽,监听端⼝接收外界请求,并将请
求处理后传递给容器做业务处理,最后将容器处理后的结果反馈给外界。
Container负责对内处理业务逻辑。其内部由Engine、Host、Context 和 Wrapper 四个容器组成,⽤于管理和调⽤ Servlet 相关逻辑。
Service对外提供的 Web 服务。主要包含连接器和容器两个核⼼组件,以及其他功能组件。Tomcat 可以管理多个Service,且各 Service 之间相互独⽴。
Tomcat 连接器核⼼原理
servlet和tomcat的关系Tomcat 连接器框架——Coyote
连接器核⼼功能
⼀、监听⽹络端⼝,接收和响应⽹络请求。
⼆、⽹络字节流处理。将收到的⽹络字节流转换成 Tomcat Request 再转成标准的 ServletRequest 给容器,同时将容器传来的 ServletResponse 转成 Tomcat Response 再转成⽹络字节流。
连接器模块设计
为满⾜连接器的两个核⼼功能,我们需要⼀个通讯端点来监听端⼝;需要⼀个处理器来处理⽹络字节流;最后还需要⼀个适配器将处理后的结果转成容器需要的结构。
组件功能
Endpoint端点,⽤来处理 Socket 接收和发送的逻辑。其内部由 Acceptor 监听请求、Handler 处理数据、AsyncTimeout 检查请求超时。具体的实现有 NioEndPoint、AprEndpoint 等。
Processor处理器,负责构建 Tomcat Request 和 Response 对象。具体的实现有 Http11Processor、
StreamProcessor 等。
适配器,实现 Tomcat Request、Response 与 ServletRequest、ServletResponse之间的相互转换。这采
Adapter⽤的是经典的适配器设计模式。
组件功能
ProtocolHandler协议处理器,将不同的协议和通讯⽅式组合封装成对应的协议处理器,如 Http11NioProtocol 封装的是HTTP + NIO。
对应的源码包路径 。对应的结构图如下
Tomcat 容器核⼼原理
Tomcat 容器框架——Catalina
容器结构分析
每个 Service 会包含⼀个容器。容器由⼀个引擎可以管理多个虚拟主机。每个虚拟主机可以管理多个 Web 应⽤。每个 Web 应⽤会有多个 Servlet 包装器。Engine、Host、Context 和 Wrapper,四个容器之间属于⽗⼦关系。
容器功能
Engine引擎,管理多个虚拟主机。
Host虚拟主机,负责 Web 应⽤的部署。
Context Web 应⽤,包含多个 Servlet 封装器。
Wrapper封装器,容器的最底层。对 Servlet 进⾏封装,负责实例的创建、执⾏和销毁功能。
对应的源码包路径 。对应的结构图如下
容器请求处理
容器的请求处理过程就是在 Engine、Host、Context 和 Wrapper 这四个容器之间层层调⽤,最后在 Servlet 中执⾏对应的业务逻辑。各容器都会有⼀个通道 Pipeline,每个通道上都会有⼀个 Basic Valve(如StandardEngineValve),类似⼀个闸门⽤来处理 Request 和 Response 。其流程图如下。
Tomcat 请求处理流程
上⾯的知识点已经零零碎碎地介绍了⼀个 Tomcat 是如何处理⼀个请求。简单理解就是连接器的处理流程 + 容器的处理流程 = Tomcat 处理流程。哈!那么问题来了,Tomcat 是如何通过请求路径到对应的虚拟站点?是如何到对应的 Servlet 呢?
映射器功能介绍
这⾥需要引⼊⼀个上⾯没有介绍的组件 Mapper。顾名思义,其作⽤是提供请求路径的路由映射。根据请求URL地址匹配是由哪个容器来处理。其中每个容器都会它⾃⼰对应的Mapper,如 MappedHost。不知道⼤家有没有回忆起被 Mapper class not found ⽀配的恐惧。在以前,每写⼀个完整的功能,都需要在 l 配置映射规则,当⽂件越来越庞⼤的时候,各个问题随着也会出现
HTTP请求流程
第⼀步:连接器监听的端⼝是8080。由于请求的端⼝和监听的端⼝⼀致,连接器接受了该请求。
第⼆步:因为引擎的默认虚拟主机是 localhost,并且虚拟主机的⽬录是webapps。所以请求到了 tomcat/webapps ⽬录。第三步:解析的 docs 是 web 程序的应⽤名,也就是 context。此时请求继续从 webapps ⽬录下 docs ⽬录。有的时候我们也会把应⽤名省略。
第四步:解析的 api 是具体的业务逻辑地址。此时需要从 docs/l 中映射关系,最后调⽤具体的函数。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<!-- 连接器监听端⼝是 8080,默认通讯协议是 HTTP/1.1 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- 名字为 Catalina 的引擎,其默认的虚拟主机是 localhost -->
<Engine name="Catalina" defaultHost="localhost">
<!-- 名字为 localhost 的虚拟主机,其⽬录是 webapps-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
SpringBoot 如何启动内嵌的 Tomcat
SpringBoot ⼀键启动服务的功能,让有很多刚⼊社会的朋友都忘记 Tomcat 是啥。随着硬件的性能越来越⾼,普通中⼩项⽬都可以直接⽤内置 Tomcat 启动。但是有些⼤⼀点的项⽬可能会⽤到 Tomcat 集和调优,内置的 Tomcat 就不⼀定能满⾜需求了。
我们先从源码中分析 SpringBoot 是如何启动 Tomcat,以下是 SpringBoot 2.x 的代码。
代码从 main ⽅法开始,执⾏ run ⽅法启动项⽬。
SpringApplication.run
从 run ⽅法点进去,到刷新应⽤上下⽂的⽅法。
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.afterRefresh(context, applicationArguments);
从 refreshContext ⽅法点进去, refresh ⽅法。并⼀层层往上其⽗类的⽅法。
在 AbstractApplicationContext 类的 refresh ⽅法中,有⼀⾏调⽤⼦容器刷新的逻辑。
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
从 onRefresh ⽅法点进去,到 ServletWebServerApplicationContext 的实现⽅法。在这⾥终于看到了希望。
protected void onRefresh() {
try {
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
从 createWebServer ⽅法点进去,到从⼯⼚类中获取 WebServer的代码。
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = WebServerFactory();
// 获取 web server
this.webServer = WebServer(new ServletContextInitializer[]{SelfInitializer()});
} else if (servletContext != null) {
try {
// 启动 web server
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
从 getWebServer ⽅法点进去,到 TomcatServletWebServerFactory 的实现⽅法,与之对应的还有 Jetty 和 Undertow。这⾥配置了基本的连接器、引擎、虚拟站点等配置。
public WebServer initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : ateTempDir("tomcat");
tomcat.AbsolutePath());
Connector connector = new Connector(this.protocol);
this.customizeConnector(connector);
tomcat.setConnector(connector);
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (();
}
this.Host(), initializers);
TomcatWebServer(tomcat);
}
服务启动后会打印⽇志
o.s.at.TomcatWebServer : Tomcat initialized with port(s): 8900 (http)
o.StandardService : Starting service [Tomcat]
org.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.34
o.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal ...
C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
o.t.ContextLoader : Root WebApplicationContext: initialization completed in 16858 ms
END
⽂章到这⾥就结束了,实在是 hold 不住了,周末写了⼀整天还没有写到源码部分,只能放在下⼀章了。再写真的就要废了,有什么不对的地⽅请多多指出
以上就是Tomcat的⼯作原理是怎样的的详细内容,更多关于Tomcat ⼯作原理的资料请关注其它相关⽂章!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论