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的引用,之後通過這個的輸出和頁面做交互就可以了。