在之前的Servlet规范中,若是Servlet做为控制器调用了一个耗时的业务方法,那么Servlet必须等到业务方法彻底返回以后才会生成响应,这将使得Servlet对业务方法的调用变成一种阻塞式的调用,所以效率比较低。Servlet3.0规范引入了异步处理来解决这个问题,异步处理容许Servlet从新发起一条线程去调用耗时的业务方法,这样就能够避免等待。html
Servlet3.0的异步处理是经过AsyncContext类来处理的,Servlet可经过ServletRequest的以下两个方法来开启异步调用、建立AsyncContext对象:java
AsyncContext startAsync()web
AsyncContext startAsync(ServletRequest, ServletResponse)浏览器
重复调用上面的方法将获得同一个AsyncContext对象。AsyncContext对象表明异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch用于请求、启动后台线程、获取request,response对象等功能,下面是一个进行异步处理的Servlet类:session
@WebServlet(urlPatterns="/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { response.setContentType("text/html;charset=GBK"); PrintWriter out = response.getWriter(); out.println("进入Servlet的时间:" + new Date() + ".<br/>"); out.flush(); AsyncContext acontext = request.startAsync(); acontext.setTimeout(20*1000); acontext.start(new Executor(acontext)); out.println("结束Servlet的时间:" + new Date() + ".<br/>"); out.flush(); } }
public class Executor implements Runnable { private AsyncContext context; public Executor(AsyncContext context) {this.context = context;} public void run(){ try { Thread.sleep(5000); ServletRequest request = context.getRequest(); List<String> books = new ArrayList<String>(); books.add("book1"); books.add("book2"); books.add("book3"); request.setAttribute("books", books); context.dispatch("/async.jsp"); } catch (Exception e) { e.printStachTrace(); } } }
在此Executor中,让线程睡眠5秒来模拟调用的耗时的业务方法,最后调用AsyncContext的dispatch方法把请求转发到指定的JSP页面。被异步请求的JSP页面须要指定session="false",代表该页面不会从新建立Session,下面是async.jsp的内容:app
<%@ page contentType="text/html;chaset=GBK" language="java" session="fasle" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <ul> <c:forEach items="${books}" var="book" > <li>${book}</li> </c:forEach> </ul> <% out.println("业务调用结束的时间:" + new Date()); request.getAsyncContext().complete();//完成异步调用 %>
上面的页面只是一个JSP页面,只是使用了JSTL标签库来迭代输出books集合,所以须要将JSTL的两个Jar包复制到项目的lib目录中。异步
对于但愿启用异步调用的Servlet而言,开发者必须显示指定开启异步调用,为Servlet开启异步调用有两种方式:1). 在@WebServlet中指定asyncSupported=true 2). 在web.xml的<servlet>元素中增长<async-supported>子元素,如下是一个配置片断:jsp
<servlet> <servlet-name>async</servlet-name> <servlet-class>com.abc.AsyncServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>async</servlet-name> <url-pattern>/async</url-pattern> </servlet-mapping>
对于支持异步调用的Servlet来讲,当Servlet以异步方式启用新线程后,该Servlet的执行不会被阻塞,该Servlet将能够向客户端浏览器生成相应——当新线程执行完成后,新线程生成的相应将再次被送往客户端浏览器。async
当Servlet启用异步调用的线程以后,该线程的执行过程对开发者是透明的。但在有些状况下,开发者须要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这能够借助于Servlet3.0提供的异步监听器来实现。异步监听器须要实现AsyncListener接口,该接口中定义了以下方法:工具
onStartAsync(AsyncEvent event):当异步调用开始时触发该方法
onComplete(AsyncEvent event):当异步调用结束时触发该方法
onError(AsyncEvent event):当异步调用出错时触发该方法
onTimeout(AsyncEvent event):当异步调用超时时触发该方法
下面是一个简单的监听器类:
public class MyAsyncListener implements AsyncListener { public void onComplete(AsyncEvent event) { System.out.println("异步调用完成:" + new Date()); } public void onError(AsyncEvent event) { System.out.println("异步调用出错:" + new Date()); } public void onStartAsync(AsyncEvent event) { System.out.println("异步调用开始:" + new Date()); } public void onTimeout(AsyncEvent event) { System.out.println("异步调用超时:" + new Date()); } }
提供了监听器以后,还须要经过AsynContext来注册此监听器,调用该对象的addListener()方法便可完成监听器的注册:
AsyncContext acontext = request.startAsync(); acontext.addListener(new MyAsyncListener());