jsp 异步处理
一. 概述
异步处理功能可以节约容器线程。你应该将此功能 使用在长时间运行的操作上。此功能的作用是释放正在 等待完成的线程,使该线程能够被另一请求所使用。
二. 编写异步Servlet和过滤器
WebServlet和WebFilter注解类型可能包含新的 asyncSupport属性。要编写支持异步处理的Servlet或过 滤器,需设置asyncSupported属性为true:
@WebServlet(asyncSupported=true ...) @WebFilter(asyncSupported=true ...)
此外,也可以在部署文件里面指定这个描述符。例 如,下面的 Servlet 配置为支持异步处理:
<servlet> <servlet-name>AsyncServlet</servlet-name> <servlet-class>servlet.MyAsyncServlet</servlet-class> <async-supported>true</async-supported> </servlet>
Servlet或过滤器要支持异步处理,可以通过调用 ServletRequest的startAsync方法来启动一个新线程。这 里有两个startAsync的重载方法:
AsyncContext startAsync() throws java.lang.IllegalStateExceptio
n
AsyncContext startAsync(ServletRequest servletRequest,
ServletResponse servletResponse) throws
java.lang.IllegalStateException
这两个重载方法都返回一个AsyncContext的实例, 这个实例提供各种方法并且包含ServletRequest和 ServletResponse。第一个重载实例比较简单并且使用方 便。由此生成的asynccontext实例将包含原生的 ServletRequest和ServletResponse。第二个允许您将原来 的ServletRequest和ServletResponse进行重写封装后传给 asynccontext。需要注意的是,你只能传递原生的 ServletRequest和ServletResponse或它们的封装到 startAsync第二种重载实例。
注意,startAsync重复调用将返回相同的 asynccontext。若一个Servlet或过滤器调用startAsync时 不支持异步处理,将抛出java.lang.illegalstateexception 异常。还请注意,asynccontext的start方法是非阻塞的, 所以下一行代码仍将执行,即使还未调度线程启动。
三. 编写异步Servlets
写一个异步或异步Servlet或过滤器比较简单。当有 一个需要相当长的时间完成的任务时,需要创建一个异 步的Servlet或过滤器。在异步Servlet或过滤器类中需要 做如下操作:
(1)调用ServletRequest中的startAsync方法。该 startAsync返一个AsyncContext 。
(2)调用AsyncContext的setTimeout(),传递容器 等待任务完成的超时时间的毫秒数。此步骤是可选的, 但如果你不设置超时,容器的将使用默认的超时时间。 如果任务未能在指定的超时时间内完成,将会抛出一个 超时异常。
(3)调用asyncContext.start,传递一个Runnable来 执行一个长时间运行的任务。
(4)调用Runnable的asynccontext.complete或 asynccontext.dispatch方法来完成任务。
这里是一个异步Servlet的doGet或doPost方法的框 架:
final AsyncContext asyncContext = servletRequest.startAsync(); asyncContext.setTimeout( ... ); asyncContext.start(new Runnable() { @Override public void run() { // long running task asyncContext.complete() or asyncContext.dispatch() } })
下面例子显示了支持异步处理的 Servlet。
AsyncDispatchServlet 类
package servlet; 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 public void doGet(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final AsyncContext asyncContext = request.startAsync(); request.setAttribute("mainThread", Thread.currentThread().getName()); asyncContext.setTimeout(5000); asyncContext.start(new Runnable() { @Override public void run() { // long-running task try { Thread.sleep(3000); } catch (InterruptedException e) { } request.setAttribute("workerThread", Thread.currentThread().getName()); // 调度到其它资源取完成任务 例如threadNames.jsp页面 asyncContext.dispatch("/threadNames.jsp"); } }); } }
这个Servlet支持异步处理且其长期运行的 任务就是简单地休眠三秒钟。为了证明这个长时间运行 的任务是在不同的线程中执行的,而不是在主线程中执 行的(即执行 Servlet的doGet方法),它将主线程的名 字和工作线程的ServletRequest分派到一个 threadNames.jsp页面。该threadNames.jsp页面显示mainThread和WorkerThread变量。它 们应打印不同的线程名字。
threadNames.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> mainThread: ${mainThread} <br /> workThread: ${workerThread} </body> </html>
注意,你需要在任务结束后调用asynccontext的 dispatch或complete,所以它不会等待,直到它超时。
你可以把你的这个URL输入到浏览器来测试 servlet:
上图显示了主线程的名称和工作线程的名称。你 在你的浏览器中看到的可能是不同的,但打印出的线程 名字会有所不同,证明了工作线程与主线程不同.
除了调度到其他资源去完成任务,你也可以调用 AsyncContext的complete方法。此方法通知servlet容器 该任务已完成。
作为第二个例子,思考一下清单 AsyncCompleteServlet Servlet。 该Servlet每秒发送一次进度更新,使用户能够监测进展 情况。它发送HTML响应和一个简单的JavaScript代码来 更新HTML div元素。
package servlet; import 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; public class AsyncCompleteServlet extends HttpServlet { private static final long serialVersionUID = 78234L; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); final PrintWriter writer = response.getWriter(); writer.println("<html><head><title>" + "Async Servlet</title></head>"); writer.println("<body><div id='progress'></div>"); final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(60000); asyncContext.start(new Runnable() { @Override public void run() { System.out.println("new thread:" + Thread.currentThread()); for (int i = 0; i < 10; i++) { writer.println("<script>"); writer.println("document.getElementById(" + "'progress').innerHTML = '" + (i * 10) + "% complete'"); writer.println("</script>"); writer.flush(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } writer.println("<script>"); writer.println("document.getElementById(" + "'progress').innerHTML = 'DONE'"); writer.println("</script>"); writer.println("</body></html>"); asyncContext.complete(); } }); } }
部署描述符(web.xml文 件)
<servlet> <servlet-name>AsyncComplete</servlet-name> <servlet-class>servlet.AsyncCompleteServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>AsyncComplete</servlet-name> <url-pattern>/async-supported</url-pattern> </servlet-mapping>
四. 异步监听器
为支持Servlet和过滤器配合执行异步操作,Servlet 3.0还增加了asynclistener接口用于接收异步处理过程中 发生事件的通知。AsyncListener接口定义了如下方法, 当某些事件发生时调用:
void onStartAsync(AsyncEvent event)
在异步操作启动完毕后调用该方法
void onComplete(AsyncEvent event)
在异步操作完成后调用该方法。
void onError(AsyncEvent event)
在异步操作失败后调用该方法.
void onTimeout(AsyncEvent event)
在异步操作超时后调用该方法,即当它未能在指定 的超时时间内完成时。
所有四种方法可以分别通过它们的 getAsyncContext、getSuppliedRequest和 getSuppliedResponse方法,从AsyncContext、 ServletRequest、ServletResponse中获取相关的 AsyncEvent。
这里有一个例子,MyAsyncListener类 实现AsyncListener接口,以便在异步操作事件发生时, 它能够得到通知。请注意,和其他网络监听器不同,你 不需要通过@WebListener注解来实现。
package listener; import java.io.IOException; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; // 不需要标注@WebListener public class MyAsyncListener implements AsyncListener { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { System.out.println("onComplete"); } @Override public void onError(AsyncEvent asyncEvent) throws IOException { System.out.println("onError"); } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { System.out.println("onStartAsync"); } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { System.out.println("onTimeout"); } }
由于AsyncListener类不是用@WebListener注解的, 因此必须为AsyncContext手动注册一个AsyncListener监 听器,用于接收所需要的事件。通过调用addListener方 法为AsyncContext注册一个AsyncListener监听器:
Class lClass; try { lClass = Class.forName("listener.MyAsyncListener"); AsyncListener al = asyncContext.createListener(lClass); asyncContext.addListener(al); } catch (ClassNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }
使用asynclistener
package servlet; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import listener.MyAsyncListener; @WebServlet(name = "AsyncListenerServlet", urlPatterns = { "/asyncListener" }, asyncSupported = true) public class AsyncListenerServlet extends HttpServlet { private static final long serialVersionUID = 62738L; @Override public void doGet(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(5000); Class lClass; try { lClass = Class.forName("listener.MyAsyncListener"); AsyncListener al = asyncContext.createListener(lClass); asyncContext.addListener(al); } catch (ClassNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { } String greeting = "hi from listener"; System.out.println("wait...."); request.setAttribute("greeting", greeting); asyncContext.dispatch("/test.jsp"); } }); } }