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