异步执行,顾名思义就是调用后无须等待它的执行,而继续往下执行,对于比较耗时的操作,我们可以抽取成异步方法来让主线程稳定快速继续执行,对于异步方法的执行结果可根据自己的要求是否需要在主线程处理。
servlet3.0和SpringMVC都提供了对异步执行的支持。
servlet同步处理
package com.morris.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@WebServlet("/sync")
public class SyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(Thread.currentThread().getName() + " process start...");
String result = sayHello();
System.out.println(Thread.currentThread().getName() + " process end...");
resp.getWriter().write(result);
}
// 模拟一个超时任务
private String sayHello() {
System.out.println(Thread.currentThread().getName() + " process working...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello sync";
}
}
运行结果如下:
http-nio-8080-exec-1 process start...
http-nio-8080-exec-1 process working...
http-nio-8080-exec-1 process end...
可以发现从头到尾都是同一个线程处理请求。
servlet异步处理
package com.morris.servlet;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
// asyncSupported = true 开启异步支持
@WebServlet(value = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(Thread.currentThread().getName() + " process start...");
AsyncContext asyncContext = req.startAsync();
asyncContext.start(() -> {
String result = sayHello();
//asyncContext.complete();
try {
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println(Thread.currentThread().getName() + " process end...");
}
// 模拟一个超时任务
private String sayHello() {
System.out.println(Thread.currentThread().getName() + " process working...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello async";
}
}
运行结果如下:
http-nio-8080-exec-8 process start...
http-nio-8080-exec-8 process end...
http-nio-8080-exec-4 process working...
可以发现主线程接收到请求后,将请求交给线程池中的其它线程处理后就里面返回了,真正的业务逻辑交给其他线程处理,实现了异步。
如果servlet开启了异步,那么这个请求经过的所有filter都要开启异步,否则会报错。
Spring MVC对异步处理的支持
Callable
Controller中的方法可以直接返回Callable类型。
@RequestMapping("asyncByCallable")
@ResponseBody
public Callable<String> asyncByCallable() {
System.out.println(Thread.currentThread().getName() + " 主线程开始...");
Callable<String> callable = () -> {
System.out.println(Thread.currentThread().getName() + " 副线程开始...");
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + " 副线程结束...");
return "asyncByCallable";
};
System.out.println(Thread.currentThread().getName() + " 主线程结束...");
return callable;
}
运行结果如下:
preHandle/asyncByCallable
http-nio-8080-exec-2 主线程开始...
http-nio-8080-exec-2 主线程结束...
MvcAsync1 副线程开始...
MvcAsync1 副线程结束...
preHandle/asyncByCallable
postHandle
afterCompletion
从运行结果可以发现主线程接收到请求后立即就结束了,由副线程处理具体的业务逻辑,但是为什么拦截器的preHandle()方法执行了两次呢?
DeferredResult
Spring MVC发现返回的是一个DeferredResult对象时,并不会立刻响应浏览器,而是会等到有其他线程调用DeferredResult.setResult()方法或者设置了超时时间后才会响应浏览器。
private final ConcurrentLinkedQueue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<>();
@RequestMapping("asyncByDeferredResult")
@ResponseBody
public DeferredResult<String> asyncByDeferredResult() {
DeferredResult deferredResult = new DeferredResult(3_000L, "time out");
queue.offer(deferredResult);
return deferredResult;
}
上面的方法如果不调用DeferredResult.setResult()
,3s后就会给浏览器返回结果time out
。下面用另一个请求调用DeferredResult.setResult()
@RequestMapping("setDeferredResult")
@ResponseBody
public String setDeferredResult() {
DeferredResult<String> deferredResult = queue.poll();
String s = UUID.randomUUID().toString();
deferredResult.setResult(s);
return s;
}
浏览器调用完请求asyncByDeferredResult
后立马调用请求setDeferredResult
,这时会发现两个请求返回的UUID是一样的。
Spring MVC异步处理的源码分析
Callable返回类型的调用流程分析
org.springframework.web.context.request.async.WebAsyncManager#startCallableProcessing
public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext)
throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = webAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = webAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
else {
// 使用默认的AsyncTaskExecutor
/**
* @see DEFAULT_TASK_EXECUTOR
*/
logExecutorWarning();
}
List<CallableProcessingInterceptor> interceptors = new ArrayList<>();
interceptors.add(webAsyncTask.getInterceptor()); // CallableProcessingInterceptor
interceptors.addAll(this.callableInterceptors.values()); // RequestBindingInterceptor
interceptors.add(timeoutCallableInterceptor); // TimeoutCallableProcessingInterceptor
final Callable<?> callable = webAsyncTask.getCallable();
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(() -> {
if (logger.isDebugEnabled()) {
logger.debug("Async request timeout for " + formatRequestUri());
}
Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (!this.errorHandlingInProgress) {
if (logger.isDebugEnabled()) {
logger.debug("Async request error for " + formatRequestUri() + ": " + ex);
}
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex);
result = (result != CallableProcessingInterceptor.RESULT_NONE ? result : ex);
setConcurrentResultAndDispatch(result);
}
});
this.asyncWebRequest.addCompletionHandler(() ->
interceptorChain.triggerAfterCompletion(this.asyncWebRequest, callable));
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
// 开启异步
// servlet api getRequest().startAsync(getRequest(), getResponse())
startAsyncProcessing(processingContext);
try {
Future<?> future = this.taskExecutor.submit(() -> {
Object result = null;
try {
interceptorChain.applyPreProcess(this.asyncWebRequest, callable);
// 异步执行Callable
result = callable.call();
}
catch (Throwable ex) {
result = ex;
}
finally {
result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, result);
}
// asyncContext.dispatch()转发,又会进入DispatchServlet
setConcurrentResultAndDispatch(result);
});
interceptorChain.setTaskFuture(future);
}
catch (RejectedExecutionException ex) {
Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
setConcurrentResultAndDispatch(result);
throw ex;
}
}
Callable的处理流程大致如下:
- 调用Controller的方法返回一个Callable
- Spring MVC使用servlet3.0开启异步
request.startAsync()
,然后提交了一个Callable给一个独立的TaskExecutor线程池执行 - 同时,DispatcherServlet和所有的拦截器退出Servlet容器线程,但是response仍然打开
- Callable返回结果后,Spring MVC会调用
asyncContext.dispatch()
将请求重新交给Servlet容器完成, - Servlet容器又将请求转发到DispatcherServlet,DispatcherServlet又被调用了一次,所以拦截器的preHandle()方法会被调用两次,此时结果已经产生,直接返回即可。
DeferredResult返回类型的调用流程分析
org.springframework.web.context.request.async.WebAsyncManager#startDeferredResultProcessing
public void startDeferredResultProcessing(
final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<>();
interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
interceptors.add(timeoutDeferredResultInterceptor);
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(() -> {
try {
interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult);
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (!this.errorHandlingInProgress) {
try {
if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
return;
}
deferredResult.setErrorResult(ex);
}
catch (Throwable interceptorEx) {
setConcurrentResultAndDispatch(interceptorEx);
}
}
});
this.asyncWebRequest.addCompletionHandler(()
-> interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
// 开启异步
startAsyncProcessing(processingContext);
try {
interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
deferredResult.setResultHandler(result -> {
result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
// asyncContext.dispatch()转发,又会进入DispatchServlet
setConcurrentResultAndDispatch(result);
});
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
}
DeferredResult前面的流程与Callable的处理流程类似,只不过asyncContext.dispatch()
的调用是在另一个请求中,另一个请求需要拿到DeferredResult对象,并调用setResult()方法,。