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

本文深入探讨SpringMVC中的异步请求处理机制,包括如何通过Callable、DeferredResult及WebAsyncTask实现异步调用,提高项目吞吐量。同时,文章详细解析了异步请求的走向原理,以及配置线程池以优化异步执行效率的方法。

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

	@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

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

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

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

}


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

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

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

            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<String>() {
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"业务处理超时");
                return "<h1>Time Out</h1>";
            }
        });

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

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

 

如果不加上会报以下错误(不过在使用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 "<async-supported>true</async-supported>" 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 "<async-supported>true</async-supported>" 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

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

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

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

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

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

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

	/**
	 * Template method for the actual execution of a task.
	 * <p>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方法:

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

说一下MVC异步走向原理:

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

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值