SpringMvc异步请求的使用及部分原理

最近隔壁项目组的项目又出问题了,一直被用户投诉太卡了,页面白屏的那种,打开源代码一看,全是非异步请求,类似于以下写法:

	@ResponseBody
	@RequestMapping(value = "/getTest")
	public String getTest() {
		System.out.println("主线程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());
		try {
			Thread.sleep(8000);//模拟业务执行时间
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());
		return "success...";
	}

对于异步请求,用这个的好处呢是可以增大项目吞吐量,一个请求过来,将处理业务内容交于另外一个线程去执行,并且立即释放主线程,请求少的时候其客户端并感受不到,当请求多的时候,tomcat线程不够用时,会有部分用户客户端出线等待或白屏状态,体验不佳,增加tomcat线程也可以,但是tomcat线程数和机器性能参数有关,极限一般是在3000~5000左右不等,而且线程越多,CPU响应时间也长,请求线程响应时间也会过长,所以,设置tomcat线程数最好是找到一个平衡点

官网介绍:https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-async
SpringMvc异步请求的使用及部分原理_第1张图片

 从Servlet3.0和SpringMvc3.2以后开始支持异步请求,可以通过使用Callable这个回调接口实现,也可以通过DeferredResult这个对象进行实现,下面为具体官方介绍的用法,以下为两种用法,还有一种是使用WebAsyncTask

@PostMapping
public Callable processUpload(final MultipartFile file) {

    return new Callable() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}


@RequestMapping("/quotes")
@ResponseBody
public DeferredResult quotes() {
    DeferredResult deferredResult = new DeferredResult();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);
    @RequestMapping("/getWebAsyncTask")
    @ResponseBody
    public WebAsyncTask asyncTask(){

		System.out.println("主线程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis());
        // 1000 为超时设置,默认执行时间为10秒
        WebAsyncTask webAsyncTask = new WebAsyncTask(2000L,new Callable(){

            public String call() throws Exception {
				System.out.println(Thread.currentThread().getName());
                //业务逻辑处理
                Thread.sleep(3000);
				System.out.println(Thread.currentThread().getName());
                return "WebAsyncTask success..";
            }
        });
        webAsyncTask.onCompletion(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+"调用完成");
            }
        });

        webAsyncTask.onTimeout(new Callable() {
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"业务处理超时");
                return "

Time Out

"; } }); System.out.println("主线程"+Thread.currentThread().getName()+"=>"+System.currentTimeMillis()); return webAsyncTask; }

在异步请求的源码中的注释看到,在用异步的请求之前了都需要在web.xml加上的Servlet上面加上true

SpringMvc异步请求的使用及部分原理_第2张图片

 SpringMvc异步请求的使用及部分原理_第3张图片

如果不加上会报以下错误(不过在使用SpringBoot项目的时候,这个会Spring被默认设置成true,所以在SpringBoot项目中无需设置):

严重: Servlet.service() for servlet [Main] in context with path [/TestWebMvc] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.] with root cause
java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.
	at org.springframework.util.Assert.state(Assert.java:392)
	at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.startAsync(StandardServletAsyncWebRequest.java:103)
	at org.springframework.web.context.request.async.WebAsyncManager.startAsyncProcessing(WebAsyncManager.java:428)
	at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:308)
	at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:255)

 进入到源码里面可以看到,报错的地方是在StandardServletAsyncWebRequest.java:103

SpringMvc异步请求的使用及部分原理_第4张图片

其实就是直接的看到getRequest()这个对象的一个属性而已(request对象地址:org.apache.catalina.connector.Request@79b39c31,说明这个请求是tomcat中的一个对象):

SpringMvc异步请求的使用及部分原理_第5张图片

在tomcat源码中,该对象的属性默认为false:

SpringMvc异步请求的使用及部分原理_第6张图片

在公司的加上那个属性标签后,结果发现属性被公司的破平台jar包吃掉了,这时不慌,可以先设置一个拦截器或者过滤器,在这个里面加上一个属性,这样也可以设置异步属性:

request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

结果发现还是不行,这时逼我改源码呀!最后将StandardServletAsyncWebRequest.java这个类重写了一下,在判断之前设置一下请求属性,这样才好,现在回到异步使用那里,其实在官方还是一种异步的方法,使用WebAsyncTask这个类这个可以增加超时回调结果,在调用中,我们打印一下异步线程名称:
SpringMvc异步请求的使用及部分原理_第7张图片

运行时时候会提示你请配置一个线程池,并且采用的线程为:MvcAsync,这个是SimpleAsyncTaskExecutor线程,但这个并非线程池,打开这个源码看的时候发现,他就是创建了一个新的Thread用来执行异步线程:

	/**
	 * Template method for the actual execution of a task.
	 * 

The default implementation creates a new Thread and starts it. * @param task the Runnable to execute * @see #setThreadFactory * @see #createThread * @see java.lang.Thread#start() */ protected void doExecute(Runnable task) { Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task)); thread.start(); }

那我们就先配置一个线程池:创建一个类继承WebMvcConfigurer接口,实现configureAsyncSupport方法:

SpringMvc异步请求的使用及部分原理_第8张图片

现在就可以正常使用异步线程啦!

说一下MVC异步走向原理:

在上面那个执行截图来看,会发现在执行异步是,会多调用一次拦截器的preHandle方法:

其实就是,请求过来时,经过拦截器后发现该请求为异步,会将tomcat中的Servlet以及Filter退出容器,保持一个response的响应连接,当业务执行完毕后,会自动去请求一次容器,将结果返回到客户端上。

而且异步执行时,SpringMVC会先调用自己的前置处理器,在源码的WebAsyncManager.java类中:

SpringMvc异步请求的使用及部分原理_第9张图片

三种前置处理器分别对应三种使用方式,其实使用Callable异步运行和使用WebAsyncTask在源码中是一致的,而且异步调用的源代码也是使用Future这个类执行的(这里用到了并发这一块)保证执行的效率:

SpringMvc异步请求的使用及部分原理_第10张图片

好了,这就是SpringMVC简单的异步调用,以及部分源码的解读,有问题请各位社区大佬指教!

你可能感兴趣的:(Spring,java基础,java,asynctask,spring)