11.3 编写异步Servlet
写一个异步或异步Servlet
或过滤器
比较简单。**当有一个需要相当长的时间完成的任务时,就需要为之创建一个异步的Servlet
或过滤器
**。
编写异步Servlet或过滤器步骤
在异步Servlet
或过滤器
类中需要做如下操作:
(1)调用ServletRequest
中的startAsync
方法。该startAsync
返一个AsyncContext
。
(2)调用AsyncContext
的setTimeout()
方法,设置容器等待任务完成的超时时间的毫秒数。此步骤是可选的,但如果你不设置超时,容器的将使用默认的超时时间。如果任务未能在指定的超时时间内完成,将会抛出一个超时异常。
(3)调用asyncContext.start()
方法,传递一个Runnable
来执行一个长时间运行的任务。
(4)调用Runnable
的asynccontext.complete
或asynccontext.dispatch
方法来完成任务。
异步Servlet的doGet或doPost模板
这里是一个异步Servlet
的doGet
或doPost
方法的框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| final AsyncContext asyncContext = servletRequest.startAsync();
asyncContext.setTimeout( ... );
asyncContext.start(new Runnable() { @Override public void run() { ..... asyncContext.complete() or asyncContext.dispatch() } })
|
实例1
一个简单的异步调度的Servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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() { try { Thread.sleep(3000); } catch (InterruptedException e) {} request.setAttribute("workerThread", Thread.currentThread().getName()); asyncContext.dispatch("/threadNames.jsp"); } }); } }
|
这个Servlet
支持异步处理,其长期运行的任务就是简单地休眠5
秒钟。为了证明这个耗时的任务是在不同的线程中执行的,而不是在主线程中执行的(即执行 Servlet
的doGet
方法),它将主线程的名字和工作线程的ServletRequest
分派到一个threadNames.jsp
页面。该threadNames.jsp
页面显示mainThread
和WorkerThread
变量。它们应打印不同的线程名字。
threadNames.jsp
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html> <head> <title>Asynchronous servlet</title> </head> <body> Main thread: ${mainThread} <br/> Worker thread: ${workerThread} </body> </html>
|
注意,你需要在任务结束后调用asyncContext
的dispatch
方法或complete
方法,所以它不会等待,直到它超时。
你可以把你的这个URL
输入到浏览器来测试servlet
:
http://localhost:8080/app11a/asyncDispatch
运行效果
下图显示了主线程的名称和工作线程的名称。你在你的浏览器中看到的可能是不同的,但打印出的线程名字会有所不同,证明了工作线程与主线程不同。
除了调度到其他资源去完成任务,你也可以调用AsyncContext
的complete
方法。此方法通知servlet
容器该任务已完成。
实例2
发送最新进度更新的异步servle
该Servlet
每秒发送一次进度更新,使用户能够监测进展情况。它发送HTML
响应和一个简单的JavaScript
代码来更新HTML div
元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.AsyncContext; import javax.servlet.ServletException; 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(); } }); } }
|
以下这段代码负责发送进度更新:
1 2 3 4 5 6
| writer.println("<script>"); writer.println("document.getElementById(" + "'progress').innerHTML = '" + (i * 10) + "% complete'"); writer.println("</script>"); writer.flush();
|
浏览器将收到此字符串,其中x是10和100之间的数字。
1 2 3
| <script> document.getElementById('progress').innerHTML = 'x% complete' </script>
|
因为该更新代码放在for
循环中,将发送10次,浏览器也接收到10次javascript
代码,进而执行javascript
代码10次,也就是更新div
标签10次从而呈现动态的效果.
部署描述符web.xml
为了向你展示如何通过在部署描述符中声明来编写一个Servlet
异步,上面的AsyncCompleteServlet
不用@WebServlet
注解。而是使用下面的部署描述符(web.xml
文件)来配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <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>/asyncComplete</url-pattern> </servlet-mapping> </web-app>
|
小结
在部署描述符中把servlet
标签的async-supported
子标签设置为true
就可以支持异步的Servlet
.
显示效果
你可以把你的URL
输入到浏览器来测试AsyncCompleteServlet
:
http://localhost:8080/app11a/asyncComplete
部分显示效果如下:
……
……