前段時間家裏有事忙,停更了好長一段時間,這裏跟等待更新的小夥伴們說一聲抱歉,沒能提早說明一下,讓小夥伴們等了這麼久,真的很差意思!
前面說完了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
加入過濾器鏈中(可能有其它的處理,這裏不直接進行處理),留待下面調用過濾器鏈再統一進行處理。
終於找到具體的實例了,太不容易了!!!
我收集有衆多的 計算機電子書籍,有須要的小夥伴自提哦~