2020-05-03
JAVASERVLET

Servlet Async 非同步動作

Servlet Async 非同步動作

Java Asynchronous Banner

有時候我們的請求需要很長時間的處理,可能是複雜的運算或是很大包的資源,這種時後就會造成執行緒長時間被佔用,久了久有可能拖慢整體的效能,甚至影響到一般的操作。

這種時候我們應該保留 Servlet 的資源,讓 Servlet 有資源分配給其他請求,等到長時間的處理資料處理完成後再回覆給客戶就好了!

而如何保留資源呢?就是丟出一個一個執行緒去外面囉! 讓我們看看 Servlet 中要怎麽實作吧!

Servlet 中 ServletRequest 提供了一個方法叫做 startAsync() 方法,會回傳 AsyncContext 物件,在取得 AsyncContext 之後,我們的回應會被延後,資源也就釋放回 Servlet 囉!

在使用之前,我們必須讓我們的 Servlet 物件支援 async 支援方法是在 @WebServlet 中設定

@WebServlet(
        urlPatterns = "/async",
        asyncSupported = true
)

設定過後就可以在 Servlet 物件中使用 Async 方法了!

@WebServlet(
        urlPatterns = "/async",
        asyncSupported = true
)
public class AsyncServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plan; charset=UTF8");
        AsyncContext asyncContext = request.startAsync(); // 開始非同步動作
          // 從 AsyncContext 內取得 Response 物件後,回應字串出去
          asyncContext.getResponse().getWriter().println("Hello Async")
        // 記得一定要跟它說完成了 
        // 不然就算結束了 頁面那端也完全不知道他結束了
        asyncContext.complete();
    }
}

這樣其實就是 Async 方法了,只是我們沒有做任何需要丟出去運算的事情而已 接下來就讓我們簡單丟一個執行緒出去試試看吧!!!

一樣拿剛剛的 AsyncServlet 來改

@WebServlet(
        urlPatterns = "/async",
        asyncSupported = true
)
public class AsyncServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plan; charset=UTF8");
        AsyncContext asyncContext = request.startAsync(); // 開始非同步動作

        doAsync()
            .thenApplyAsync(s -> s + "讓你久等了")
            .whenComplete((ok, err) -> {
                try {
                    asyncContext.getResponse().getWriter().println(ok); // 把 ok 的訊息印出來;因為只是測試 就先不處理 err 了!
                    asyncContext.complete(); // 記得一定要跟它說完成了 不然就算結束了 在頁面上看起來也是沒有結束
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        // asyncContext.complete(); 不可以放在這裡哦,如果放這裡會在 async 執行完之前,就被這裡的 complete() 給跳掉

        // --- 補充 ---
        // 如果沒有特別傳傳送其他 Response 進去
        // 我們原先的 response 跟 asyncContext.getResponse() 是一摸一樣的東西哦!
        // 只是都用 async 了,我覺得用 asyncContext.getResponse() 會比較清楚自己現在在 async 中~
        // System.out.println(asyncContext.getResponse());
        // System.out.println(response);
        // System.out.println(asyncContext.getResponse() == response);
    }

    /**
     * 非同步動作
     * @return
     */
    private CompletableFuture<String> doAsync() {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000); // 等個三秒
                return "I'm back!!";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

( 這邊很多的操作跟 Servlet 比較沒關係,執行緒的操作本來就比較困難所以看不懂也沒有關係,我們就主要聚焦於 AsyncContext 的操作就好了! )