Tomcat源码学习第4篇 - Servlet请求分析
共 3801字,需浏览 8分钟
·
2021-05-04 22:38
前段时间家里有事忙,停更了好长一段时间,这里跟等待更新的小伙伴们说一声抱歉,没能提前说明一下,让小伙伴们等了这么久,真的不好意思!
前面说完了Tomcat
的初始化和启动步骤,那么接下来就要进入重头戏了!在本篇文章中,我会跟前面一样,通过图文的方式来带着小伙伴们了解一个 Servlet
是如何被tomcat
处理的,具体的处理链路都有哪些。
一、请求分析
在《Tomcat源码学习第2篇》中备注了各个组件的说明。
当一个servlet
请求到来的时候,首先经过的是connector
组件,它是用来接收请求的。
该组件接收到请求之后,会把相关请求进行封装,然后传递到engine
组件中。
紧跟着,engine
组件会锁定对应的host
,context
以及wrapper
,一层层的传递下去,找到最终处理请求的servlet实例。
二、深入探索
不知道大家还有没有印象,在前面的文章中,我们在NioEndpoint
类中,启动Accepter
线程的入口处上方还有着一个线程组在启动运行,然而却没有讲解该线程是用来干嘛的~
NioEndpoint.startInternal()
点击跳转到该类过来,我们可以看到他实现了Runnable
接口,那么我们直接查看他的run()
方法,看看它的运行逻辑。
Poller.run()
通过注释我们可以知道,该线程主要用于轮询已连接的套接字,检查是否触发了事件,并在事件发生时将关联的套接字移交给对应的处理器。在源码中我们可以看到keyCount
变量记录着待处理请求数,提供给后面做相应判断。
继续往下走,通过keyCount
判断是否有请求需要进行处理,需要的话则通过selector.selectedKeys()
拿到需要被处理的channel
集合,进行循环处理。在while
循环中我们看到,所有就绪的通道都调用的是processKey(sk, socketWrapper)
方法进行处理。
点击跳转过来该方法,在这里可以看到他对该sk
做了读写判断,既然是请求进来,那肯定是做读操作,我们先进读相关的方法看一下。
NioEndpoint.processKey()
进来之后我们可以看到它首先在缓存池中尝试去获取一个处理线程,当缓存池中没有线程时,就创建一个新的线程,如果有的话就直接使用。
AbstractEndpoint.processSocket()
既然是线程了,那么我们就关心线程的核心方法即可。点击SocketProcessorBase
跳转查看run()
方法。
SocketProcessorBase.run()
在doRun()
处打上断点,单击下一步,跳转到NioEndpoint.doRun()
方法中。Poller
线程移交到这边的线程进行处理,在该线程中需要得到当前的socket
,做进一步的处理。
进入该方法之后,我们可以看到它首先对wrapper
进行判断,不为空再取出socket
,然后尝试着在connections
中去获取对应的processor
,如果获取不到,再尝试获取已经处理过连接,但是尚未销毁的processor
中去获取,还获取不到才进行创建。这样可以避免频繁的创建和销毁对象。
AbstractProtocol.process()
得到processor
之后,调用process
方法对报文进行解析。
进入该方法之后,我们可以看到这里面是对socketEvent
的状态进行判断,我们当前请求主要是读状态,在此处打上断点,跳到该方法进来看一下。
AbstractProcessorLight.process()
这里我们可以看到是进入到了 http11
类中,在该类里面对报文进行解析,封装原生的request
和response
对象。这里的response
因为我们还没有到返回的步骤,所以只是做个初步的参数设置。后续要传入Adapter
进行下一步操作。
Http11Processor.service()
在这里对原生的request
和response
进行转换,得到HttpServletRequest
和HttpServletResponse
。然后根据请求信息找到能够处理当前请求的host
,context
,wrapper
。
CoyoteAdapter.service()
在这方法可以看到它会通过getMapper()
方法去匹配能够处理当前请求的 host,context,wrapper
。到这里可能有的小伙伴会奇怪,为什么是从mapper
中去匹配呢?这个问题留给你们去探索一下,等下篇再给你们解答。
CoyoteAdapter.postParseRequest()
上一方法中,通过connector
获取service
之后再取得对应的mapper
,可是进来之后却没有看到对该mapper
对象的构建,那该对象是哪里来的呢?
Mapper.map()
不知道大家还记不记得在第二篇中,在StandardService
类中initInternal()
和startInternal()
方法中有mapperListener
方法的初始化和启动。
在该方法中查找到对应的host, context, wrapper
。
Mapper.internalMap()
回到CoyoteAdapter.postParseRequest()
,通过Evaluste
我们可以看到当前请求对应的host, context, wrapper
以及实例的映射均已找到。
接下来要做的就是根据链路组件进行一级级的调用,直至最后取出servlet
执行。
CoyoteAdapter.service()
先得到host
,在通过host
继续调用下一级组件
StandardEngineValve.invoke()
AbstractAccessLogValve.invoke()
ErrorReportValve.invoke()
这里拿到context
,继续invoke()
。
StandardHostValve.invoke()
AuthenticatorBase.invoke()
StandardContextValve.invoke()
拿到wrapper
之后,继续向下执行,从wrapper
容器中得到servlet
对象。
StandardWrapperValve.invoke()
紧接着,把得到的servlet
加入过滤器链中(可能有其它的处理,这里不直接进行处理),留待下面调用过滤器链再统一进行处理。
ApplicationFilterChain.doFilter()
终于找到具体的实例了,太不容易了!!!
ApplicationFilterChain.internalDoFilter()
三、总结
- END -我收集有众多的 计算机电子书籍,有需要的小伙伴自提哦~