Tomcat 是一个免费的、开源的、轻量级的 Web 应用服务器。适合在并发量不是很高的中小企业项目中使用。学习本章,你将掌握它的目录结构、组件结构、连接器核心原理、容器核心原理、请求处理流程 和 SpringBoot 是如何启动内嵌Tomcat 这六大方面以及文章中出现的高频词汇。提升自己的设计思维,从容面对面试官的刁难问题。
Tomcat 核心原理分析,提升设计思维
目录结构
以下是 Tomcat 8 主要目录结构
目录 |
功能 |
bin |
存放可执行文件,如 startup 和 shutdown |
conf |
存放配置文件,如核心配置文件 server.xml 和应用默认的部署描述文件 web.xml |
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 Request 再转成标准的 ServletRequest 给容器,同时将容器传来的 ServletResponse 转成 Tomcat Response 再转成网络字节流。
连接器模块设计
为满足连接器的两个核心功能,我们需要一个通讯端点来监听端口;需要一个处理器来处理网络字节流;最后还需要一个适配器将处理后的结果转成容器需要的结构。
组件 |
功能 |
Endpoint |
端点,用来处理 Socket 接收和发送的逻辑。其内部由 Acceptor 监听请求、Handler 处理数据、AsyncTimeout 检查请求超时。具体的实现有 NioEndPoint、AprEndpoint 等。 |
Processor |
处理器,负责构建 Tomcat Request 和 Response 对象。具体的实现有 Http11Processor、StreamProcessor 等。 |
Adapter |
适配器,实现 Tomcat Request、Response 与 ServletRequest、ServletResponse之间的相互转换。这采用的是经典的适配器设计模式。 |
ProtocolHandler |
协议处理器,将不同的协议和通讯方式组合封装成对应的协议处理器,如 Http11NioProtocol 封装的是 HTTP + NIO。 |
对应的源码包路径 org.apache.coyote
。对应的结构图如下
容器核心原理
每个 Service 会包含一个容器。容器由一个引擎可以管理多个虚拟主机。每个虚拟主机可以管理多个 Web 应用。每个 Web 应用会有多个 Servlet 包装器。Engine、Host、Context 和 Wrapper,四个容器之间属于父子关系。
容器 |
功能 |
Engine |
引擎,管理多个虚拟主机。 |
Host |
虚拟主机,负责 Web 应用的部署。 |
Context |
Web 应用,包含多个 Servlet 封装器。 |
Wrapper |
封装器,容器的最底层。对 Servlet 进行封装,负责实例的创建、执行和销毁功能。 |
org.apache.coyote
。对应的结构图如下
容器请求处理
容器的请求处理过程就是在 Engine、Host、Context 和 Wrapper 这四个容器之间层层调用,最后在 Servlet 中执行对应的业务逻辑。各容器都会有一个通道 Pipeline,每个通道上都会有一个 Basic Valve(如StandardEngineValve), 类似一个闸门用来处理 Request 和 Response 。其流程图如下。
请求处理流程
上面的知识点已经零零碎碎地介绍了一个 Tomcat 是如何处理一个请求。简单理解就是连接器的处理流程 + 容器的处理流程 = Tomcat 处理流程。哈!那么问题来了,Tomcat 是如何通过请求路径找到对应的虚拟站点?是如何找到对应的 Servlet 呢?
映射器功能介绍
不知道大家有没有回忆起被 Mapper class not found 支配的恐惧。在以前,每写一个完整的功能,都需要在 web.xml 配置映射规则,当文件越来越庞大的时候,各个问题随着也会出现
HTTP请求流程
打开 tomcat/conf 目录下的 server.xml 文件来分析一个http://localhost:8080/docs/api 请求。
第一步:连接器监听的端口是8080。由于请求的端口和监听的端口一致,连接器接受了该请求。
第二步:因为引擎的默认虚拟主机是 localhost,并且虚拟主机的目录是webapps。所以请求找到了 tomcat/webapps 目录。
第三步:解析的 docs 是 web 程序的应用名,也就是 context。此时请求继续从 webapps 目录下找 docs 目录。有的时候我们也会把应用名省略。
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.refreshContext(context);
this.afterRefresh(context, applicationArguments);
从 refreshContext 方法点进去,找 refresh 方法。并一层层往上找其父类的方法。
this.refresh(context);
在 AbstractApplicationContext 类的 refresh 方法中,有一行调用子容器刷新的逻辑。
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
从 onRefresh 方法点进去,找到 ServletWebServerApplicationContext 的实现方法。在这里终于看到了希望。
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException
("Unable to start web server", var2);
}
}
从 createWebServer 方法点进去,找到从工厂类中获取 WebServer的代码。
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
// 获取 web server
this.webServer = factory.getWebServer
(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
// 启动 web server
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException
("Cannot initialize servlet context", var4);
}
}
从 getWebServer 方法点进去,找到 TomcatServletWebServerFactory 的实现方法,与之对应的还有 Jetty 和 Undertow。这里配置了基本的连接器、引擎、虚拟站点等配置。
public WebServer getWebServer
(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ?
this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
源码分析流程图:
服务启动后会打印日志
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8900 (http)
o.apache.catalina.core.StandardService : Starting service [Tomcat]
org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.34
o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal ...
o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 16858 ms
单词知识
以下内容部分来源百度翻译
Container
英 [kənˈteɪnə(r)] 美 [kənˈteɪnər]
n.容器;集装箱;货柜
容器是包装或装载物品的贮存器。容器在编程领域很常见,如 Tomcat 容器、Spring 容器、SpringMVC 容器、Docker 容器等等。其动词 contain 有包含的意思。可以用于字符串是否包含某关键字的逻辑。
Connector
英 [kəˈnektə(r)] 美 [kəˈnektər]
n.连接物;连接器;连线
连接器,其动词 connect 有连接的意思。在 web 应用中几乎无处不在,常见的有数据库、消息通道连接等等。有连接就会断开disconnect,也可用是 connect lose。一般连接丢失后都会有重连机制 reconnect,大部分的框架技术都已经自带重连机制。我们只需要根据业务场景设置重连的日志记录和报警记录等。
Service
英 [ˈsɜːvɪs] 美 [ˈsɜːrvɪs]
n.公共服务系统;公共事业;公共事业机构(或公司);服务性企业(或行业、业务)
v.检修;维护;维修;保养;提供服务;支付(债务)利息
注意区分 server 和 service。server 一般指硬件的服务器,service 一般指软件提供的功能。代码中 server 通常作为服务器配置项,如配置服务器端口 server.port。service 经常作为某模块业务层取名,如 UserService。
Endpoint
英 [ˈɛndˌpɔɪnt] 美 [ˈɛndˌpɔɪnt]
n.终点;端点;光线的端点
Processor
英 [ˈprəʊsesə(r)] 美 [ˈprɑːsesər]
n.加工机(或工人);处理器;处理机
process 加工,处理
Adapter
英 [əˈdæptə] 美 [əˈdæptər]
n.(电器设备的)转接器,适配器;改编者;改写者
适配器模式是将某个类的接口转换成客户端期望的另一个接口,主要目的是兼容性。也称为包装器。
Engine
英 [ˈendʒɪn] 美 [ˈendʒɪn]
n.发动机;引擎;火车头;机车;有…型发动机的;有…个引擎的
vt.给…安装发动机
Host
英 [həʊst] 美 [hoʊst]
n.主人;东道主;主办国(或城市、机构);(电视或广播的)节目主持人
v.主办,主持(活动);主持(电视或广播节目等);作为主人组织(聚会);做东
Context
英 [ˈkɒntekst] 美 [ˈkɑːntekst]
n.(事情发生的)背景,环境,来龙去脉;上下文;语境
Spring 框架中经常会出现 xxxContext 的类。如从 Spring 容器中获取 Bean 对象的方法:
ApplicationContext.getBean(beanName)
Wrapper
英 [ˈræpə(r)] 美 [ˈræpər]
n.包装纸;封皮;封套;(食品等的)包装材料
学英语会编程|ITDragon博客