Servlet 3特性:異步Servlet
理解異步Servlet之前,讓我們試著理解為什么需要它。假設我們有一個Servlet需要很多的時間來處理,類似下面的內容:
LongRunningServlet.java
package com.journaldev.servlet;import java.io.IOException; import java.io.PrintWriter;
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("/LongRunningServlet") public class LongRunningServlet extends HttpServlet { private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); System.out.println("LongRunningServlet Start::Name=" + Thread.currentThread().getName() + "::ID=" + Thread.currentThread().getId()); String time = request.getParameter("time"); int secs = Integer.valueOf(time); // max 10 seconds if (secs > 10000) secs = 10000; longProcessing(secs); PrintWriter out = response.getWriter(); long endTime = System.currentTimeMillis(); out.write("Processing done for " + secs + " milliseconds!!"); System.out.println("LongRunningServlet Start::Name=" + Thread.currentThread().getName() + "::ID=" + Thread.currentThread().getId() + "::Time Taken=" + (endTime - startTime) + " ms."); } private void longProcessing(int secs) { // wait for given time before finishing try { Thread.sleep(secs); } catch (InterruptedException e) { e.printStackTrace(); } }
}</pre>
如果我們的URL是:http://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000
得到響應為“Processing done for 8000 milliseconds! !“。現在,如果你會查看服務器日志,會得到以下記錄:
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103 LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.所以Servlet線程實際運行超過 8秒,盡管大多數時間用來處理其它Servlet請求或響應。
這可能導致線程饑餓——因為我們的Servlet線程被阻塞,直到所有的處理完成。如果服務器的請求得到了很多過程,它將達到最大Servlet線程限制和進一步的請求會拒絕連接錯誤。
Servlet 3.0之前,這些長期運行的線程容器特定的解決方案,我們可以產生一個單獨的工作線程完成耗時的任務,然后返回響應客戶。Servlet線程返回Servlet池后啟動工作線程。Tomcat 的 Comet、WebLogic FutureResponseServlet 和 WebSphere Asynchronous Request Dispatcher都是實現異步處理的很好示例。
容器特定解決方案的問題在于,在不改變應用程序代碼時不能移動到其他Servlet容器。這就是為什么在Servlet3.0提供標準的方式異步處理Servlet的同時增加異步Servlet支持。
實現異步Servlet
讓我們看看步驟來實現異步Servlet,然后我們將提供異步支持Servlet上面的例子:
- 首先Servlet,我們提供異步支持 Annotation @WebServlet 的屬性asyncSupported 值為true。
- 由于實際實現委托給另一個線程,我們應該有一個線程池實現。我們可以一個通過Executors framework 創建線程池和使用servlet context listener來初始化線程池。
- 通過ServletRequest.startAsync方法獲取AsyncContext的實例。AsyncContext提供方法讓ServletRequest和ServletResponse對象引用。它還提供了使用調度方法將請求轉發到另一個 dispatch() 方法。
- 編寫一個可運行的實現,我們將進行重處理,然后使用AsyncContext對象發送請求到另一個資源或使用ServletResponse編寫響應對象。一旦處理完成,我們通過AsyncContext.complete()方法通知容器異步處理完成。
- 添加AsyncListener實現AsyncContext對象實現回調方法,我們可以使用它來提供錯誤響應客戶端裝進箱的錯誤或超時,而異步線程處理。在這里我們也可以做一些清理工作。
</ol>一旦我們將完成我們的項目對于異步Servlet示例,項目結構看起來會像下面的圖片:
在監聽中初始化線程池
package com.journaldev.servlet.async;import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener;
@WebListener public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) { // create the thread pool ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100)); servletContextEvent.getServletContext().setAttribute("executor", executor); } public void contextDestroyed(ServletContextEvent servletContextEvent) { ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent .getServletContext().getAttribute("executor"); executor.shutdown(); }
}</pre>
實現很直接,如果你不熟悉ThreadPoolExecutor 框架請讀線程池的ThreadPoolExecutor 。關于listeners 的更多細節,請閱讀教程Servlet Listener。
工作線程實現
package com.journaldev.servlet.async;import java.io.IOException; import java.io.PrintWriter;
import javax.servlet.AsyncContext;
public class AsyncRequestProcessor implements Runnable {
private AsyncContext asyncContext; private int secs; public AsyncRequestProcessor() { } public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) { this.asyncContext = asyncCtx; this.secs = secs; } @Override public void run() { System.out.println("Async Supported? " + asyncContext.getRequest().isAsyncSupported()); longProcessing(secs); try { PrintWriter out = asyncContext.getResponse().getWriter(); out.write("Processing done for " + secs + " milliseconds!!"); } catch (IOException e) { e.printStackTrace(); } //complete the processing asyncContext.complete(); } private void longProcessing(int secs) { // wait for given time before finishing try { Thread.sleep(secs); } catch (InterruptedException e) { e.printStackTrace(); } }
}</pre>
注意:在請求和響應時使用AsyncContext對象,然后在完成時調用 asyncContext.complete() 方法。
AsyncListener 實現
package com.journaldev.servlet.async;import java.io.IOException; import java.io.PrintWriter;
import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebListener;
@WebListener public class AppAsyncListener implements AsyncListener {
@Override public void onComplete(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onComplete"); // we can do resource cleanup activity here } @Override public void onError(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onError"); //we can return error response to client } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onStartAsync"); //we can log the event here } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onTimeout"); //we can send appropriate response to client ServletResponse response = asyncEvent.getAsyncContext().getResponse(); PrintWriter out = response.getWriter(); out.write("TimeOut Error in Processing"); }
}</pre>
通知的實現在 Timeout()方法,通過它發送超時響應給客戶端。
Async Servlet 實現
這是我們的異步Servlet實現,注意使用AsyncContext和ThreadPoolExecutor進行處理。
package com.journaldev.servlet.async;import java.io.IOException; import java.util.concurrent.ThreadPoolExecutor;
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(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true) public class AsyncLongRunningServlet extends HttpServlet { private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); System.out.println("AsyncLongRunningServlet Start::Name=" + Thread.currentThread().getName() + "::ID=" + Thread.currentThread().getId()); request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); String time = request.getParameter("time"); int secs = Integer.valueOf(time); // max 10 seconds if (secs > 10000) secs = 10000; AsyncContext asyncCtx = request.startAsync(); asyncCtx.addListener(new AppAsyncListener()); asyncCtx.setTimeout(9000); ThreadPoolExecutor executor = (ThreadPoolExecutor) request .getServletContext().getAttribute("executor"); executor.execute(new AsyncRequestProcessor(asyncCtx, secs)); long endTime = System.currentTimeMillis(); System.out.println("AsyncLongRunningServlet End::Name=" + Thread.currentThread().getName() + "::ID=" + Thread.currentThread().getId() + "::Time Taken=" + (endTime - startTime) + " ms."); }
}</pre>
Run Async Servlet
現在,當我們將上面運行servlet URL:
http://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000
得到響應和日志:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124 AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms. Async Supported? true AppAsyncListener onComplete如果運行時設置time=9999,在客戶端超時以后會得到響應超時錯誤處理和日志:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117 AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms. Async Supported? true AppAsyncListener onTimeout AppAsyncListener onError AppAsyncListener onComplete Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing. at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439) at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197) at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) at java.lang.Thread.run(Thread.java:680)注意:Servlet線程執行完,很快就和所有主要的處理工作是發生在其他線程。
這是所有異步Servlet內容,希望你喜歡它。下載 AsyncServletExample 工程
原文鏈接: journaldev 翻譯: ImportNew.com - 彭秦進
譯文鏈接: http://www.importnew.com/8864.html