分析Tomcat的工作原理
SpringBoot就像一条巨蟒,慢慢缠绕着我们,使我们麻痹。不得不承认,使用了SpringBoot确实提高了工作效率,但同时也让我们遗忘了很多技能。刚入社会的时候,我还是通过Tomcat手动部署JavaWeb项目,还经常对Tomcat进行性能调优。除此之外,还需要自己理清楚各Jar之间的关系,以避免Jar丢失和各版本冲突导致服务启动异常的问题。到如今,这些繁琐而又重复的工作已经统统交给SpringBoot处理,我们可以把更多的精力放在业务逻辑上。但是,清楚Tomcat的工作原理和处理请求流程和分析Spring框架源码一样的重要。至少面试官特别喜欢问这些底层原理和设计思路。希望这篇文章能给你一些帮助。
Tomcat整体架构
Tomcat是一个免费的、开源的、轻量级的Web应用服务器。适合在并发量不是很高的中小企业项目中使用。
文件目录结构
以下是Tomcat8主要目录结构
目录 | 功能说明 |
---|---|
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
连接器核心功能
一、监听网络端口,接收和响应网络请求。
二、网络字节流处理。将收到的网络字节流转换成TomcatRequest再转成标准的ServletRequest给容器,同时将容器传来的ServletResponse转成TomcatResponse再转成网络字节流。
连接器模块设计
为满足连接器的两个核心功能,我们需要一个通讯端点来监听端口;需要一个处理器来处理网络字节流;最后还需要一个适配器将处理后的结果转成容器需要的结构。
组件 | 功能 |
---|---|
Endpoint | 端点,用来处理Socket接收和发送的逻辑。其内部由Acceptor监听请求、Handler处理数据、AsyncTimeout检查请求超时。具体的实现有NioEndPoint、AprEndpoint等。 |
Processor | 处理器,负责构建TomcatRequest和Response对象。具体的实现有Http11Processor、StreamProcessor等。 |
Adapter | 适配器,实现TomcatRequest、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,每个通道上都会有一个BasicValve(如StandardEngineValve),类似一个闸门用来处理Request和Response。其流程图如下。
Tomcat请求处理流程
上面的知识点已经零零碎碎地介绍了一个Tomcat是如何处理一个请求。简单理解就是连接器的处理流程+容器的处理流程=Tomcat处理流程。哈!那么问题来了,Tomcat是如何通过请求路径找到对应的虚拟站点?是如何找到对应的Servlet呢?
映射器功能介绍
这里需要引入一个上面没有介绍的组件Mapper。顾名思义,其作用是提供请求路径的路由映射。根据请求URL地址匹配是由哪个容器来处理。其中每个容器都会它自己对应的Mapper,如MappedHost。不知道大家有没有回忆起被Mapperclassnotfound支配的恐惧。在以前,每写一个完整的功能,都需要在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中找映射关系,最后调用具体的函数。
SpringBoot如何启动内嵌的Tomcat
SpringBoot一键启动服务的功能,让有很多刚入社会的朋友都忘记Tomcat是啥。随着硬件的性能越来越高,普通中小项目都可以直接用内置Tomcat启动。但是有些大一点的项目可能会用到Tomcat集群和调优,内置的Tomcat就不一定能满足需求了。
我们先从源码中分析SpringBoot是如何启动Tomcat,以下是SpringBoot2.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的实现方法。在这里终于看到了希望。
protectedvoidonRefresh(){ super.onRefresh(); try{ this.createWebServer(); }catch(Throwablevar2){ thrownewApplicationContextException("Unabletostartwebserver",var2); } }
从createWebServer方法点进去,找到从工厂类中获取WebServer的代码。
if(webServer==null&&servletContext==null){ ServletWebServerFactoryfactory=this.getWebServerFactory(); //获取webserver this.webServer=factory.getWebServer(newServletContextInitializer[]{this.getSelfInitializer()}); }elseif(servletContext!=null){ try{ //启动webserver this.getSelfInitializer().onStartup(servletContext); }catch(ServletExceptionvar4){ thrownewApplicationContextException("Cannotinitializeservletcontext",var4); } }
从getWebServer方法点进去,找到TomcatServletWebServerFactory的实现方法,与之对应的还有Jetty和Undertow。这里配置了基本的连接器、引擎、虚拟站点等配置。
publicWebServergetWebServer(ServletContextInitializer...initializers){ Tomcattomcat=newTomcat(); FilebaseDir=this.baseDirectory!=null?this.baseDirectory:this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connectorconnector=newConnector(this.protocol); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iteratorvar5=this.additionalTomcatConnectors.iterator(); while(var5.hasNext()){ ConnectoradditionalConnector=(Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this.prepareContext(tomcat.getHost(),initializers); returnthis.getTomcatWebServer(tomcat); }
服务启动后会打印日志
o.s.b.w.embedded.tomcat.TomcatWebServer:Tomcatinitializedwithport(s):8900(http)
o.apache.catalina.core.StandardService:Startingservice[Tomcat]
org.apache.catalina.core.StandardEngine:StartingServletEngine:ApacheTomcat/8.5.34
o.a.catalina.core.AprLifecycleListener:TheAPRbasedApacheTomcatNativelibrarywhichallowsoptimal...
o.a.c.c.C.[Tomcat].[localhost].[/]:InitializingSpringembeddedWebApplicationContext
o.s.web.context.ContextLoader:RootWebApplicationContext:initializationcompletedin16858ms
END
文章到这里就结束了,实在是hold不住了,周末写了一整天还没有写到源码部分,只能放在下一章了。再写真的就要废了,有什么不对的地方请多多指出
以上就是Tomcat的工作原理是怎样的的详细内容,更多关于Tomcat工作原理的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。