在Servlet3.0的规范中新增了对异步请求的支持,SpringMVC又在此基础上对异步请求提供了方便。异步请求是在处理比较耗时的业务时先将request返回,然后另起线程处理耗时的业务,处理完后在返回给用户。
异步请求可以给我们带来很多方便,最直接的用法就是处理耗时的业务,比如,需要查询数据库,需要调用别的服务器来处理等情况下可以先将请求返回给客户端,然后启用新线程处理耗时业务。如果我们合适的扩展可以实现订阅者模式的消息订阅功能,比如,当有异常情况发生时可以主动将相关信息发送给运维人员,还有现在的很多邮箱自动回复都是使用这种技术。
Http协议是单向的,只能客户端自己拉不能服务器主动推,Servlet对异步请求的支持并没有修改Http,而是对Http的巧妙利用。异步请求的核心原理主要分为两大类,一类是轮询,另一类是长连接。轮询就是定时自动发起请求检查有没有需要返回的数据,这种对资源浪费比较大。长连接的原理是客户端发起请求,服务端处理并返回后并不结束连接,这样就可以在后面再次返回给客户端数据。Servlet对异步请求的支持其实采用的是长连接的方式,也就是说,异步请求中在原始的请求返回的时候并没有关闭连接,关闭的只是处理请求的那个县城,只有在异步请求全部处理完之后才会关闭连接。
在Servlet3.0规范中使用异步处理请求非常简单,只需要在请求处理过程中调用request的startAsync返回AsyncContext。
什么是AsyncContext在异步请求中充当着非常重要的角色,可以称为异步请求上下文也可以称为异步请求容器。类似于ServletContext.我们多次调用startAsync都是返回的同一个AsyncContext。代码如下:
public interface AsyncContext {
String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";
String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";
String ASYNC_PATH_INFO = "javax.servlet.async.path_info";
String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";
String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";
ServletRequest getRequest();
ServletResponse getResponse();
boolean hasOriginalRequestAndResponse();
void dispatch();
void dispatch(String var1);
void dispatch(ServletContext var1, String var2);
void complete();
void start(Runnable var1);
void addListener(AsyncListener var1);
void addListener(AsyncListener var1, ServletRequest var2, ServletResponse var3);
T createListener(Class var1) throws ServletException;
void setTimeout(long var1);
long getTimeout();
}
getResponse() 用于获取response。dispatch用于分发新地址。complete用于通知容器已经处理完了,start方法用于启动实际处理线程,addListener用于添加监听器;setTimeout方法用于修改超时时间。
@WebServlet(
name = “WorkServlet”,
urlPatterns = “/work”,
asyncSupported = true
)
public class WorkServlet extends HttpServlet {
private static final long serialVersionUID =1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置ContentType,关闭缓存
resp.setContentType("text/plain;charset=UTF-8");
resp.setHeader("Cache-Control","private");
resp.setHeader("Pragma","no-cache");
final PrintWriter writer= resp.getWriter();
writer.println("老师检查作业了");
writer.flush();
List zuoyes=new ArrayList();
for (int i = 0; i < 10; i++) {
zuoyes.add("zuoye"+i);;
}
final AsyncContext ac=req.startAsync();//开启异步请求
doZuoye(ac,zuoyes);
writer.println("老师布置作业");
writer.flush();
}
private void doZuoye(final AsyncContext ac, final List zuoyes) {
ac.setTimeout(1*60*60*1000L);
ac.start(new Runnable() {
@Override
public void run() {
//通过response获得字符输出流
try {
PrintWriter writer=ac.getResponse().getWriter();
for (String zuoye:zuoyes) {
writer.println("\""+zuoye+"\"请求处理中");
Thread.sleep(1*1000L);
writer.flush();
}
ac.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
在上面的程序是我们最基本的异步请求,不过不够完善。老师是需要思考宏观问题,所以在写完作业之后需要给老师汇报哪些题难,哪些题目有问题或者自己的这次经验总结,不过这些事不应该由做作业的学生来做,应该由专门的学习汇报员来统计分析。所以就有了监听器。
public class TeacherListener implements AsyncListener {
final SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void onComplete(AsyncEvent event) throws IOException {
System.out.println("在"+formatter.format(new Date())+"工作处理完成");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
System.out.println("在"+formatter.format(new Date())+"工作超时");
}
@Override
public void onError(AsyncEvent event) throws IOException {
System.out.println("在"+formatter.format(new Date())+"工作处理错误");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
System.out.println("在"+formatter.format(new Date())+"工作处理开始");
}
}
所有代码具体参照github地址
https://github.com/lzggsimida123/ServletAsync