一. 概述
異步處理功能可以節約容器線程。你應該將此功能 使用在長時間運行的操作上。此功能的作用是釋放正在 等待完成的線程,使該線程能夠被另一請求所使用。
二. 編寫異步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"); } }); } }