servlet之前的操作同時同步的,就是按照這樣的一個流程來走的:
1.請求根據一個路徑路由到一個servlet中,
2.servlet獲取一系列的參數
3.執行一系列的邏輯(花費時間所占的比重也更大)
4.返回結果
上面的問題出現在這一系列的操作都是同步的,所以這個請求必定是堵塞到所以任務都完成之後才返回的,
這樣將會很浪費資源,因為線程堵塞在那裡,僅僅是等待任務的完成。但是在servlet3.0之後,我們基本上可以
是這樣做的
1.請求根據一個路徑路由到一個servlet中,
2.將邏輯放入到異步隊列中去
3.返回結果
4.異步隊列處理任務,得出結果,返回給頁面
而servet3.0對於異步的處理主要涉及的有兩個特性,一個是新增的類AsyncContext,另外的一個就是asyncSupported屬性
①如果我們想要讓我們的servlet支持異步的話,那麼asyncSupported這個屬性是一定需要設置的,對於注解的類型來說,我們直接設置屬性
@WebServlet(asyncSupported=true,urlPatterns={"/async"})
就可以了,對於老版本的配置問價來說,只需要在配置web.xml 的servlet那裡增加一個
<async-supported>true</async-supported>
還有一個就是對於動態的servlet,設置
dynamic.setAsyncSupported(true);
就可以了
②而對於AsyncContext 需要記住的東西還是蠻多的,但是它主要的是保留了請求和相應的引用,在前面提到的返回結果之後的操作就是通過在異步環境下,對這兩個引用進行操作。
要獲取這個就需要使用request在3.0之後增加的方法,startAsync(..) ,這個方法就是返回一個AsyncContext實體對象,這裡包含了request和response的引用,至於我們異步的處理方式,就有很多種了,我們可以直接定義一個工作隊列,異步的方式一個個的進行處理,又或者是直接使用AsyncContext.start(Runnable)方法啟動一個新的線程去進行處理邏輯
AsyncContext主要的方法:
getRequest() 獲得請求即request,我們可以在異步的環境像在service中使用一樣
getReponse() 和上面差不多一個意思
hasOriginalRequestAndResponse()這個方法表示的是我們使用的AsyncContext是使用原始的請求獲取的,還是通過封裝過的請求和相應創建的
簡單的講就是 原始的類型表示的是調用startAsync()。但是封裝的就是startAsync(ServletRequest, ServletResponse)或者其他類型啦,
dispatch()方法,這個方法有有好幾個重載,表示的是轉發,和req.getRequestDispatcher()有點類似,但是比較豐富
如果使用的是startAsync(ServletRequest, ServletResponse)初始化AsyncContext,且傳入的請求是HttpServletRequest的一個實例,則使用HttpServletRequest.getRequestURI()返回的URI進行分派。否則分派的是容器最後分派的請求URI。
下面的代碼是網上的:
// 請求到 /url/A AsyncContext ac = request.startAsync(); ... ac.dispatch(); // 異步分派到 /url/A // 請求到 /url/A // 轉發到 /url/B request.getRequestDispatcher(“/url/B”).forward(request, response); // 從FORWARD的目標內啟動異步操作 AsyncContext ac = request.startAsync(); ac.dispatch(); // 異步分派到 /url/A // 請求到 /url/A // 轉發到 /url/B request.getRequestDispatcher(“/url/B”).forward(request, response); // 從FORWARD的目標內啟動異步操作 AsyncContext ac = request.startAsync(request, response); ac.dispatch(); //異步分派到 /url/B
dispatch(String path) 這個方法就是轉發到指定的url上去
complete():在我們使用了request.startAsync(..)獲得AsyncContext之後,在完成異步操作以後,需要調用這個方法結束異步的操作。如果請求分派到一個不支持異步操作的Servlet,或者由AsyncContext.dispatch調用的目標servlet之後沒有調用complete,則complete方法會由容器調用。但是對於比合法操作來說,比如沒有調用startAsync放方法,卻代用complete() ,那麼就會拋出IllegalStateException的異常,同時在調用complete()之前,調用dispath()方法是不起作用的,當然了,因為這個時候異步還沒結束嘛,當然不會又什麼作用了。
setTimeOut(..) 設置超時的時間 表示的是異步處理的最大時間,如果是一個負數的話,那麼表示永遠不會超時
start(Runnable run) Runnable表示的就是異步處理的任務。我們在做的時候 會AsyncContext 帶進去 因為所以的操作 都需要依靠他呢
addListener(AsyncListener listener);增加監聽器 就是監聽AsyncContext各種狀態發現變化的,主要有

前面三個都比較好理解,最後異步監聽器將以它們添加到請求時的順序得到通知。
下面是AsyncContext的一般使用方式
package com.hotusm.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns={"/url"},asyncSupported=true)
public class AsynDemoServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//resp.setHeader("Connection", "Keep-Alive");
resp.setContentType("text/html;charset=utf-8");
System.out.println(req.isAsyncSupported()+" "+req.isAsyncStarted());
/*req.getAsyncContext(); 表示的是最近的那個被request創建或者是
* 重轉發的AsyncContext
*/
final AsyncContext ac = req.startAsync();
//設置超時的時間
ac.setTimeout(5*1000L);
//這種方式
ac.start(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
PrintWriter writer = ac.getResponse().getWriter();
writer.write("1");
writer.flush();
//這是測試 同一個AsyncContext在沒有調用complete 之前能不能多次的
//調用request 和response
PrintWriter writer1 = ac.getResponse().getWriter();
writer1.write("2");
writer1.flush();
ServletRequest request = ac.getRequest();
request.setAttribute("isAsyn", true);
/*
* 2.在調用完complete之後 表示這個異步已經結束了 如果在調用
* getRequest 或者是getResponse的話 都會拋出IllegalStateException
*
* */
ac.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
});
//設置監聽
ac.addListener(new AsyncListenerImpl());
// 在同一個request中不能同時調用多次
//req.startAsync();
PrintWriter out = resp.getWriter();
out.write("hello async");
out.write("<br/>");
//調用flush 不然還是不會輸出 因為沒有將內容刷出去
out.flush();
}
static class AsyncListenerImpl implements AsyncListener{
public void onComplete(AsyncEvent event) throws IOException {
System.out.println("onComplete");
}
public void onTimeout(AsyncEvent event) throws IOException {
System.out.println("onTimeout");
event.getAsyncContext().complete();
}
public void onError(AsyncEvent event) throws IOException {
System.out.println("onError");
}
public void onStartAsync(AsyncEvent event) throws IOException {
System.out.println("onStartAsync");
}
}
}
當我們上面的url的時候 會馬上返回hello async,然後在大概三秒鐘之後,輸出12
上面的方式只是使用了start(Runnable run);的方式.我們也可以將AsyncContext放到一個工作隊列中去,然後另外的一個線程池去做處理。
示例代碼:
package com.hotusm.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
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={"/async1"},asyncSupported=true)
public class AsyncDispatchServlet1 extends HttpServlet{
private LinkedBlockingQueue<AsyncContext> works=new LinkedBlockingQueue<AsyncContext>(100);
@Override
public void init() throws ServletException {
//因為這裡是測試 所以就開了5個線程來進行處理 但是真實的情況下 肯定是設計一個伸縮性的方案
new Thread(new HelperWork()).start();
new Thread(new HelperWork()).start();
new Thread(new HelperWork()).start();
new Thread(new HelperWork()).start();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Connection", "Keep-Alive");
resp.addHeader("Cache-Control", "private");
resp.addHeader("Pragma", "no-cache");
resp.setContentType("text/html;charset=utf-8");
try {
works.put(req.startAsync());
} catch (Exception e) {
}
PrintWriter writer = resp.getWriter();
writer.write("等待異步完成");
writer.flush();
}
private class HelperWork implements Runnable{
public void run() {
try {
AsyncContext ac = works.take();
//模擬業務消耗
TimeUnit.SECONDS.sleep(2L)
HttpServletRequest request = (HttpServletRequest)ac.getRequest();
Map<String, String[]> maps = request.getParameterMap();
System.out.println(maps);
HttpServletResponse response = (HttpServletResponse)ac.getResponse();
PrintWriter writer = response.getWriter();
writer.write(maps.toString());
writer.flush();
ac.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
上面只是一種思路,我們還可以放入到線程池中進行處理等等。
然後再講一下怎麼通過ajax怎麼異步的通信,我們只需要在第一次訪問servlet的時候,保留AsyncContext的引用,之後通過這個的輸出和頁面做交互就可以了。