SpringMVC异步处理的 5 种方式示例详解

网友投稿 306 2023-01-28

SpringMVC异步处理的 5 种方式示例详解

前段时间研究了下 diamond 的原理,其中有个重要的知识点是长连接的实现,用到了 servlet 的异步处理。异步处理最大的好处是可以提高并发量,不阻塞当前线程。其实 Spring MVC 也支持了异步处理,本文记录下相关的技术点。

异步处理 demo

如果要启用异步返回,需要开启 @EnableAsync。如下的代码中,使用 DeferredResult 进行异步处理。

请求进来后,首先创建 DeferredResult 对象,设置超时时间为 60 秒。然后指定DeferredResult 在异步完成和等待超时时的回调。同步的处理只需要创建异步任何,然后返回DeferredResult 即可。这样 Spring MVC 处理完此次请求后,不会立即返回 response 给客户端,会一直等待DeferredResult 处理完成。如果DeferredResult 没有在 60 秒内处理完成,就会触发超时,然后返回 response 给客户端。

@RequestMapping(value = "/async/demo")

public DeferredResult async(){

// 创建 DeferredResult,设置超时时间 6DqfbA0s

DeferredResult deferredResult = new DeferredResult<>((long)60 * 1000);

String uuid = UUID.randomUUID().toString();

Runnable callback = () -> manager.remove(deferredResult, uuid);

// 设置完成和超时的回调

deferredResult.onCompletion(callback);

deferredResult.onTimeout(callback);

// 创建异步任务

manager.addAsyncTask(deferredResult, uuid);

// 同步返回 DeferredResult

return deferredResult;

}

对于异步任务来说,需要持有DeferredResult 对象。在异步处理结束时,需要手动调用DeferredResult.setResult完成输出。调用setResult 时,数据输出写到客户端,然后触发异步完成事件执行回调。

task.getDeferredResult().setResult(ConfigjsonUtils.toJsonString(map));

使用 DeferredResult 进行异步处理

DeferredResult 这个类代表延迟结果。DeferredResult 可以用在异步任务中,其他线程能够获取DeferredResult并设置DeferredResult 的返回数据。通常可以使用线程池、队列等配合DeferredResult 实现异步处理。

根据官方描述,Spring MVC 处理流程如下:

把 controller 返回的 DeferredResult 保存在内存队列或集合当中;

Spring MVC 调用 request.startAsync(),开启异步;

DispatcherServlet 和所有的 Filter 退出当前请求线程;

业务应用在异步线程中设置 DeferredResult 的返回值, Spring MVC 会再次发送请求;

DispatcherServlet 再次被调用,并使用 DeferredResult 的返回值http://;

使用 Callable 进行异步处理

使用 Callable 进行异步处理与 DeferredResult 类似。不同的是,Callable 会交给系统指定的 TaskExecutor 执行。

根据官方描述,Spring MVC 处理流程如下:

controller 返回 Callable ;

Spring MVC 调用 request.startAsync(),开启异步,提交 Callable 到一个任务线程池 ;

DispatcherServlet 和所有的 Filter 退出当前请求线程;

业务应用在异步线程中 返回值, Spring MVC 会再次发送请求;

DispatcherServlet 再次被调用,并使用 Callable 的返回值;

@RequestMapping(value = "/async/demo")

public Callable async(){

Callable callable = () -> String.valueOf(System.currentTimeMillis());

// 同步返回

return callable;

}

使用 ListenableFuture 进行异步处理

ListenableFuture 作为返回值,与DeferredResult 类似。也需要使用者自行处理异步线程,但不支持超时、完成回调,需要自行处理。

@RequestMapping(value = "/async/demo")

public ListenableFuture async(){

ListenableFutureTask ListenableFuture= new ListenableFutureTask<>(() -> {

return String.valueOf(System.currentTimeMillis());

});

Executors.newSingleThreadExecutor().submit(ListenableFuture);

return ListenableFuture;

}

使用 ResponseBodyEmitter 进行异步处理

DeferredResult 和 Callable 都只能返回一个异步值。如果需要返回多个对象,就要使用 ResponseBodyEmitter。返回的每个对象都会被 HttpMessageConverter 处理并写回输出流。如果希望设置更多返回数据,如 header、status 等,可以把 ResponseBodyEmitter 作为 ResponseEntity 的实体数据返回。

@RequestMapping("/async/responseBodyEmitter")

public ResponseBodyEmitter responseBodyEmitter(){

ResponseBodyEmitter responseBodyEmitter=new ResponseBodyEmitter();

Executors.newSingleThreadExecutor().submit(() -> {

try {

responseBodyEmitter.send("demo");

responseBodyEmitter.send("test");

responseBodyEmitter.complete();

} catch (Exception ignore) {}

});

return responseBodyEmitter;

}

使用 StreamingResponseBody 进行异步处理

如果希望跳过返回值的自动转换,直接把输出流写入OutputStream,可以使用 StreamingResponseBody。也可以作为 ResponseEntity 的实体数据返回。

@RequestMapping("/async/streamingResponseBody")

public StreamingResponseBody streamingResponseBody(){

StreamingResponseBody streamingResponseBody = outputStream -> {

Executors.newSingleThreadExecutor().submit(() -> {

try {

outputStream.write("streamingResponseBody".getBytes());

} catch (IOException ignore) {}

});

};

return streamingResponseBody;

}

各种处理方式的对比

可返回次数

数据转换

回调

线程池

DeferredResult

1 次

完成、超时

自行处理

Callable

1 次

系统处理

ListenableFuture

1 次

自行处理

ResponseBodyEmitter

多次

自行处理

StreamingResponseBody

多次

自行处理

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:springcloud结合bytetcc实现数据强一致性原理解析
下一篇:SpringMVC 如何使用注解完成登录拦截
相关文章

 发表评论

暂时没有评论,来抢沙发吧~