Servlet3.0对异步处理提供了支持。
每个请求到达Web应用后,Web应用会为其分配一个线程来专门负责该请求,直到响应发送前,该线程都不会被线程池回收。若有些请求需要长时间处理(比如某些耗时运算或者需要等待某个资源),就会阻塞线程,若这类的请求很多,许多线程都将被长时间占用,对于系统就会产生较大负担,甚至会造成程序的效能瓶颈。
基本上一些需要长时间处理的请求,用户通常也不要求请求后就立即响应。如果可以让这类请求先释放分配给该请求的线程,让Web应用有机会将线程资源分配给其它请求,这样就可以减轻系统负担。而原先释放了所分配线程的请求,其响应将被延后,直到任务完成后再对用户发送响应。
Servlet3.0对异步处理提供了支持。
在Servlet3.0中,ServletRequest类提供了几个新方法对异步处理进行支持: - startAsync():利用原来的请求和响应创建一个异步处理上下文; - startAsync(ServletRequest, ServletResponse):利用特定的请求和响应创建一个异步处理上下文; - isAsyncSupported():判断当前请求是否支持异步操作; - isAsyncStarted():判断当前请求是否开始异步处理; - getAsyncContext():返回异步处理上下文,在异步处理开始后才能被调用,否则会抛出异常。
在调用了startAsync()方法取得AsyncContext对象后,这次的响应将被延后,并释放被分配到的线程。
AsyncContext提供了如下方法: - getRequest():返回请求对象; - getResponse():返回响应对象; - setTimeout(long):设置超时时间; - addListener(AsyncListener):注册监听器; - start(Runnable):开始异步处理耗时任务; - complete():异步处理完成,将向用户发送响应; - dispatch(String):将请求转发给另一个Servlet或jsp页面。
在调用了complete()方法后,此次异常处理结束,响应将在此时发送给用户。
这里需要注意的是,AsyncContext不是异步输出,而是同步输出,但是会释放服务器端的线程。使用AsyncContext的时候,对于浏览器来说是在同步等待输出的,但是对于服务器端来说,处理此请求的线程并没有卡在那里等待,则是把当前的耗时任务加入一个线程池中处理,而请求线程将被释放。和自己发起一个新线程去处理耗时任务不同的是,服务器端会创建一个线程池去处理那些需要异步处理的请求,而如果每次请求都发起一个线程去处理的话,这就有可能会消耗大量的线程资源。
下面是一个简单的异步处理程序:
// AsyncServlet.java @WebServlet(asyncSupported = true, urlPatterns = { "/async" }) public class AsyncServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=GBK"); PrintWriter out=response.getWriter(); out.println("<title>async task</title>"); out.println("进入Servlet:"+new Date()+"<br>"); out.flush(); AsyncContext ac=request.startAsync(request, response); ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { event.getAsyncContext().getResponse().getWriter().println("业务完成:"+new Date()); event.getAsyncContext().dispatch("/async"); } @Override public void onError(AsyncEvent event) throws IOException { event.getAsyncContext().getResponse().getWriter().println("业务错误:"+new Date()); } @Override public void onStartAsync(AsyncEvent event) throws IOException { event.getAsyncContext().getResponse().getWriter().println("业务开始:"+new Date()); } @Override public void onTimeout(AsyncEvent event) throws IOException { event.getAsyncContext().getResponse().getWriter().println("业务超时:"+new Date()); } }); ac.setTimeout(30*1000); ac.start(new Task(ac)); out.println("离开Servlet:"+new Date()+"<br>"); out.flush(); } public static class Task implements Runnable { private AsyncContext ac; public Task(AsyncContext a) { ac=a; } @Override public void run() { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } ac.complete(); } } }在这个Servlet中,我们调用startAsync(request, response)方法获得了一个AsyncContext对象,并设置超时时间,注册AsyncListener监听器后开始异步执行任务。此任务阻塞5秒来模拟耗时操作,然后调用complete方法结束此异步处理。
AsyncListener可以对异步处理进行监听: - onComplete:异步处理完成后将回调此方法; - onError:异步处理出错后将回调此方法; - onStartAsync:异步处理开始后将回调此方法; - onTimeout:异步处理超时后将回调此方法。
另外,还需要使用@WebServlet注解的asyncSupported属性对此Servlet进行配置。如果存在Filter作用于此Servlet,则这些Filter也需要设置asyncSupported属性为true。
运行此Web应用,在浏览器地址栏输入:http://localhost:8080/WebDemo/async,大约等待5秒后会出现响应页面:
进入Servlet:Sun Feb 05 17:54:41 CST 2017 离开Servlet:Sun Feb 05 17:54:41 CST 2017 业务完成:Sun Feb 05 17:54:46 CST 2017可以看到,现在这个Servlet不会被耗时任务所阻塞了。
