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");
}
});
}
}