怎么在Spring-MVC中对Servlet异步进行处理
创始人
2024-12-29 00:42:48
0次

Servlet3.0对异步请求的支持

在Servlet3.0规范中使用异步处理请求非常简单,只需要在请求处理过程中调用request的startAsync返回AsyncContext。

什么是AsyncContext在异步请求中充当着非常重要的角色,可以称为异步请求上下文也可以称为异步请求容器。类似于ServletContext.我们多次调用startAsync都是返回的同一个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 createListener(Class var1) throws ServletException;
  void setTimeout(long var1);
  long getTimeout();
}

getResponse() 用于获取response。dispatch用于分发新地址。complete用于通知容器已经处理完了,start方法用于启动实际处理线程,addListener用于添加监听器;setTimeout方法用于修改超时时间。

Servlet3.0处理异步请求实例

@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 {
  //设置ContentType,关闭缓存
  resp.setContentType("text/plain;charset=UTF-8");
  resp.setHeader("Cache-Control","private");
  resp.setHeader("Pragma","no-cache");
  final PrintWriter writer= resp.getWriter();
  writer.println("老师检查作业了");
  writer.flush();
  List zuoyes=new ArrayList();
  for (int i = 0; i < 10; i++) {
    zuoyes.add("zuoye"+i);;
  }
  final AsyncContext ac=req.startAsync();//开启异步请求
  doZuoye(ac,zuoyes);
  writer.println("老师布置作业");
  writer.flush();
}

private void doZuoye(final AsyncContext ac, final List zuoyes) {
  ac.setTimeout(1*60*60*1000L);
  ac.start(new Runnable() {
    @Override
    public void run() {
      //通过response获得字符输出流
      try {
        PrintWriter writer=ac.getResponse().getWriter();
        for (String zuoye:zuoyes) {
          writer.println("\""+zuoye+"\"请求处理中");
          Thread.sleep(1*1000L);
          writer.flush();
        }
        ac.complete();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  });
}

怎么在Spring-MVC中对Servlet异步进行处理

异步请求监听器

在上面的程序是我们最基本的异步请求,不过不够完善。老师是需要思考宏观问题,所以在写完作业之后需要给老师汇报哪些题难,哪些题目有问题或者自己的这次经验总结,不过这些事不应该由做作业的学生来做,应该由专门的学习汇报员来统计分析。所以就有了监听器。

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())+"工作超时");
  }

  @Override
  public void onError(AsyncEvent event) throws IOException {
    System.out.println("在"+formatter.format(new Date())+"工作处理错误");
  }

  @Override
  public void onStartAsync(AsyncEvent event) throws IOException {
    System.out.println("在"+formatter.format(new Date())+"工作处理开始");
  }
}

所有代码具体参照github地址

https://github.com/lzggsimida123/ServletAsync

补充:SpringMVC对Servlet3异步请求的支持

SpringMVC对Servlet3异步请求的支持有两种方式,分别是通过处理器方法返回Callable和DeferredResult。

按照Servlet3的规范,支持异步请求时需要配置对应的Servlet和Filter支持异步请求,为了使SpringMVC支持异步请求的处理,需要在定义DispatcherServlet时配置其支持异步请求,在DispatcherServlet之前定义的Filter也需要配置支持异步请求。


  springmvc
  org.springframework.web.servlet.DispatcherServlet
  
    contextConfigLocation
    /WEB-INF/applicationContext-mvc.xml
  
  1
  
  true


  springmvc
  /

返回Callable

当处理器的返回方法是Callable类型时会默认发起异步请求,并使用一个TaskExecutor来调用返回的Callable,之后的处理就跟正常的SpringMVC请求是一样的。Callable的返回结果也跟正常请求SpringMVC的一样,可以返回Model、ModelAndView、String、Object等,也可以结合@ResponseBody使用,具体可以参考CallableMethodReturnValueHandler的handleReturnValue()。

  @RequestMapping("/callable")
  public Callable forCallable(Model model) throws Exception {
    return () -> {
      TimeUnit.SECONDS.sleep(1);//睡眠1秒,模仿某些业务操作
      model.addAttribute("a", "aaaaaaa");
      return "async_request_callable";
    };
  }

如果需要针对于单个Callable请求指定超时时间,我们可以把Callable用一个WebAsyncTask包裹起来。然后还可以指定超时回调和正常处理完成的回调。

 @RequestMapping("/callable/timeout")
  public WebAsyncTask forCallableWithTimeout(Model model) throws Exception {
    long timeout = 5 * 1000L;
    WebAsyncTask asyncTask = new WebAsyncTask<>(timeout, () -> {
      TimeUnit.MILLISECONDS.sleep(timeout + 10);
      model.addAttribute("a", "aaaaaaa");
      return "async_request_callable";
    });
    asyncTask.onTimeout(() -> {
      System.out.println("响应超时回调");
      return "async_request_callable_timeout";
    });
    asyncTask.onCompletion(() -> {
      System.out.println("响应callable调用完成的回调");
    });
    return asyncTask;
  }

返回DeferredResult

使用DeferredResult的返回结果的编程通常是在处理器方法中创建一个DeferredResult实例,把它保存起来后再进行返回,比如保存到一个队列中,然后在另外的一个线程中会从这个队列中拿到相应的DeferredResult对象进行相应的业务处理后会往DeferredResult中设置对应的返回值。返回了DeferredResult后SpringMVC将创建一个DeferredResultHandler用于监听DeferredResult,一旦DeferredResult中设置了返回值后,DeferredResultHandler就将对返回值进行处理。DeferredResult的处理过程见DeferredResultMethodReturnValueHandler的handleReturnValue()。

@RequestMapping("/deferredresult")
public DeferredResult forDeferredResult() throws Exception {
  DeferredResult result = new DeferredResult<>();
  new Thread(() -> {
    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    result.setResult("async_request_deferredresult");
  }).start();
  return result;
}

对于DeferredResult也是可以单独指定超时时间和超时后的回调的,它的超时时间可以直接通过构造函数传递,单位是毫秒。

@RequestMapping("/deferredresult/timeout")
public DeferredResult forDeferredResultWithTimeout() throws Exception {
  DeferredResult 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("响应超时回调函数");
  });
  
  result.onCompletion(() -> {
    System.out.println("响应完成的回调函数");
  });
  
  return result;
}

配置

可以通过的子元素来定义处理异步请求默认的超时时间和需要使用的TaskExecutor。如果不指定默认超时时间则默认会使用容器的异步请求超时时间,如果不指定需要使用的TaskExecutor,则默认会使用一个SimpleAsyncTaskExecutor。在下面的配置中我们就配置了默认的超时时间是15秒,且处理异步请求的TaskExecutor是bean容器中名为asyncTaskExecutor的TaskExecutor。


  

拦截器

返回Callable类型的请求可以通过实现CallableProcessingInterceptor接口自定义一个拦截器来拦截,也可以通过继承CallableProcessingInterceptorAdapter抽象类来定义拦截器,这样就只需要选择自己感兴趣的方法进行实现。CallableProcessingInterceptor接口定义如下:

public interface CallableProcessingInterceptor { 
	static final Object RESULT_NONE = new Object(); 
	static final Object RESPONSE_HANDLED = new Object();
 
	/**
	 * Invoked before the start of concurrent handling in the original
	 * thread in which the {@code Callable} is submitted for concurrent handling.
	 *
	 * 

 * 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.  * 

 *  * @param request the current request  * @param task the task for the current async request  * @throws Exception in case of errors  */  void beforeConcurrentHandling(NativeWebRequest request, Callable task) throws Exception;   /**  * Invoked after the start of concurrent handling in the async  * thread in which the {@code Callable} is executed and before 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  */  void preProcess(NativeWebRequest request, Callable task) throws Exception;   /**  * Invoked after 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  */  void postProcess(NativeWebRequest request, Callable 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  */  Object handleTimeout(NativeWebRequest request, Callable 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  */  void afterCompletion(NativeWebRequest request, Callable task) throws Exception;   }

它的配置是通过配置的。


  
    
      
    
  

返回DeferredResult的也可以进行拦截,这需要我们实现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
	 */
	 void beforeConcurrentHandling(NativeWebRequest request, DeferredResult 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}.
	 *
	 * 

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  */  void preProcess(NativeWebRequest request, DeferredResult 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.  *  * 

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  */  void postProcess(NativeWebRequest request, DeferredResult 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  */  boolean handleTimeout(NativeWebRequest request, DeferredResult 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  */  void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) throws Exception;   }

自定义的DeferredResultProcessingInterceptor是通过配置的。


  
    
      
    
  

当发起异步请求时,SpringMVC传统的HandlerInterceptor的postHandle()和afterCompletion()不会执行,但是等异步请求结束后它们还是会执行的。如果需要在异步处理完成之后做一些事情,也可以选择实现AsyncHandlerInterceptor接口的afterConcurrentHandlingStarted(),AsyncHandlerInterceptor接口继承了HandlerInterceptor。

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...