Servlet 3特性:異步Servlet

jopen 10年前發布 | 33K 次閱讀 Servlet Java開發

理解異步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上面的例子:

  1. 首先Servlet,我們提供異步支持 Annotation @WebServlet 的屬性asyncSupported 值為true。
  2. 由于實際實現委托給另一個線程,我們應該有一個線程池實現。我們可以一個通過Executors framework 創建線程池和使用servlet context listener來初始化線程池。
  3. 通過ServletRequest.startAsync方法獲取AsyncContext的實例。AsyncContext提供方法讓ServletRequest和ServletResponse對象引用。它還提供了使用調度方法將請求轉發到另一個 dispatch() 方法。
  4. 編寫一個可運行的實現,我們將進行重處理,然后使用AsyncContext對象發送請求到另一個資源或使用ServletResponse編寫響應對象。一旦處理完成,我們通過AsyncContext.complete()方法通知容器異步處理完成。
  5. 添加AsyncListener實現AsyncContext對象實現回調方法,我們可以使用它來提供錯誤響應客戶端裝進箱的錯誤或超時,而異步線程處理。在這里我們也可以做一些清理工作。
  6. </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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!