1. Overview
1.概述
In this tutorial, we’ll look at how we can use the DeferredResult class in Spring MVC to perform asynchronous request processing.
在本教程中,我们将探讨如何在Spring MVC中使用DeferredResult类来执行异步请求处理。
Asynchronous support was introduced in Servlet 3.0 and, simply put, it allows processing an HTTP request in another thread than the request receiver thread.
异步支持是在Servlet 3.0中引入的,简单地说,它允许在另一个线程中处理一个HTTP请求,而不是请求接收线程。
DeferredResult, available from Spring 3.2 onwards, assists in offloading a long-running computation from an http-worker thread to a separate thread.
DeferredResult,从Spring 3.2开始可用,有助于将一个长期运行的计算从一个http工作线程卸载到一个单独的线程。
Although the other thread will take some resources for computation, the worker threads are not blocked in the meantime and can handle incoming client requests.
虽然其他线程会占用一些资源进行计算,但工人线程在此期间不会被阻塞,可以处理传入的客户端请求。
The async request processing model is very useful as it helps scale an application well during high loads, especially for IO intensive operations.
异步请求处理模型非常有用,因为它有助于在高负载期间很好地扩展应用程序,特别是对于IO密集型操作。
2. Setup
2.设置
For our examples, we’ll use a Spring Boot application. For more details on how to bootstrap the application, refer to our previous article.
对于我们的例子,我们将使用一个Spring Boot应用程序。关于如何引导应用程序的更多细节,请参考我们之前的开始文章。
Next, we’ll demonstrate both synchronous and asynchronous communication using DeferredResult and also compare how asynchronous one scales better for high load and IO intensive use cases.
接下来,我们将使用DeferredResult演示同步和异步通信,并比较异步通信在高负载和IO密集型用例中的扩展性如何。
3. Blocking REST Service
3.阻止REST服务
Let’s start with developing a standard blocking REST service:
让我们从开发一个标准的阻塞式REST服务开始。
@GetMapping("/process-blocking")
public ResponseEntity<?> handleReqSync(Model model) {
// ...
return ResponseEntity.ok("ok");
}
The problem here is that the request processing thread is blocked until the complete request is processed and the result is returned. In case of long-running computations, this is a sub-optimal solution.
这里的问题是,请求处理线程被阻塞,直到完整的请求被处理并返回结果。如果是长期运行的计算,这是一个次优的解决方案。
To address this, we can make better use of container threads to handle client requests as we’ll see in the next section.
为了解决这个问题,我们可以更好地利用容器线程来处理客户端请求,我们将在下一节看到。
4. Non-Blocking REST Using DeferredResult
4.使用DeferredResult的非阻塞REST
To avoid blocking, we’ll use callbacks-based programming model where instead of the actual result, we’ll return a DeferredResult to the servlet container.
为了避免阻塞,我们将使用基于回调的编程模型,代替实际结果,我们将返回一个DeferredResult给servlet容器。
@GetMapping("/async-deferredresult")
public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
LOG.info("Received async-deferredresult request");
DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet thread freed");
return output;
}
Request processing is done in a separate thread and once completed we invoke the setResult operation on the DeferredResult object.
请求处理是在一个单独的线程中完成的,一旦完成,我们就在DeferredResult对象上调用setResult操作。
Let’s look at the log output to check that our threads behave as expected:
让我们看看日志输出,以检查我们的线程行为是否符合预期。
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
Received async-deferredresult request
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
Servlet thread freed
[nio-8080-exec-6] java.lang.Thread : Processing in separate thread
Internally, the container thread is notified and the HTTP response is delivered to the client. The connection will remain open by the container(servlet 3.0 or later) until the response arrives or times out.
在内部,容器线程被通知,HTTP响应被传递给客户端。容器(servlet 3.0或更高版本)将保持连接开放,直到响应到达或超时。
5. DeferredResult Callbacks
5.DeferredResult Callbacks
We can register 3 types of callbacks with a DeferredResult: completion, timeout and error callbacks.
我们可以用一个DeferredResult注册3种类型的回调:完成、超时和错误回调。
Let’s use the onCompletion() method to define a block of code that’s executed when an async request completes:
让我们使用onCompletion()方法来定义一个代码块,当一个异步请求完成时执行。
deferredResult.onCompletion(() -> LOG.info("Processing complete"));
Similarly, we can use onTimeout() to register custom code to invoke once timeout occurs. In order to limit request processing time, we can pass a timeout value during the DeferredResult object creation:
同样,我们可以使用onTimeout()来注册自定义代码,以便在超时发生时调用。为了限制请求处理时间,我们可以在DeferredResult对象创建期间传递一个超时值。
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(500l);
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout occurred.")));
In case of timeouts, we’re setting a different response status via timeout handler registered with DeferredResult.
在超时的情况下,我们通过与DeferredResult注册的超时处理程序设置一个不同的响应状态。
Let’s trigger a timeout error by processing a request that takes more than the defined timeout values of 5 seconds:
让我们通过处理一个超过定义的超时值5秒的请求来触发一个超时错误。
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
...
}
deferredResult.setResult(ResponseEntity.ok("OK")));
});
Let’s look at the logs:
让我们看一下日志。
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
Request timeout occurred
There will be scenarios where long-running computation fails due to some error or exception. In this case, we can also register an onError() callback:
会有这样的情况:由于一些错误或异常,长期运行的计算会失败。在这种情况下,我们也可以注册一个onError()回调。
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
});
In case of an error, while computing the response, we’re setting a different response status and message body via this error handler.
如果出现错误,在计算响应的同时,我们通过这个错误处理程序设置一个不同的响应状态和消息体。
6. Conclusion
6.结论
In this quick article, we looked at how Spring MVC DeferredResult eases the creation of asynchronous endpoints.
在这篇快速文章中,我们看了Spring MVC DeferredResult如何简化异步端点的创建。
As usual, the complete source code is available over on Github.
像往常一样,完整的源代码可在Github上获得。