在Servlet3.0以前,Servlet采用Thread-Per-Request的方式处理请求,即一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作(I/O操作非常耗时),比如访问数据库、调用第三方服务接口、网络I/O等,那么处理请求的线程将一直阻塞,直到I/O操作完成才继续处理请求。当并发量很大的时候,这将带来严重的性能问题。如下图所示:
Servlet3.0以后,可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,并含有Request、Response对象的引用。获取AsyncContext对象并从当前Controller返回后,AsyncContext对象将被送到另一个线程(一般是线程池)中取执行,并在心的线程中返回结果给客户端。如图所示:
多说几句:
随着Spring5
发布,提供了一个响应式Web框架:Spring WebFlux
。之后可能就不需要Servlet
容器的支持了。以下是其先后对比图:
左侧是传统的基于Servlet
的Spring Web MVC
框架,右侧是5.0版本新引入的基于Reactive Streams
的Spring WebFlux
框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。
对于其发展前景还是拭目以待吧。有时间也该去了解下Spring5
了。
————————————————————————————————————————————————
说明:
需要注意的是只有在调用request.startAsync
前将监听器添加到AsyncContext
,监听器的onStartAsync
方法才会起作用,而调用startAsync
前AsyncContext
还不存在,所以第一次调用startAsync
是不会被监听器中的onStartAsync
方法捕获的,只有在超时后又重新开始的情况下onStartAsync
方法才会起作用。
一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。
0.控制器
@RestController
@RequestMapping("/servlet")
public class ServletController {
private static Logger logger=Logger.getLogger(ServletController.class);
@RequestMapping("/async")
public void toDoAsync(HttpServletRequest request, HttpServletResponse response){
AsyncContext asyncContext=request.startAsync();
asyncContext.setTimeout(2000);
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
logger.info("完成异步任务");
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
logger.info("超时咯!");
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
logger.info("出错咯!");
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
logger.info("开始咯!");
}
});
logger.info("线程:"+Thread.currentThread().getName());
asyncContext.start(()->{
try {
Thread.sleep(1000);
logger.info("内部线程:"+Thread.currentThread().getName());
asyncContext.getResponse().setCharacterEncoding("utf-8");
asyncContext.getResponse().setContentType("text/html;character=utf-8");
asyncContext.getResponse().getWriter().println("这是【异步】的请求返回");
} catch (Exception e) {
e.printStackTrace();
}
asyncContext.complete();
});
logger.info("线程:"+Thread.currentThread().getName());
}
}
使用过滤器、Servlet时,需要加入asyncSupported
为true
配置,开启异步请求支持。
在spring中有三种方式实现异步请求:DeferredResult、Callable、WebAsyncTask。以后再去了解它们适用的不同场景。
0.创建CallableConfig
主要就是配置线程池和配置超时时间、超时处理等。
@Configuration
public class AsyncConfig implements WebMvcConfigurer {
@Bean("asyncThreadPoolExecutor")
public ThreadPoolTaskExecutor getThreadPool(){
ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(1);
executor.setKeepAliveSeconds(200);
executor.setThreadNamePrefix("callable-");
executor.initialize();
return executor;
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(60*1000);
configurer.setTaskExecutor(getThreadPool());
}
}
1.创建控制器
返回的Callable将交给线程池处理:
@RestController
@RequestMapping("/spring")
public class SpringAsyncController {
private static Logger logger=Logger.getLogger(SpringAsyncController.class);
@GetMapping("/callable")
public Callable callable() {
logger.info("外部线程:"+Thread.currentThread().getName());
return new Callable() {
@Override
public String call() throws Exception {
logger.info("内部线程:"+Thread.currentThread().getName());
return "callable!";
}
};
}
}
2.设置Filter支持异步任务
@WebFilter(asyncSupported = true)
相比于callable
,DeferredResult
可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。
@RestController
@RequestMapping("/spring")
public class SpringAsyncController {
private static Logger logger=Logger.getLogger(SpringAsyncController.class);
public static ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(30);
@GetMapping("/defferredresult")
public DeferredResult deferredResult(){
logger.info("外部线程:"+Thread.currentThread().getName());
DeferredResult result=new DeferredResult(60*1000L);
result.onTimeout(()->{
logger.info("超时啦");
result.setResult("超时咯");
});
result.onCompletion(()->{
logger.info("完成咯");
});
FIXED_THREAD_POOL.execute(()->{
logger.info("内部线程:"+Thread.currentThread().getName());
result.setResult("DeferredResult");
});
return result;
}
}
0.和Callable一样创建Config
1.创建Controller
@RestController
@RequestMapping("/spring")
public class SpringAsyncController {
private static Logger logger=Logger.getLogger(SpringAsyncController.class);
@GetMapping("/websynctask")
public WebAsyncTask webAsyncTask(){
logger.info("外部线程");
WebAsyncTask webAsyncTask=new WebAsyncTask(6 * 1000L, new Callable() {
@Override
public String call() throws Exception {
logger.info("内部线程"+Thread.currentThread().getName());
return "webAsyncTask";
}
});
webAsyncTask.onTimeout(()->{
logger.info("超时啦");
return "WebAsyncTasl超时咯";
});
webAsyncTask.onCompletion(()->{
logger.info("完成咯");
});
return webAsyncTask;
}
}