大部分程序员不知道的 Servelt3 异步请求,原来这么简单?阿粉带你全面扫盲!(下)

简介: 当一个 HTTP 请求到达 Tomcat,Tomcat 将会从线程池中取出线程,然后按照如下流程处理请求: 将请求信息解析为 HttpServletRequest 分发到具体 Servlet 处理相应的业务 通过 HttpServletResponse 将响应结果返回给等待客户端

SpringMVC

Servlet3  API ,无法使用 SpringMVC 为我们提供的特性,我们需要自己处理响应信息,处理方式相对繁琐。

SpringMVC 3.2 基于 Servelt3 引入异步请求处理方式,我们可以跟使用同步请求一样,方便使用异步请求。

SpringMVC 提供有两种异步方式,只要将 Controller 方法返回值修改下述类即可:

  • DeferredResult
  • Callable

DeferredResult

DeferredResult 是 SpringMVC 3.2 之后引入新的类,只要让请求方法返回 DeferredResult,就可以快速使用异步请求,示例代码如下:

ExecutorService executorService = Executors.newFixedThreadPool(10);
@RequestMapping("/hello_v1")
public DeferredResult<String> hello_v1() {
    // 设置超时时间
    DeferredResult<String> deferredResult = new DeferredResult<>(7000L);
    // 异步线程处理结束,将会执行该回调方法
    deferredResult.onCompletion(() -> {
        log.info("异步线程处理结束");
    });
    // 如果异步线程执行时间超过设置超时时间,将会执行该回调方法
    deferredResult.onTimeout(() -> {
        log.info("异步线程超时");
        // 设置返回结果
        deferredResult.setErrorResult("timeout error");
    });
    deferredResult.onError(throwable -> {
        log.error("异常", throwable);
        // 设置返回结果
        deferredResult.setErrorResult("other error");
    });
    executorService.submit(() -> {
        try {
            TimeUnit.SECONDS.sleep(5);
            deferredResult.setResult("hello_v1");
            // 设置返回结果
        } catch (Exception e) {
            e.printStackTrace();
            // 若异步方法内部异常
            deferredResult.setErrorResult("error");
        }
    });
    log.info("servlet 线程处理结束");
    return deferredResult;
}

创建 DeferredResult 实例时可以传入特定超时时间。另外我们可以设置默认超时时间:

# 异步请求超时时间
spring.mvc.async.request-timeout=2000

如果异步程序执行完成,可以调用  DeferredResult#setResult返回响应结果。此时若有设置 DeferredResult#onCompletion 回调方法,将会触发该回调方法。

同时我们还可以设置超时回调方法 DeferredResult#onTimeout,一旦异步线程执行超时,将会触发该回调方法。

最后 DeferredResult 还提供其他异常的回调方法 onError,起初阿粉以为只要异步线程内发生异常,就会触发该回调方法。尝试在异步线程内抛出异常,但是无法成功触发。

后续阿粉查看这个方法的 doc,当 web 容器线程处理异步请求时发生异常,才能成功触发。

26.jpg

image-20200326195610915

阿粉不知道如何才能发生这个异常,有经验的小伙伴们的可以留言告知下。

Callable

Spring 另外还提供一种异步请求使用方式,直接使用 JDK Callable。示例代码如下:

@RequestMapping("/hello_v2")
public Callable<String> hello_v2() {
    returnnew Callable<String>() {
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(5);
            log.info("异步方法结束");
            return"hello_v2";
        }
    };
}

默认情况下,直接执行将会输出 WARN 日志:

27.jpg

image-20200326213122894

这是因为默认情况使用 SimpleAsyncTaskExecutor 执行异步请求,每次调用执行都将会新建线程。由于这种方式不复用线程,生产不推荐使用这种方式,所以我们需要使用线程池代替。

我们可以使用如下方式自定义线程池:

@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor executor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setThreadNamePrefix("test-");
    threadPoolTaskExecutor.setCorePoolSize(10);
    threadPoolTaskExecutor.setMaxPoolSize(20);
    return threadPoolTaskExecutor;
}

注意 Bean 名称一定要是 applicationTaskExecutor,若不一致, Spring 将不会使用自定义线程池。

或者可以直接使用 SpringBoot 配置文件方式配置代替:

# 核心线程数
spring.task.execution.pool.core-size=10
# 最大线程数
spring.task.execution.pool.max-size=20
# 线程名前缀
spring.task.execution.thread-name-prefix=test
# 还有另外一些配置,读者们可以自行配置

这种方式异步请求的超时时间只能通过配置文件方式配置。

spring.mvc.async.request-timeout=10000

如果需要为单独请求的配置特定的超时时间,我们需要使用 WebAsyncTask 包装 Callable

@RequestMapping("/hello_v3")
public WebAsyncTask<String> hello_v3() {
    System.out.println("asdas");
    Callable<String> callable=new Callable<String>() {
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(5);
            log.info("异步方法结束");
            return"hello_v3";
        }
    };
    // 单位 ms
    WebAsyncTask<String> webAsyncTask=new WebAsyncTask<>(10000,callable);
    return webAsyncTask;
}

总结

SpringMVC 两种异步请求方式,本质上就是帮我们包装 Servlet3  API ,让我们不用关心具体实现细节。虽然日常使用我们一般会选择使用 SpringMVC 两种异步请求方式,但是我们还是需要了解异步请求实际原理。所以大家如果在使用之前,可以先尝试使用 Servlet3 API  练习,后续再使用 SpringMVC。

Reference

  1. https://www.baeldung.com/spring-deferred-result
  2. https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support
相关文章
|
7月前
|
XML 存储 前端开发
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容(三)
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容
|
缓存 Java 数据库
最熟悉的陌生人Spring框架
大家好,我是苍何。最近思考了一个问题,为什么会出现公司面试造火箭,工作扭螺丝的现象,包括各种八股文的连环大绝杀问到你不会为主,其实这是考察你的知识面以及掌握的深度,而为什么需要这样呢?归其原因,无非是通过筛选找到那些会思考的人,他们需要的并不是CRUD的工具人,而是会思考能创新的工程师。
83 0
|
7月前
|
XML JSON 前端开发
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容(二)
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容
|
6月前
|
存储 安全 Java
小白聊《JAVA编程思想》-一切都是对象(一)
小白聊《JAVA编程思想》-一切都是对象(一)
32 0
|
3月前
|
设计模式 网络协议 网络架构
软件工程师,学会封装不香么
软件工程师,学会封装不香么
39 0
|
7月前
|
缓存 前端开发 安全
究竟何为GET,何为POST?前端程序员的必修课
究竟何为GET,何为POST?前端程序员的必修课
93 0
|
7月前
|
XML 前端开发 JavaScript
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容(一)
前端网络请求真的搞懂了吗?解密前端参数传递方式,让开发更从容
|
XML 编解码 Java
我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽! 上
我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽! 上
|
前端开发 Java 应用服务中间件
|
JSON 前端开发 JavaScript

热门文章

最新文章