Tomcat源碼學習第4篇 - Servlet請求分析
前段時間家里有事忙,停更了好長一段時間,這里跟等待更新的小伙伴們說一聲抱歉,沒能提前說明一下,讓小伙伴們等了這么久,真的不好意思!
前面說完了Tomcat的初始化和啟動步驟,那么接下來就要進入重頭戲了!在本篇文章中,我會跟前面一樣,通過圖文的方式來帶著小伙伴們了解一個 Servlet是如何被tomcat處理的,具體的處理鏈路都有哪些。
一、請求分析
在《Tomcat源碼學習第2篇》中備注了各個組件的說明。
當一個servlet請求到來的時候,首先經過的是connector組件,它是用來接收請求的。
該組件接收到請求之后,會把相關請求進行封裝,然后傳遞到engine組件中。
緊跟著,engine組件會鎖定對應的host,context以及wrapper,一層層的傳遞下去,找到最終處理請求的servlet實例。

二、深入探索
不知道大家還有沒有印象,在前面的文章中,我們在NioEndpoint類中,啟動Accepter線程的入口處上方還有著一個線程組在啟動運行,然而卻沒有講解該線程是用來干嘛的~
NioEndpoint.startInternal()

點擊跳轉到該類過來,我們可以看到他實現了Runnable接口,那么我們直接查看他的run()方法,看看它的運行邏輯。
Poller.run()

通過注釋我們可以知道,該線程主要用于輪詢已連接的套接字,檢查是否觸發(fā)了事件,并在事件發(fā)生時將關聯的套接字移交給對應的處理器。在源碼中我們可以看到keyCount變量記錄著待處理請求數,提供給后面做相應判斷。

繼續(xù)往下走,通過keyCount判斷是否有請求需要進行處理,需要的話則通過selector.selectedKeys()拿到需要被處理的channel集合,進行循環(huán)處理。在while循環(huán)中我們看到,所有就緒的通道都調用的是processKey(sk, socketWrapper)方法進行處理。

點擊跳轉過來該方法,在這里可以看到他對該sk做了讀寫判斷,既然是請求進來,那肯定是做讀操作,我們先進讀相關的方法看一下。
NioEndpoint.processKey()

進來之后我們可以看到它首先在緩存池中嘗試去獲取一個處理線程,當緩存池中沒有線程時,就創(chuàng)建一個新的線程,如果有的話就直接使用。
AbstractEndpoint.processSocket()

既然是線程了,那么我們就關心線程的核心方法即可。點擊SocketProcessorBase跳轉查看run()方法。
SocketProcessorBase.run()

在doRun()處打上斷點,單擊下一步,跳轉到NioEndpoint.doRun()方法中。Poller線程移交到這邊的線程進行處理,在該線程中需要得到當前的socket,做進一步的處理。


進入該方法之后,我們可以看到它首先對wrapper進行判斷,不為空再取出socket,然后嘗試著在connections中去獲取對應的processor,如果獲取不到,再嘗試獲取已經處理過連接,但是尚未銷毀的processor中去獲取,還獲取不到才進行創(chuàng)建。這樣可以避免頻繁的創(chuàng)建和銷毀對象。
AbstractProtocol.process()


得到processor之后,調用process方法對報文進行解析。

進入該方法之后,我們可以看到這里面是對socketEvent的狀態(tài)進行判斷,我們當前請求主要是讀狀態(tài),在此處打上斷點,跳到該方法進來看一下。
AbstractProcessorLight.process()

這里我們可以看到是進入到了 http11類中,在該類里面對報文進行解析,封裝原生的request和response對象。這里的response因為我們還沒有到返回的步驟,所以只是做個初步的參數設置。后續(xù)要傳入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執(zhí)行。
CoyoteAdapter.service()

先得到host,在通過host繼續(xù)調用下一級組件
StandardEngineValve.invoke()

AbstractAccessLogValve.invoke()

ErrorReportValve.invoke()

這里拿到context,繼續(xù)invoke()。
StandardHostValve.invoke()

AuthenticatorBase.invoke()

StandardContextValve.invoke()

拿到wrapper之后,繼續(xù)向下執(zhí)行,從wrapper容器中得到servlet對象。
StandardWrapperValve.invoke()

緊接著,把得到的servlet加入過濾器鏈中(可能有其它的處理,這里不直接進行處理),留待下面調用過濾器鏈再統(tǒng)一進行處理。


ApplicationFilterChain.doFilter()

終于找到具體的實例了,太不容易了?。?!
ApplicationFilterChain.internalDoFilter()

三、總結

- END -我收集有眾多的 計算機電子書籍,有需要的小伙伴自提哦~
