SpringMVC 处理异步请求 - TFdream/blog GitHub Wiki
springmvc 3.2开始就支持servlet3.0的异步请求。平常我们请求一个controller一般都是同步的,如果在代码执行中,遇到耗时的业务操作,那servlet容器线程就会被锁死,当有其他请求进来的时候就会受堵了,有助于提高系统的吞吐量。
springmvc3.2之后支持异步请求,能够在controller中返回一个Callable或者DeferredResult。当返回Callable的时候,大概的执行过程如下:
- 当controller返回值是Callable的时候,springmvc就会启动一个线程将Callable交给TaskExecutor去处理
- 然后DispatcherServlet还有所有的spring拦截器都退出主线程,然后把response保持打开的状态
- 当Callable执行结束之后,springmvc就会重新启动分配一个request请求,然后DispatcherServlet就重新调用和处理Callable异步执行的返回结果,然后返回视图
DeferredResult的执行过程和Callable差不多,唯一不同的时候,DeferredResult是由应用程序其他线程执行返回结果,而Callable是由TaskExecutor执行返回结果。
要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,pom.mxl配置:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
需要在web.xml加上servlet3.x的scheme库,如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>springmvc async Demo</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/applicationContext*.xml;</param-value>
</context-param>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
package springmvc.tutorials.async.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Ricky Fung
*/
@Controller
@RequestMapping("/demo")
public class DemoController {
private final AtomicInteger counter = new AtomicInteger(1);
//业务处理线程池
private final ExecutorService pool = Executors.newFixedThreadPool(5);
@RequestMapping("/deferredResult")
@ResponseBody
public DeferredResult<Object> request(@RequestParam String username) {
int seq = counter.getAndIncrement();
System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());
DeferredResult<Object> deferredResult = new DeferredResult<>(5000L);
//异步处理
dealInOtherThread(deferredResult, seq);
return deferredResult;
}
private void dealInOtherThread(final DeferredResult<Object> deferredResult, final int seq) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
sleep(2000);
System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());
Map<String, String> map = new HashMap<>();
map.put("seq", String.valueOf(seq));
map.put("thread", Thread.currentThread().getName());
deferredResult.setResult(map);
}
});
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package springmvc.tutorials.async.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Ricky Fung
*/
@Controller
@RequestMapping("/foo")
public class FooController {
private final AtomicInteger counter = new AtomicInteger(1);
@RequestMapping("/callable")
@ResponseBody
public Callable<Object> request() {
final int seq = counter.getAndIncrement();
System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());
Callable<Object> callable = new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
sleep(2000);
System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());
Map<String, String> map = new HashMap<>();
map.put("seq", String.valueOf(seq));
map.put("thread", Thread.currentThread().getName());
return map;
}
};
return callable;
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用WebAsyncTask包装Callable,可以自定义客户端超时间,如下:
package springmvc.tutorials.async.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Ricky Fung
*/
@Controller
@RequestMapping("/foo")
public class FooController {
private final AtomicInteger counter = new AtomicInteger(1);
@RequestMapping("/webAsyncTask")
@ResponseBody
public WebAsyncTask<List<String>> req() {
final int seq = counter.getAndIncrement();
System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());
Callable<List<String>> callable = new Callable<List<String>>() {
@Override
public List<String> call() throws Exception {
System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
sleep(2000);
System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());
List<String> list = new ArrayList<>();
list.add(String.valueOf(seq));
list.add(Thread.currentThread().getName());
return list;
}
};
return new WebAsyncTask<>(10000, callable);
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}