SpringMVC处理异步请求

た 入场券 2022-06-06 10:43 440阅读 0赞

在上一篇博客中我们讲了一些线程池及异步请求的好处,这一篇我们主要讲SpringMVC如何使用异步请求。

在SpringMVC使用异步响应,主要是指定Controller的@RequestMapping标识的方法的返回值类型,常见有三种类型:Callable,DeferredResult,WebAsyncTask。当SpringMVC检测到返回的对象类型是这三种类型是,会启动异步形式处理。

对于异步请求,也就是需要额外的线程来代理Servlet容器内线程来处理用户请求,也就是说需要额外的线程,可以使用项目内线程池,比如Spring,可以定义一个线程池,专门用户那些项目内的异步处理及异步请求的处理。

对于异步请求,一般是用于处理时间比较长的请求,那么久需要一个时长限制,可以定义一个默认的超时时限,当然每个请求还可以定义自己的超时时限,如果请求未定义,则使用默认时限。

对于异步请求,SpringMVC有一些针对其的Inteceptor,用户可以自定义自己的异步请求拦截器,比如在超时了做些什么,在异步处理结束了做些什么,记录异步请求的处理时间等等。

下面看异步请求的环境配置,以Java形式配置来举例:

  1. @Configuration
  2. @EnableWebMvc
  3. public class MvcContextConfig extends WebMvcConfigurerAdapter {
  4. /** * Spring内部自定义线程池,可以对@Async注解以及Controler返回的Callable,WebAsyncTask和DeferredResult等Spring内异步线程的支持 * * 当一个任务通过execute(Runnable)方法欲添加到线程池时: * * 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 * * 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 * * 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 * * 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。 * * 也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 * * 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。 * * {@linkplain ThreadPoolExecutor#execute(Runnable)} * * @return */
  5. @Bean
  6. public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
  7. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  8. executor.setCorePoolSize(10); // 线程池维护线程的最少数量
  9. executor.setMaxPoolSize(20); // 线程池维护线程的最大数量
  10. executor.setKeepAliveSeconds(300); // 空闲线程的最长保留时间,超过此时间空闲线程会被回收
  11. executor.setQueueCapacity(30); // 线程池所使用的缓冲队列
  12. executor.setThreadNamePrefix("Spring-ThreadPool#");
  13. // rejection-policy:当线程池线程已达到最大值且任务队列也满了的情况下,如何处理新任务
  14. // CALLER_RUNS:这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功
  15. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  16. executor.afterPropertiesSet();
  17. return executor;
  18. }
  19. @Override
  20. public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
  21. configurer.setDefaultTimeout(5 * 1000); //设置默认的超时时间
  22. configurer.setTaskExecutor(threadPoolTaskExecutor); //设置异步请求使用的线程池
  23. //注册异步请求的拦截器
  24. configurer.registerCallableInterceptors(new AsyncCallableInterceptor());
  25. configurer.registerDeferredResultInterceptors(new AsyncDeferredResultInterceptor());
  26. }
  27. }

以上是基于Java形式的配置,如果是要基于配置文件的形式来配置,那么看如下代码:

  1. public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  2. @Bean
  3. public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
  4. ......
  5. AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
  6. configureAsyncSupport(configurer);
  7. if (configurer.getTaskExecutor() != null) {
  8. adapter.setTaskExecutor(configurer.getTaskExecutor());
  9. }
  10. if (configurer.getTimeout() != null) {
  11. adapter.setAsyncRequestTimeout(configurer.getTimeout());
  12. }
  13. adapter.setCallableInterceptors(configurer.getCallableInterceptors());
  14. adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
  15. return adapter;
  16. }
  17. }

实际的那些异步环境配置,最终都配置到了RequestMappingHandlerAdapter 这个Bean中,因此使用配置文件配置是,直接配置RequestMappingHandlerAdapter 这个Bean。

对于异步拦截器来说,Callable和WebAsyncTask共用一种拦截器CallableProcessingInterceptor,因为WebAsyncTask就是对Callable的一个封装,添加了一些额外的方法,比如超时处理等,可以通过继承CallableProcessingInterceptorAdapter适配器重写想要添加逻辑的方法。DeferredResult使用DeferredResultProcessingInterceptor作为拦截器,可以通过继承DeferredResultProcessingInterceptorAdapter适配器来重写想要添加逻辑的 方法。SpringMVC会在处理的相应阶段调用拦截器的相应方法。

下面讲这三种异步处理的使用形式。

对于Callable来说,很简单,直接返回一个Callable对象,将要处理的逻辑放在Callable的run方法内,当SpringMVC检测到返回的是Callable类型的对象时,会使用我们定义的线程池内的线程去执行此Callable,检测逻辑稍后讲。

对于DeferredResult来说,关键点是要在一个线程内调用DeferredResult的setResult(T)方法,也就是说我们需要在Controller方法内就启动线程,而不能返回一个任务由SpringMVC来启动。

WebAsyncTask和Callable类似,是对Callable的一个封装,可以增加处理超时的逻辑以及处理完成后的逻辑等。

下面看使用案例:

  1. import java.util.concurrent.Callable;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import org.springframework.web.context.request.async.DeferredResult;
  8. import org.springframework.web.context.request.async.WebAsyncTask;
  9. /** * @since 2017年6月9日 上午10:23:43 * */
  10. @RestController
  11. @RequestMapping("/async")
  12. public class AsyncController {
  13. private static final String TIME_OUT_RESULT = "TIME_OUT";
  14. private static final String ASYNC_RESULT = "SUCCESS";
  15. final static Logger LOGGER = LoggerFactory.getLogger(AsyncController.class);
  16. @RequestMapping(value = "/callable", method = RequestMethod.GET)
  17. public Callable<String> getCallableMessage() {
  18. LOGGER.debug("请求{}方法,线程id:{}", "getAsyncMessage", Thread.currentThread().getId());
  19. LOGGER.debug("释放线程id:{}", Thread.currentThread().getId());
  20. return () -> {
  21. Thread.sleep(10000);
  22. return "异步信息";
  23. };
  24. }
  25. /** * DeferredResult延迟结果使用案例 * * @return * @throws InterruptedException */
  26. @RequestMapping(value = "/deferred", method = RequestMethod.GET)
  27. public DeferredResult<String> getDeferredResult() throws InterruptedException {
  28. LOGGER.debug("请求{}方法,线程id:{}", "getDeferredResult", Thread.currentThread().getId());
  29. final DeferredResult<String> def = new DeferredResult<String>(6000L, TIME_OUT_RESULT);
  30. def.onTimeout(() -> {
  31. LOGGER.error("请求处理超时");
  32. def.setErrorResult(TIME_OUT_RESULT);
  33. });
  34. def.onCompletion(() -> LOGGER.debug("请求结束"));
  35. new Thread(() -> {
  36. def.setResult(processAsyncResult());
  37. def.setResultHandler((result) -> LOGGER.debug("DeferredResultHandler.handleResult[{}]", def.getResult()));
  38. }).start();
  39. LOGGER.debug("释放线程id:{}", Thread.currentThread().getId());
  40. return def;
  41. }
  42. private String processAsyncResult() {
  43. try {
  44. Thread.sleep(5000);
  45. } catch (InterruptedException e) {
  46. Thread.currentThread().interrupt();
  47. }
  48. return ASYNC_RESULT;
  49. }
  50. /** * WebAsyncTask使用实例 * * @return */
  51. @RequestMapping(value = "/asyncTask", method = RequestMethod.GET)
  52. public WebAsyncTask<String> getAsyncTask() {
  53. WebAsyncTask<String> asyncTask = new WebAsyncTask<>(6000L, () -> processAsyncResult());
  54. asyncTask.onCompletion(() -> LOGGER.debug("异步任务结束"));
  55. asyncTask.onTimeout(() -> {
  56. LOGGER.debug("异步任务结束");
  57. return TIME_OUT_RESULT;
  58. });
  59. return asyncTask;
  60. }
  61. }

SpringMVC监测Controller方法返回的对象类型,如果是Callable,使用CallableMethodReturnValueHandler去处理;如果是DeferredResult,使用DeferredResultMethodReturnValueHandler处理;如果是WebAsyncTask,则使用AsyncTaskMethodReturnValueHandler处理。他们最终都使用WebAsyncManager这个类来处理异步请求,这个类封装了HttpServletRequest,相关内容感兴趣的可以自行查看。

下面看测试代码,测试框架可以使用我的一篇博客中将的Mockmvc,配置代码也在博客上。

  1. public class AsyncControllerTest extends BaseControllerTest {
  2. @Autowired
  3. private AsyncController asyncController;
  4. /* (non-Javadoc) * @see com.bob.test.config.BaseControllerTest#init() */
  5. @Override
  6. protected void init() {
  7. super.mappedController = asyncController;
  8. }
  9. @Test
  10. public void testDeferredResult() throws Exception {
  11. String result = this.getAsyncRequest("/async/deferred", "");
  12. System.out.println(result);
  13. }
  14. }

发表评论

表情:
评论列表 (有 0 条评论,440人围观)

还没有评论,来说两句吧...

相关阅读

    相关 springmvc异步处理请求

    有两种情况,第一种是业务逻辑复杂,但不需要业务逻辑的结果,第二种是需要返回业务逻辑的处理结果 第一种比较简单,利用多线程处理业务逻辑,或者利用spring中@Asyn注