分析Tomcat的工作原理
springboot 就像一条巨蟒,慢慢缠绕着我们,使我们麻痹。不得不承认,使用了 springboot 确实提高了工作效率,但同时也让我们遗忘了很多技能。刚入社会的时候,我还是通过 tomcat 手动部署 javaweb 项目,还经常对 tomcat 进行性能调优。除此之外,还需要自己理清楚各 jar 之间的关系,以避免 jar 丢失和各版本冲突导致服务启动异常的问题。到如今,这些繁琐而又重复的工作已经统统交给 springboot 处理,我们可以把更多的精力放在业务逻辑上。但是,清楚 tomcat 的工作原理和处理请求流程和分析 spring 框架源码一样的重要。至少面试官特别喜欢问这些底层原理和设计思路。希望这篇文章能给你一些帮助。
tomcat 整体架构
tomcat 是一个免费的、开源的、轻量级的 web 应用服务器。适合在并发量不是很高的中小企业项目中使用。
文件目录结构
以下是 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 连接器核心原理
tomcat 连接器框架——coyote
连接器核心功能
一、监听网络端口,接收和响应网络请求。
二、网络字节流处理。将收到的网络字节流转换成 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
。对应的结构图如下
tomcat 容器核心原理
tomcat 容器框架——catalina
容器结构分析
每个 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 处理流程。哈!那么问题来了,tomcat 是如何通过请求路径找到对应的虚拟站点?是如何找到对应的 servlet 呢?
映射器功能介绍
这里需要引入一个上面没有介绍的组件 mapper。顾名思义,其作用是提供请求路径的路由映射。根据请求url地址匹配是由哪个容器来处理。其中每个容器都会它自己对应的mapper,如 mappedhost。不知道大家有没有回忆起被 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 目录。有的时候我们也会把应用名省略。
第四步:解析的 api 是具体的业务逻辑地址。此时需要从 docs/web-inf/web.xml 中找映射关系,最后调用具体的函数。
<?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.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
end
文章到这里就结束了,实在是 hold 不住了,周末写了一整天还没有写到源码部分,只能放在下一章了。再写真的就要废了,有什么不对的地方请多多指出
以上就是tomcat的工作原理是怎样的的详细内容,更多关于tomcat 工作原理的资料请关注其它相关文章!