Servlet 3引入一项新的特性,可以让Servlet异步处理请求,本文来介绍这新技术
一.概述
WebServlet 和 WebFilter 注解类型可以包含 asyncSupport属性。为了编写能够支持异步的Servlet和Filter ,该
属性必须为true
支持异步处理的Servlet或者Filter 可以通过在ServletRequest 中调用startAsync 方法来启动新的线程。
startAsync 有两个重载方法:
AsyncContext startAsync ()
AsyncContext startAsync(ServletRequest request ,ServletResponse response)
注意:重复调用startAsync方法将会启动同一个线程,将会抛出一个java.lang.lllegalStateException 异常
调用 AsyncContext的start方法不会造成阻塞,因此它派发的线程还没启动,也会继续执行下一行代码
二.编写异步的Servlet
如果你有一个任务需要相对比较长的时间才能完成,最好成绩一个Servlet或者Filter 。在异步的Servlet或者Filter类中
需要完成以下工作:
1.在ServletRequest 中调用startAsync 方法来启动新的线程
2.在AsyncContext中调用setTimeout()方法,设置一个任务必须在指定的时间内完成的毫秒数
3.调用asyncContext.start方法,传递一个执行长时间任务的Runnable
4.任务完成时,通过Runnable调用asyncContext.complete方法或者asyncContext.dispatch方法
举个例子
import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name="AsyncDispatchServlet", urlPatterns="/asyncDispatch", asyncSupported=true) public class AsyncDispatchServlet extends HttpServlet { private static final long serialVersionUID=222L; @Override protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub final AsyncContext asyncContext=req.startAsync(); req.setAttribute("mainThread", Thread.currentThread().getName()); asyncContext.setTimeout(5000); asyncContext.start(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try{ Thread.sleep(4000); }catch(InterruptedException e){ } req.setAttribute("workerThread", Thread.currentThread().getName()); asyncContext.dispatch("/threadNames.jsp"); } }); } }上述代码在任务里休眠3秒。将主线程和工作线程发送到test.jsp页面,显示他们的线程名称
<body> Main thread:${mainThread} <br> Worker Thread:${workerThread} </body>
三.发送进程更新的异步
这个Servlet每秒钟发送一次进程更新,以便用户能够追踪进程
mport java.io.IOException; import java.io.PrintWriter; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(asyncSupported=true,urlPatterns="/asyncComplete") public class AsyncCompleteServlet extends HttpServlet { private static final long serialVersionUID=78234L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub resp.setContentType("text/html"); final PrintWriter write=resp.getWriter(); write.println("<html><head><title>" +"Async Servlet</title></head>"); write.println("<body><div id='progress'></div>"); final AsyncContext asyncContext=req.startAsync(); asyncContext.setTimeout(60000); asyncContext.start(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("new Thread:"+Thread.currentThread()); for(int i=0;i<10;i++){ write.println("<script>"); write.print("document.getElementById('progress').innerHTML='" +(i*10)+"% complete'"); write.println("</script>"); write.flush(); try{ Thread.sleep(1000); }catch(InterruptedException e){} } write.println("<script>"); write.println("document.getElementById('progress').innerHTML='DONE'"); write.println("</script>"); write.println("</body></html>"); asyncContext.complete(); } }); } }
四.同步监听器
除了Servlet和Filter执行异步操作之外,Servlet 3.0还新增了一个AsyncListener接口,以便通知用户在异步处理期间
发生的情况。
AsyncListener接口定义的方法:
void onStartAsync(AsyncEvent event) 在刚启动一个异步操作时调用
void onComplete(AsyncEvent event) 当一个异步操作已经完成时调用
void onError(AsyncEvent event) 当一个异步操作失败是调用
void onTimeout(AsyncEvent event) 当一个异步操作已经超时的时候调用
这四个方法都会受到一个AsyncEvent事件,可以通过调用它的getAsyncContext,getSuppliedRequest .getSuppliedResponse
方法从中获取相关的AsyncContext,ServletRequest,ServletResponse实例
举个例子:MyAsyncListener类实现了AsyncListener接口,以便当异步操作中有事件发生时能够受到通知,
与其他监听器不同,它没有用@WebListener标注AsyncListener的实现
import java.io.IOException; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; //do not annotate with @WebListener public class MyAsyncListener implements AsyncListener { @Override public void onComplete(AsyncEvent arg0) throws IOException { // TODO Auto-generated method stub System.out.println("onComplete"); } @Override public void onError(AsyncEvent arg0) throws IOException { // TODO Auto-generated method stub System.out.println("onError"); } @Override public void onStartAsync(AsyncEvent arg0) throws IOException { // TODO Auto-generated method stub System.out.println("onStartAsync"); } @Override public void onTimeout(AsyncEvent arg0) throws IOException { // TODO Auto-generated method stub System.out.println("onTimeout"); } }
import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name="AsyncListenerServlet",urlPatterns="/asyncListener", asyncSupported=true) public class AsyncListenerServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub final AsyncContext asyncContext=req.startAsync(); asyncContext.setTimeout(5000); asyncContext.addListener(new MyAsyncListener()); asyncContext.start(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try{ Thread.sleep(3000); }catch(InterruptedException e){ } String greeting="hi from listener"; System.out.println("wait...."); req.setAttribute("greeting", greeting); asyncContext.dispatch("/index.jsp"); } }); } }