【springmvc】对异步处理的支持

异步执行,顾名思义就是调用后无须等待它的执行,而继续往下执行,对于比较耗时的操作,我们可以抽取成异步方法来让主线程稳定快速继续执行,对于异步方法的执行结果可根据自己的要求是否需要在主线程处理。

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的处理流程大致如下:

  1. 调用Controller的方法返回一个Callable
  2. Spring MVC使用servlet3.0开启异步request.startAsync(),然后提交了一个Callable给一个独立的TaskExecutor线程池执行
  3. 同时,DispatcherServlet和所有的拦截器退出Servlet容器线程,但是response仍然打开
  4. Callable返回结果后,Spring MVC会调用asyncContext.dispatch()将请求重新交给Servlet容器完成,
  5. 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()方法,。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

morris131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值