Spring-MVC異步請(qǐng)求之Servlet異步處理
在Servlet3.0的規(guī)范中新增了對(duì)異步請(qǐng)求的支持,SpringMVC又在此基礎(chǔ)上對(duì)異步請(qǐng)求提供了方便。
異步請(qǐng)求是在處理比較耗時(shí)的業(yè)務(wù)時(shí)先將request返回,然后另起線程處理耗時(shí)的業(yè)務(wù),處理完后在返回給用戶。
異步請(qǐng)求可以給我們帶來(lái)很多方便,最直接的用法就是處理耗時(shí)的業(yè)務(wù),比如,需要查詢數(shù)據(jù)庫(kù),需要調(diào)用別的服務(wù)器來(lái)處理等情況下可以先將請(qǐng)求返回給客戶端,然后啟用新線程處理耗時(shí)業(yè)務(wù)。
如果我們合適的擴(kuò)展可以實(shí)現(xiàn)訂閱者模式的消息訂閱功能,比如,當(dāng)有異常情況發(fā)生時(shí)可以主動(dòng)將相關(guān)信息發(fā)送給運(yùn)維人員,還有現(xiàn)在的很多郵箱自動(dòng)回復(fù)都是使用這種技術(shù)。
Http協(xié)議是單向的,只能客戶端自己拉不能服務(wù)器主動(dòng)推,Servlet對(duì)異步請(qǐng)求的支持并沒(méi)有修改Http,而是對(duì)Http的巧妙利用。異步請(qǐng)求的核心原理主要分為兩大類(lèi),一類(lèi)是輪詢,另一類(lèi)是長(zhǎng)連接。
輪詢就是定時(shí)自動(dòng)發(fā)起請(qǐng)求檢查有沒(méi)有需要返回的數(shù)據(jù),這種對(duì)資源浪費(fèi)比較大。長(zhǎng)連接的原理是客戶端發(fā)起請(qǐng)求,服務(wù)端處理并返回后并不結(jié)束連接,這樣就可以在后面再次返回給客戶端數(shù)據(jù)。
Servlet對(duì)異步請(qǐng)求的支持其實(shí)采用的是長(zhǎng)連接的方式,也就是說(shuō),異步請(qǐng)求中在原始的請(qǐng)求返回的時(shí)候并沒(méi)有關(guān)閉連接,關(guān)閉的只是處理請(qǐng)求的那個(gè)縣城,只有在異步請(qǐng)求全部處理完之后才會(huì)關(guān)閉連接。
Servlet3.0對(duì)異步請(qǐng)求的支持
在Servlet3.0規(guī)范中使用異步處理請(qǐng)求非常簡(jiǎn)單,只需要在請(qǐng)求處理過(guò)程中調(diào)用request的startAsync返回AsyncContext。
什么是AsyncContext在異步請(qǐng)求中充當(dāng)著非常重要的角色,可以稱(chēng)為異步請(qǐng)求上下文也可以稱(chēng)為異步請(qǐng)求容器。類(lèi)似于ServletContext.我們多次調(diào)用startAsync都是返回的同一個(gè)AsyncContext。代碼如下:
public interface AsyncContext { String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri"; String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path"; String ASYNC_PATH_INFO = "javax.servlet.async.path_info"; String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path"; String ASYNC_QUERY_STRING = "javax.servlet.async.query_string"; ServletRequest getRequest(); ServletResponse getResponse(); boolean hasOriginalRequestAndResponse(); void dispatch(); void dispatch(String var1); void dispatch(ServletContext var1, String var2); void complete(); void start(Runnable var1); void addListener(AsyncListener var1); void addListener(AsyncListener var1, ServletRequest var2, ServletResponse var3); <T extends AsyncListener> T createListener(Class<T> var1) throws ServletException; void setTimeout(long var1); long getTimeout(); }
getResponse() 用于獲取response。dispatch用于分發(fā)新地址。complete用于通知容器已經(jīng)處理完了,start方法用于啟動(dòng)實(shí)際處理線程,addListener用于添加監(jiān)聽(tīng)器;setTimeout方法用于修改超時(shí)時(shí)間。
Servlet3.0處理異步請(qǐng)求實(shí)例
@WebServlet( name = “WorkServlet”, urlPatterns = “/work”, asyncSupported = true ) public class WorkServlet extends HttpServlet { private static final long serialVersionUID =1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //設(shè)置ContentType,關(guān)閉緩存 resp.setContentType("text/plain;charset=UTF-8"); resp.setHeader("Cache-Control","private"); resp.setHeader("Pragma","no-cache"); final PrintWriter writer= resp.getWriter(); writer.println("老師檢查作業(yè)了"); writer.flush(); List<String> zuoyes=new ArrayList<String>(); for (int i = 0; i < 10; i++) { zuoyes.add("zuoye"+i);; } final AsyncContext ac=req.startAsync();//開(kāi)啟異步請(qǐng)求 doZuoye(ac,zuoyes); writer.println("老師布置作業(yè)"); writer.flush(); } private void doZuoye(final AsyncContext ac, final List<String> zuoyes) { ac.setTimeout(1*60*60*1000L); ac.start(new Runnable() { @Override public void run() { //通過(guò)response獲得字符輸出流 try { PrintWriter writer=ac.getResponse().getWriter(); for (String zuoye:zuoyes) { writer.println("\""+zuoye+"\"請(qǐng)求處理中"); Thread.sleep(1*1000L); writer.flush(); } ac.complete(); } catch (Exception e) { e.printStackTrace(); } } }); }
異步請(qǐng)求監(jiān)聽(tīng)器
在上面的程序是我們最基本的異步請(qǐng)求,不過(guò)不夠完善。老師是需要思考宏觀問(wèn)題,所以在寫(xiě)完作業(yè)之后需要給老師匯報(bào)哪些題難,哪些題目有問(wèn)題或者自己的這次經(jīng)驗(yàn)總結(jié),不過(guò)這些事不應(yīng)該由做作業(yè)的學(xué)生來(lái)做,應(yīng)該由專(zhuān)門(mén)的學(xué)習(xí)匯報(bào)員來(lái)統(tǒng)計(jì)分析。所以就有了監(jiān)聽(tīng)器。
public class TeacherListener implements AsyncListener { final SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void onComplete(AsyncEvent event) throws IOException { System.out.println("在"+formatter.format(new Date())+"工作處理完成"); } @Override public void onTimeout(AsyncEvent event) throws IOException { System.out.println("在"+formatter.format(new Date())+"工作超時(shí)"); } @Override public void onError(AsyncEvent event) throws IOException { System.out.println("在"+formatter.format(new Date())+"工作處理錯(cuò)誤"); } @Override public void onStartAsync(AsyncEvent event) throws IOException { System.out.println("在"+formatter.format(new Date())+"工作處理開(kāi)始"); } }
所有代碼具體參照github地址
https://github.com/lzggsimida123/ServletAsync
補(bǔ)充:SpringMVC對(duì)Servlet3異步請(qǐng)求的支持
SpringMVC對(duì)Servlet3異步請(qǐng)求的支持有兩種方式,分別是通過(guò)處理器方法返回Callable和DeferredResult。
按照Servlet3的規(guī)范,支持異步請(qǐng)求時(shí)需要配置對(duì)應(yīng)的Servlet和Filter支持異步請(qǐng)求,為了使SpringMVC支持異步請(qǐng)求的處理,需要在定義DispatcherServlet時(shí)配置其支持異步請(qǐng)求,在DispatcherServlet之前定義的Filter也需要配置支持異步請(qǐng)求。
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- 啟用異步支持 --> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
返回Callable
當(dāng)處理器的返回方法是Callable類(lèi)型時(shí)會(huì)默認(rèn)發(fā)起異步請(qǐng)求,并使用一個(gè)TaskExecutor來(lái)調(diào)用返回的Callable,之后的處理就跟正常的SpringMVC請(qǐng)求是一樣的。Callable的返回結(jié)果也跟正常請(qǐng)求SpringMVC的一樣,可以返回Model、ModelAndView、String、Object等,也可以結(jié)合@ResponseBody使用,具體可以參考CallableMethodReturnValueHandler的handleReturnValue()。
@RequestMapping("/callable") public Callable<String> forCallable(Model model) throws Exception { return () -> { TimeUnit.SECONDS.sleep(1);//睡眠1秒,模仿某些業(yè)務(wù)操作 model.addAttribute("a", "aaaaaaa"); return "async_request_callable"; }; }
如果需要針對(duì)于單個(gè)Callable請(qǐng)求指定超時(shí)時(shí)間,我們可以把Callable用一個(gè)WebAsyncTask包裹起來(lái)。然后還可以指定超時(shí)回調(diào)和正常處理完成的回調(diào)。
@RequestMapping("/callable/timeout") public WebAsyncTask<String> forCallableWithTimeout(Model model) throws Exception { long timeout = 5 * 1000L; WebAsyncTask<String> asyncTask = new WebAsyncTask<>(timeout, () -> { TimeUnit.MILLISECONDS.sleep(timeout + 10); model.addAttribute("a", "aaaaaaa"); return "async_request_callable"; }); asyncTask.onTimeout(() -> { System.out.println("響應(yīng)超時(shí)回調(diào)"); return "async_request_callable_timeout"; }); asyncTask.onCompletion(() -> { System.out.println("響應(yīng)callable調(diào)用完成的回調(diào)"); }); return asyncTask; }
返回DeferredResult
使用DeferredResult的返回結(jié)果的編程通常是在處理器方法中創(chuàng)建一個(gè)DeferredResult實(shí)例,把它保存起來(lái)后再進(jìn)行返回,比如保存到一個(gè)隊(duì)列中,然后在另外的一個(gè)線程中會(huì)從這個(gè)隊(duì)列中拿到相應(yīng)的DeferredResult對(duì)象進(jìn)行相應(yīng)的業(yè)務(wù)處理后會(huì)往DeferredResult中設(shè)置對(duì)應(yīng)的返回值。返回了DeferredResult后SpringMVC將創(chuàng)建一個(gè)DeferredResultHandler用于監(jiān)聽(tīng)DeferredResult,一旦DeferredResult中設(shè)置了返回值后,DeferredResultHandler就將對(duì)返回值進(jìn)行處理。DeferredResult的處理過(guò)程見(jiàn)DeferredResultMethodReturnValueHandler的handleReturnValue()。
@RequestMapping("/deferredresult") public DeferredResult<String> forDeferredResult() throws Exception { DeferredResult<String> result = new DeferredResult<>(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } result.setResult("async_request_deferredresult"); }).start(); return result; }
對(duì)于DeferredResult也是可以單獨(dú)指定超時(shí)時(shí)間和超時(shí)后的回調(diào)的,它的超時(shí)時(shí)間可以直接通過(guò)構(gòu)造函數(shù)傳遞,單位是毫秒。
@RequestMapping("/deferredresult/timeout") public DeferredResult<String> forDeferredResultWithTimeout() throws Exception { DeferredResult<String> result = new DeferredResult<>(10 * 1000); new Thread(() -> { try { TimeUnit.SECONDS.sleep(31); } catch (InterruptedException e) { e.printStackTrace(); } result.setResult("async_request_deferredresult"); }).start(); result.onTimeout(() -> { System.out.println("響應(yīng)超時(shí)回調(diào)函數(shù)"); }); result.onCompletion(() -> { System.out.println("響應(yīng)完成的回調(diào)函數(shù)"); }); return result; }
配置
可以通過(guò)<mvc:annotation-driven/>的子元素<mvc:async-support/>來(lái)定義處理異步請(qǐng)求默認(rèn)的超時(shí)時(shí)間和需要使用的TaskExecutor。如果不指定默認(rèn)超時(shí)時(shí)間則默認(rèn)會(huì)使用容器的異步請(qǐng)求超時(shí)時(shí)間,如果不指定需要使用的TaskExecutor,則默認(rèn)會(huì)使用一個(gè)SimpleAsyncTaskExecutor。在下面的配置中我們就配置了默認(rèn)的超時(shí)時(shí)間是15秒,且處理異步請(qǐng)求的TaskExecutor是bean容器中名為asyncTaskExecutor的TaskExecutor。
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"/> </mvc:annotation-driven>
攔截器
返回Callable類(lèi)型的請(qǐng)求可以通過(guò)實(shí)現(xiàn)CallableProcessingInterceptor接口自定義一個(gè)攔截器來(lái)攔截,也可以通過(guò)繼承CallableProcessingInterceptorAdapter抽象類(lèi)來(lái)定義攔截器,這樣就只需要選擇自己感興趣的方法進(jìn)行實(shí)現(xiàn)。CallableProcessingInterceptor接口定義如下:
public interface CallableProcessingInterceptor { static final Object RESULT_NONE = new Object(); static final Object RESPONSE_HANDLED = new Object(); /** * Invoked <em>before</em> the start of concurrent handling in the original * thread in which the {@code Callable} is submitted for concurrent handling. * * <p> * This is useful for capturing the state of the current thread just prior to * invoking the {@link Callable}. Once the state is captured, it can then be * transferred to the new {@link Thread} in * {@link #preProcess(NativeWebRequest, Callable)}. Capturing the state of * Spring Security's SecurityContextHolder and migrating it to the new Thread * is a concrete example of where this is useful. * </p> * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked <em>after</em> the start of concurrent handling in the async * thread in which the {@code Callable} is executed and <em>before</em> the * actual invocation of the {@code Callable}. * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked <em>after</em> the {@code Callable} has produced a result in the * async thread in which the {@code Callable} is executed. This method may * be invoked later than {@code afterTimeout} or {@code afterCompletion} * depending on when the {@code Callable} finishes processing. * * @param request the current request * @param task the task for the current async request * @param concurrentResult the result of concurrent processing, which could * be a {@link Throwable} if the {@code Callable} raised an exception * @throws Exception in case of errors */ <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception; /** * Invoked from a container thread when the async request times out before * the {@code Callable} task completes. Implementations may return a value, * including an {@link Exception}, to use instead of the value the * {@link Callable} did not return in time. * * @param request the current request * @param task the task for the current async request * @return a concurrent result value; if the value is anything other than * {@link #RESULT_NONE} or {@link #RESPONSE_HANDLED}, concurrent processing * is resumed and subsequent interceptors are not invoked * @throws Exception in case of errors */ <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked from a container thread when async processing completes for any * reason including timeout or network error. * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception; }
它的配置是通過(guò)<mvc:callable-interceptors/>配置的。
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"> <mvc:callable-interceptors> <bean class="YourCallableProcessingInterceptor"/> </mvc:callable-interceptors> </mvc:async-support> </mvc:annotation-driven>
返回DeferredResult的也可以進(jìn)行攔截,這需要我們實(shí)現(xiàn)DeferredResultProcessingInterceptor接口或者繼承自DeferredResultProcessingInterceptorAdapter。DeferredResultProcessingInterceptor接口定義如下:
public interface DeferredResultProcessingInterceptor { /** * Invoked immediately before the start of concurrent handling, in the same * thread that started it. This method may be used to capture state just prior * to the start of concurrent processing with the given {@code DeferredResult}. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked immediately after the start of concurrent handling, in the same * thread that started it. This method may be used to detect the start of * concurrent processing with the given {@code DeferredResult}. * * <p>The {@code DeferredResult} may have already been set, for example at * the time of its creation or by another thread. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked after a {@code DeferredResult} has been set, via * {@link DeferredResult#setResult(Object)} or * {@link DeferredResult#setErrorResult(Object)}, and is also ready to * handle the concurrent result. * * <p>This method may also be invoked after a timeout when the * {@code DeferredResult} was created with a constructor accepting a default * timeout result. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @param concurrentResult the result to which the {@code DeferredResult} * @throws Exception in case of errors */ <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception; /** * Invoked from a container thread when an async request times out before * the {@code DeferredResult} has been set. Implementations may invoke * {@link DeferredResult#setResult(Object) setResult} or * {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing. * * @param request the current request * @param deferredResult the DeferredResult for the current request; if the * {@code DeferredResult} is set, then concurrent processing is resumed and * subsequent interceptors are not invoked * @return {@code true} if processing should continue, or {@code false} if * other interceptors should not be invoked * @throws Exception in case of errors */ <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked from a container thread when an async request completed for any * reason including timeout and network error. This method is useful for * detecting that a {@code DeferredResult} instance is no longer usable. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; }
自定義的DeferredResultProcessingInterceptor是通過(guò)<mvc:deferred-result-interceptors>配置的。
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"> <mvc:deferred-result-interceptors> <bean class="YourDeferredResultProcessingInterceptor"/> </mvc:deferred-result-interceptors> </mvc:async-support> </mvc:annotation-driven>
當(dāng)發(fā)起異步請(qǐng)求時(shí),SpringMVC傳統(tǒng)的HandlerInterceptor的postHandle()和afterCompletion()不會(huì)執(zhí)行,但是等異步請(qǐng)求結(jié)束后它們還是會(huì)執(zhí)行的。如果需要在異步處理完成之后做一些事情,也可以選擇實(shí)現(xiàn)AsyncHandlerInterceptor接口的afterConcurrentHandlingStarted(),AsyncHandlerInterceptor接口繼承了HandlerInterceptor。
(注:本文是基于Spring4.1.0所寫(xiě))
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
一文總結(jié)RabbitMQ中的消息確認(rèn)機(jī)制
RabbitMQ消息確認(rèn)機(jī)制指的是在消息傳遞過(guò)程中,發(fā)送方發(fā)送消息后,接收方需要對(duì)消息進(jìn)行確認(rèn),以確保消息被正確地接收和處理,本文為大家整理了RabbitMQ中的消息確認(rèn)機(jī)制,需要的可以參考一下2023-06-06使用mybatis框架連接mysql數(shù)據(jù)庫(kù)的超詳細(xì)步驟
MyBatis是目前java項(xiàng)目連接數(shù)據(jù)庫(kù)的最流行的orm框架了,下面這篇文章主要給大家介紹了關(guān)于使用mybatis框架連接mysql數(shù)據(jù)庫(kù)的超詳細(xì)步驟,文中通過(guò)實(shí)例代碼和圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04Spring學(xué)習(xí)筆記2之表單數(shù)據(jù)驗(yàn)證、文件上傳實(shí)例代碼
這篇文章主要介紹了Spring學(xué)習(xí)筆記2之表單數(shù)據(jù)驗(yàn)證、文件上傳 的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07java實(shí)現(xiàn)支付寶支付接口的調(diào)用
本文主要介紹了java實(shí)現(xiàn)支付寶支付接口的調(diào)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動(dòng)生成19位長(zhǎng)度主鍵id的坑
這篇文章主要介紹了Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動(dòng)生成19位長(zhǎng)度主鍵id的坑,本文一步步給大家分享解決方法,給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12MyBatis入門(mén)之增刪改查+數(shù)據(jù)庫(kù)字段和實(shí)體字段不一致問(wèn)題處理方法
這篇文章主要介紹了MyBatis入門(mén)之增刪改查+數(shù)據(jù)庫(kù)字段和實(shí)體字段不一致問(wèn)題處理方法,需要的朋友可以參考下2017-05-05多模塊maven的deploy集成gitlab?ci自動(dòng)發(fā)版配置
這篇文章主要為大家介紹了多模塊maven項(xiàng)目deploy集成gitlab?ci自動(dòng)發(fā)版的配置流程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02