天天看點

springmvc工作流程_啥是SpringMvc異步處理

生活在這個世界上,我們必須承認任何事物都是運動變化着的,沒有什麼東西是一成不變的。

不僅因為這句話是出自馬克思主義哲學的唯物辯證法,而且事實确實如此。下面就來描述這樣的一個變化。

分工的産生與協作

想必大家都買過房或終将會買房,那自然離不開裝修,就以裝修這個話題來展開吧。假設有一個搞裝修的人叫小王。

小王活兒做得特别好,但目前還是一個人單打獨鬥。他正在幹活兒的這家業主的鄰居發現了他的活兒好,于是專門來找他咨詢情況。

小王不得不停下手頭的工作,來為潛在的客戶解答疑惑或訴說方案。最終靠實力把潛在客戶變成了真正的客戶。小王的名氣就這樣傳開了。

附近小區的人聞名而來,找他溝通裝修方案或确認時間安排,小王不得不每次都停下手頭工作,專心為客戶解答。等客戶走後,他再開始繼續工作。

随着來找他的人與日俱增,漸漸的小王發現他真正用于幹活兒的時間越來越少了,因為他在接待客戶上花費了太多的時間。

但沒有辦法,為了能夠接到更多的活兒,他必須為客戶提供這個咨詢,必須要花掉這些時間。結果導緻幹活兒的時間被壓縮了,活兒幹不完了,該咋辦呢?

其實解決方法很簡單,所有人都能想得到,那就是小王招了一個勞工專門給他幹活。勞工幹完活後先過小王這一關,小王覺得可以了才會傳遞給業主。

小王呢則繼續為客戶提供咨詢服務,在空閑的時候也會去和僅有的一個勞工一起幹活。看來問題已得到解決,一切重新回到平衡狀态中。

由于小王走了狗屎運,每天來的客戶實在是太多了,這個剛剛建立的平衡又被打破了。小王不得不全天候的提供咨詢,索性不再幹活了。

而且由于接的活兒越來越多,無奈不得不又招了第二個勞工,第三個勞工,第四個。。。

我們可以看到很多事情都在逐漸的發生着改變,小王從一個隻會幹活的勞工逐漸過渡到隻負責接待客戶的顧問,不再幹活。

為了适應自己的這個新角色,小王不得不招了一個又一個勞工為自己幹活,這樣自己才能得以從原來的工作中徹底解脫。

可見随着事物的發展,分工的産生是一種必然,這就導緻了崗位種類的增加。原來隻有一個崗位,既負責幹活又負責咨詢。

現在變為了兩個崗位,一個專門負責咨詢和監工,其實就是工長了,一個專門負責幹活和施工,其實就是勞工。

這種“工長 + 勞工”的模式在現實生活中使用的非常普遍,工長為勞工配置設定工作和驗收勞工的工作以及解答勞工的問題,其實就是兩個崗位之間的協作。

是以,分工的産生與協作代表着一種更加先進和高效的生産方式,至少從理論上來看是這樣的,他們各司其職,然後再強強聯合,結果可想而知。

Web容器對異步的支援

看完上一小節,希望大家記住這八個字,“各司其職、互相協作”。OK,馬上進入程式世界。

SpringMvc的實質就是對Java Web的封裝,是以為了更容易的了解SpringMvc的異步,必須先了解Java Web的異步。

Java Web的實質就是一套标準(接口),Web容器實作了這套标準,是以我們站在Web容器的立場來說Java Web的異步,非常好了解。

無論采用什麼架構開發的Java Web應用,最終都是在Web容器中運作的,如tomcat就是最常用的Web容器。

啟動SpringBoot時,可以看看日志中列印出的線程名稱,其實就是tomcat線程池裡的線程。可以多請求幾次,發現每次處理請求的線程Id都不一樣。

我們知道,其實每一個請求過來後,Web容器都會從自己的線程池中拿出一個線程來運作Java Web應用以處理請求。當請求處理完後,這個線程會被還回到線程池中以便處理下一個請求。

如果請求能在非常短的時間内處理完成,是沒有問題的。我們設想一下,如果請求執行的非常慢,那麼這個線程将無法還回去,于是線程池中可用的線程數目将少一個。

由于線程池的容量是有限的,如果很多執行的很慢的請求同時到來,那麼線程池中的線程将會用光,而且短時間内都還不回來。此時的其它請求都将無法被處理。

我們發現執行很快的請求和非常耗時的請求是屬于兩種不同性質的事物,現在它們都由Web容器的線程池的線程來從頭處理到尾,就像當初的小王。

既要接待客戶做咨詢工作,又要等客戶走了自己幹活。這顯然是低效的,唯一的方法就是将這兩種工作分開,分别交由兩個崗位去做。

于是我們就看到,小王招了很多勞工,自己專業做咨詢接活兒,轉手把活兒交給勞工去做,勞工做好後把活兒再交回給小王,小王再向業主傳遞。

我們可以把這個思路套到Web容器上,就是Web容器線程池的線程隻用來處理執行速度很快的請求,這對應于小王隻負責咨詢接活兒。

對于非常耗時的請求,Web容器線程池的線程将把這個請求交給其它專門的線程池的線程去處理,這對應于小王把接到的活兒交給他的勞工去做。

Web容器線程池的線程将再去接受其它請求,因為耗時的請求已經被交出去了。這對應于小王再去接别的活兒,因為上一個活兒已經交給勞工去幹了。

專門的線程池的線程處理完這個請求後會把它還回給Web容器線程池的線程,這對應于勞工幹完活兒後會把活兒先交給小王去驗收。

Web容器線程池的線程拿到處理結果,把結果寫入響應,這個請求就被處理完畢了。這對應于小王把勞工幹好的活兒傳遞給業主,施工就算完畢了。

我們來分析一下,小王由于角色轉變而帶來的工作轉變都有哪些。一開始小王單打獨鬥,小王工作有三種,小王從業主接活兒,小王自己幹活,小王向業主傳遞。

到最後小王成了工長,此時的工作是四種,小王從業主接活兒,小王把活兒交給勞工去做,勞工把做好的活兒交回給小王,小王向業主傳遞。

對比後發現,小王為了使自己不幹活,是以引入了勞工,以及由此産生的他與勞工之間的一去一來的協調互動。

同樣來分析一下,Web容器線程池的線程前前後後有哪些變化。一開始線程處理所有的請求,可以分為三個階段,線程接受請求,線程處理請求,線程寫回響應。

到最後線程的工作分為四個階段,線程接受請求,線程把待處理的請求交給專門的線程池,專門的線程池把處理好的請求交回給線程,線程寫回響應。

對比後發現,Web容器線程池的線程為了使自己不處理耗時請求,是以引入了專門的線程池,以及由此産生的它與專門的線程池的一去一來的協調互動。

是不是發現線程池和小王的行為是完全對應的。而且他們面臨的問題都一樣,就是把需要處理的任務交出去給别人,别人把處理完畢的任務再還回給他。

好了,現在我來告訴你,這就是Java Web異步處理的整體邏輯思想。化繁為簡後,其實就是一去一來這兩個動作罷了。

自己把執行流程交出去

1AsyncContext startAsync()
           

别人把執行流程還回來

1void dispatch()
           

上面這兩個方法就完成了一去一來這兩個動作,僅此而已。

看到這些,可能有些人大失所望,傳說中“牛X和高大上”的異步處理,到最後也不過區區十四個字,“執行流程交出去,執行流程還回來”。

可能還會有人覺得,異步處理不應該和用戶端也有關系嗎?其實并沒有,純粹是伺服器端的把戲。而且對于單個請求的處理時間也不會減少。

就像你去飯店吃飯,你點的這份飯在後廚是一個廚師完成的還是多個廚師協作完成的,其實你并不知道,一般情況下你也并不關心。

SpringMvc對異步的支援

上面描述的異步處理邏輯隻是一個規範(接口),是不帶實作的。任何想要支援Java Web異步處理的,需要在遵守這個規範的前提下,自己提供一套實作。

說白了,就是也要采用交出去還回來的模式,但是如何交出去,怎樣還回來,要自己去實作,還有這個專門的線程池也要自己來提供。

SpringMvc提供了對異步的支援,是以它遵守了這個規範,而且它也定義了相似的接口來與Java Web規範互動。

這個接口就是AsyncWebRequest,它的實作類是StandardServletAsyncWebRequest。同樣的兩個方法。

交出去

1void startAsync()
           

還回來

1void dispatch()
           

在這兩個方法的實作中,分别去調用了Java Web規範中的對應方法,這就是與Java Web規範的互動。

同時SpringMvc自己是有線程池的,是以在第一個方法裡實作了把執行流程交出去的操作,在第二個方法裡實作了把執行流程還回來的操作。

還有一個問題就是,并不是所有的方法都需要異步處理,是以就需要有一種方式來告訴SpringMvc,哪個方法需要異步處理。

SpringMvc選擇了采用方法傳回值的類型來進行區分,凡是@RequestMapping方法的傳回值類型是以下這些的,就表明開發人員想進行異步處理,于是SpringMvc就進行異步處理。

1Callable 2 3DeferredResult 4 5WebAsyncTask 6 7StreamingResponseBody 8 9ResponseEntity1011ResponseBodyEmitter1213ResponseEntity
           

對于異步處理的方式,其實也包括兩大類,一類是開發人員不管,完全交給SpringMvc去處理,一類是開發人員自己把控,不用SpringMvc參與。

對于第一類,我們傳回Callable類型就可以了,這樣SpringMvc會把它送出到線程池裡去執行。

springmvc工作流程_啥是SpringMvc異步處理

對于第二類,我們傳回DeferredResult類型即可,它的意思是延遲結果,就是需要延遲一會才會有結果,但是如何延遲呢,SpringMvc并不會去管。

springmvc工作流程_啥是SpringMvc異步處理

是以是由開發人員來決定的,開發人員需要自己弄個線程去執行,并且一定要記住最後要設定一下結果才行。

我們看到耗時的代碼都跑到别的線程裡去執行了,那麼SpringMvc的處理主流程自然就結束了,這就是執行流程交出去的過程。

隻不過有一點需要注意,這個請求的處理并沒有完成,隻是暫時離開了SpringMvc的主流程,在别的線程池裡運作着呢。

那麼一段時間後,别的線程池運作結束,已經取得了結果,那這個結果和執行流程又該如何還回來呢?

直接還回來嗎?顯然是不可能的,因為SpringMvc的處理主流程在把任務交出去的那一刻就已經結束了、不存在了。

其實是别的線程池在處理結束并得到結果後,處理流程連同結果會傳回到Web容器中,由Web容器再次分派這個請求到SpringMvc中來。

這就是為什麼上面第二個方法的名字叫做dispatch的原因,就是再次分派這個請求到Web應用中來嘛,是以會再次進入到SpringMvc中,這不就把執行流程還回來了嘛。

可能會有人産生疑惑,如果SpringMvc再次處理這個請求那不就亂套了嗎?答案是顯然不會的,因為這次異步的結果已經存在了,自然不會再異步處理了,而是把它跳過去直接進入後續處理。

也就是對方法傳回值(ReturnValue)的處理,比如序列化成JSON寫入響應,或進行視圖渲染,把渲染後的視圖寫入響應,這樣這個請求就算處理完畢了。

作者:程式設計新說李新傑

來源:微信公衆号

繼續閱讀