每個請求來到 Web 容器,Web 容器會為其分配一條執行緒來專門負責該請求,直到回應完成前,該執行緒都不會被釋放回容器。執行緒會耗用系統資源,若有些請 求需要長時間處理(例如長時間運算、等待某個資源),就會長時間佔用執行緒,若這類的請求很多,許多執行緒都被長時間佔用,對於系統就會是個效能負擔,甚至造成應用程式的效能瓶頸。
基本上一些需長時間處理的請求,通常客戶端也較不在乎請求後要有立即的回應,若可以,讓這類請求先釋放容器分配給該請求的執行緒,讓容器可以有機會將執行緒資源分配給其它的請求,可以減輕系統負擔。原先釋放了容器所分配執行緒的請求,其回應將被延後,直到處理完成(例如長時間運算完成、所需資源已獲得)再行對客戶端的回應。
在 Servlet 3.0 中,在 ServletRequest
上提供了 startAsync()
方法:
AsyncContext startAsync() throws java.lang.IllegalStateException;
AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws java.lang.IllegalStateException
這兩個方法都會傳回 AsyncContext
介面的實作物件,前者會直接利用原有的請求與回應物件來建立 AsyncContext
,後者可以讓你傳入自己建立的請求、回應包裹物件。在呼叫了 startAsync()
方法取得 AsyncContext
物件之後,這次的回應會被延後,並釋放容器所分配的執行緒。
你可以透過 AsyncContext
的 getRequest()
、getResponse()
方法取得請求、回應物件,此次對客戶端的回應將暫緩至呼叫 AsyncContext
的 complete()
方法或 dispatch()
為止,前者表示回應完成,後者表示將回應調派給指定的 URL。
若要能呼叫 ServletRequest
的 startAsync()
使用 AsyncContext
,你的 Servlet 必須能支援非同步處理,如果使用 @WebServlet
來標註,則可以設定其 asyncSupported
為 true
。例如:
@WebServlet(urlPatterns = "/asyncXXX", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
...
如果使用 web.xml 設定 Servlet,則可以設定 <async-supported>
標籤為 true
:
...
<servlet>
<servlet-name>AsyncXXX</servlet-name>
<servlet-class>cc.openhome.AsyncXXX</servlet-class>
<async-supported>true</async-supported>
</servlet>
...
如果 Servlet 將會非同步處理,若其前端有過濾器,則過濾器亦需標示其支援非同步處理,如果使用 @WebFilter
,同樣是可以設定其 asyncSupported
為 true
。例如:
@WebFilter(urlPatterns = "/asyncXXX", asyncSupported = true)
public class AsyncXXXFilter implements Filter{
...
如果使用 web.xml 設定過濾器,則可以設定 <async-supported>
標籤為 true
:
...
<filter>
<filter-name>AsyncXXXFilter</filter-name>
<filter-class>cc.openhome.AsyncXXXFilter</filter-class>
<async-supported>true</async-supported>
</filter>
...
底下示範一個非同步處理的例子,對於進來的請求,Servlet 會取得其 AsyncContext
,並釋放容器所分配的執行緒,回應被延後,對於這些被延後回應的請求,建立一個 CompletableFuture
物件,使用預設的執行緒池進行非同步處理。
package cc.openhome;
import java.io.*;
import java.util.concurrent.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet(
urlPatterns={"/async"},
asyncSupported = true
)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charset=UTF8");
AsyncContext ctx = request.startAsync();
doAsync(ctx).thenApplyAsync(String::toUpperCase)
.thenAcceptAsync(resource -> {
try {
ctx.getResponse().getWriter().println(resource);
ctx.complete();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
private CompletableFuture<String> doAsync(AsyncContext ctx) {
return CompletableFuture.supplyAsync(() -> {
try {
String resource = ctx.getRequest().getParameter("resource");
Thread.sleep(10000); // 模擬長時間的處理
return String.format("%s back finally...XD", resource);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}