Web容器會為每一個請求分配一個線程,默認(rèn)情況下,響應(yīng)完成前,該線程占用的資源都不會釋放。如果有些請求需要長時間處理(例如長時間運算,等待某個資源),就會長時間占用線程所需資源,對系統(tǒng)的性能造成負(fù)擔(dān)
Servlet3.0新增了異步處理,可以先釋放容器分配給請求的線程和相關(guān)資源,原先釋放了容器所分配線程的請求,響應(yīng)將會被延后
為了支持異步處理,ServletRequest中提供了startAsync()方法,方法的處理都是返回AsyncContext對象
public interface AsyncContext {
String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";
String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";
String ASYNC_PATH_INFO = "javax.servlet.async.path_info";
String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";
String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";
ServletRequest getRequest();
ServletResponse getResponse();
boolean hasOriginalRequestAndResponse();
void dispatch();
void dispatch(String var1);
void dispatch(ServletContext var1, String var2);
void complete();
void start(Runnable var1);
void addListener(AsyncListener var1);
void addListener(AsyncListener var1, ServletRequest var2, ServletResponse var3);
<T extends AsyncListener> T createListener(Class<T> var1) throws ServletException;
void setTimeout(long var1);
long getTimeout();
}
如果要調(diào)用ServletRequest的startAsync()以取得AsyncContext,必須告知容器當(dāng)前Servlet支持異步處理,可以在@WebServlet注解中設(shè)置asyncSupported為ture
@WebServlet(name = "AsyncServlet",urlPatterns = "async",asyncSupported = true)
當(dāng)然可以在web.xml中設(shè)置,如下:
<servlet>
<servlet-name>AsyncServlet</servlet-name>
<servlet-class>AsyncServlet</servlet-class>
<!-- 設(shè)置當(dāng)前Servlet支持異步處理 -->
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>AsyncServlet</servlet-name>
<url-pattern>/async</url-pattern>
</servlet-mapping>
如果當(dāng)前支持異步處理的Servlet之前有過濾器,則過濾器也需要設(shè)置asyncSupported為true,同樣可以在注解或web.xml中設(shè)置,否則會拋出如下錯誤:
A filter or servlet of the current chain does not support asynchronous operations.
@WebServlet(name = "AsyncServlet", urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1、開啟異步處理,響應(yīng)延后
AsyncContext async = request.startAsync();
// 2、做一些長時間運算或等待某個資源的操作
// 3、結(jié)束異步處理,開始對客戶端響應(yīng)
async.complete();
}
}
模擬服務(wù)器推送消息
Http是基于請求和響應(yīng)規(guī)范,Http服務(wù)器無法直接對客戶端傳輸消息,因為沒有請求就沒有響應(yīng);在這種請求、響應(yīng)模型下,如果客戶端想要獲取服務(wù)器端的最新狀態(tài),必須以定期方式發(fā)送請求,查詢服務(wù)器端的最新狀態(tài),但這種方式會浪費網(wǎng)絡(luò)流量。
一種解決方案就是,服務(wù)器將每次請求都延后處理,直到服務(wù)器端數(shù)據(jù)發(fā)送變化之后在進(jìn)行響應(yīng),當(dāng)然這樣的話客戶端會一直處于等待響應(yīng)狀態(tài),不過可以搭配Ajax發(fā)送異步請求,對請求在延后響應(yīng),就OK了。這就是所謂的服務(wù)器推送機(jī)制
在web容器中,我們可以監(jiān)聽所有的異步請求,然后統(tǒng)一做響應(yīng)延后處理,實現(xiàn)服務(wù)器推送,代碼如下:
/**
* 監(jiān)聽所有的異步請求,實現(xiàn)服務(wù)器推送的目的
*/
@WebListener()
public class AsyncsListener implements ServletContextListener {
// 所有的異步請求都放在該集合中
private List<AsyncContext> asyncs = new ArrayList<>();
@Override
public void contextInitialized(ServletContextEvent event) {
event.getServletContext().setAttribute("asyncs", asyncs);
new Thread(new Runnable() {
@Override
public void run() {
int num = (int) (Math.random() * 1000);
try {
Thread.sleep(num);
synchronized (asyncs) {
for (AsyncContext async : asyncs) {
async.getResponse().getWriter().print(num);
async.complete();
}
asyncs.clear();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
更多的AsyncContext細(xì)節(jié)
1. 如果沒有聲明 asyncSupported為true, 調(diào)用startAsync()將會拋出IllegalStateException
2. 當(dāng)在支持異步處理的Servlet中調(diào)用startAsync()方法時,該次請求會離開容器所分配的線程