javascript
SpringMvc异步请求的使用及部分原理
最近隔壁項目組的項目又出問題了,一直被用戶投訴太卡了,頁面白屏的那種,打開源代碼一看,全是非異步請求,類似于以下寫法:
@ResponseBody@RequestMapping(value = "/getTest")public String getTest() {System.out.println("主線程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());try {Thread.sleep(8000);//模擬業(yè)務(wù)執(zhí)行時間} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主線程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());return "success...";}對于異步請求,用這個的好處呢是可以增大項目吞吐量,一個請求過來,將處理業(yè)務(wù)內(nèi)容交于另外一個線程去執(zhí)行,并且立即釋放主線程,請求少的時候其客戶端并感受不到,當請求多的時候,tomcat線程不夠用時,會有部分用戶客戶端出線等待或白屏狀態(tài),體驗不佳,增加tomcat線程也可以,但是tomcat線程數(shù)和機器性能參數(shù)有關(guān),極限一般是在3000~5000左右不等,而且線程越多,CPU響應(yīng)時間也長,請求線程響應(yīng)時間也會過長,所以,設(shè)置tomcat線程數(shù)最好是找到一個平衡點
官網(wǎng)介紹:https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-async
?從Servlet3.0和SpringMvc3.2以后開始支持異步請求,可以通過使用Callable這個回調(diào)接口實現(xiàn),也可以通過DeferredResult這個對象進行實現(xiàn),下面為具體官方介紹的用法,以下為兩種用法,還有一種是使用WebAsyncTask:
@PostMapping public Callable<String> processUpload(final MultipartFile file) {return new Callable<String>() {public String call() throws Exception {// ...return "someView";}};}@RequestMapping("/quotes") @ResponseBody public DeferredResult<String> quotes() {DeferredResult<String> deferredResult = new DeferredResult<String>();// Save the deferredResult somewhere..return deferredResult; }// In some other thread... deferredResult.setResult(data); @RequestMapping("/getWebAsyncTask")@ResponseBodypublic WebAsyncTask<String> asyncTask(){System.out.println("主線程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());// 1000 為超時設(shè)置,默認執(zhí)行時間為10秒WebAsyncTask<String> webAsyncTask = new WebAsyncTask<String>(2000L,new Callable<String>(){public String call() throws Exception {System.out.println(Thread.currentThread().getName());//業(yè)務(wù)邏輯處理Thread.sleep(3000);System.out.println(Thread.currentThread().getName());return "WebAsyncTask success..";}});webAsyncTask.onCompletion(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+"調(diào)用完成");}});webAsyncTask.onTimeout(new Callable<String>() {public String call() throws Exception {System.out.println(Thread.currentThread().getName()+"業(yè)務(wù)處理超時");return "<h1>Time Out</h1>";}});System.out.println("主線程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());return webAsyncTask;}在異步請求的源碼中的注釋看到,在用異步的請求之前了都需要在web.xml加上的Servlet上面加上<async-supported>true</async-supported>:
?
如果不加上會報以下錯誤(不過在使用SpringBoot項目的時候,這個會Spring被默認設(shè)置成true,所以在SpringBoot項目中無需設(shè)置):
嚴重: Servlet.service() for servlet [Main] in context with path [/TestWebMvc] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml.] with root cause java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml.at org.springframework.util.Assert.state(Assert.java:392)at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.startAsync(StandardServletAsyncWebRequest.java:103)at org.springframework.web.context.request.async.WebAsyncManager.startAsyncProcessing(WebAsyncManager.java:428)at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:308)at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:255)?進入到源碼里面可以看到,報錯的地方是在StandardServletAsyncWebRequest.java:103
其實就是直接的看到getRequest()這個對象的一個屬性而已(request對象地址:org.apache.catalina.connector.Request@79b39c31,說明這個請求是tomcat中的一個對象):
在tomcat源碼中,該對象的屬性默認為false:
在公司的加上那個屬性標簽后,結(jié)果發(fā)現(xiàn)屬性被公司的破平臺jar包吃掉了,這時不慌,可以先設(shè)置一個攔截器或者過濾器,在這個里面加上一個屬性,這樣也可以設(shè)置異步屬性:
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);結(jié)果發(fā)現(xiàn)還是不行,這時逼我改源碼呀!最后將StandardServletAsyncWebRequest.java這個類重寫了一下,在判斷之前設(shè)置一下請求屬性,這樣才好,現(xiàn)在回到異步使用那里,其實在官方還是一種異步的方法,使用WebAsyncTask這個類這個可以增加超時回調(diào)結(jié)果,在調(diào)用中,我們打印一下異步線程名稱:
運行時時候會提示你請配置一個線程池,并且采用的線程為:MvcAsync,這個是SimpleAsyncTaskExecutor線程,但這個并非線程池,打開這個源碼看的時候發(fā)現(xiàn),他就是創(chuàng)建了一個新的Thread用來執(zhí)行異步線程:
/*** Template method for the actual execution of a task.* <p>The default implementation creates a new Thread and starts it.* @param task the Runnable to execute* @see #setThreadFactory* @see #createThread* @see java.lang.Thread#start()*/protected void doExecute(Runnable task) {Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));thread.start();}那我們就先配置一個線程池:創(chuàng)建一個類繼承WebMvcConfigurer接口,實現(xiàn)configureAsyncSupport方法:
現(xiàn)在就可以正常使用異步線程啦!
說一下MVC異步走向原理:
在上面那個執(zhí)行截圖來看,會發(fā)現(xiàn)在執(zhí)行異步是,會多調(diào)用一次攔截器的preHandle方法:
其實就是,請求過來時,經(jīng)過攔截器后發(fā)現(xiàn)該請求為異步,會將tomcat中的Servlet以及Filter退出容器,保持一個response的響應(yīng)連接,當業(yè)務(wù)執(zhí)行完畢后,會自動去請求一次容器,將結(jié)果返回到客戶端上。
而且異步執(zhí)行時,SpringMVC會先調(diào)用自己的前置處理器,在源碼的WebAsyncManager.java類中:
三種前置處理器分別對應(yīng)三種使用方式,其實使用Callable異步運行和使用WebAsyncTask在源碼中是一致的,而且異步調(diào)用的源代碼也是使用Future<?>這個類執(zhí)行的(這里用到了并發(fā)這一塊)保證執(zhí)行的效率:
好了,這就是SpringMVC簡單的異步調(diào)用,以及部分源碼的解讀,有問題請各位社區(qū)大佬指教!
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的SpringMvc异步请求的使用及部分原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习数据驱动_利用深度学习实现手绘数
- 下一篇: Java并发编程之FutureTask源