servlet以前的操做同時同步的,就是按照這樣的一個流程來走的:html
1.請求根據一個路徑路由到一個servlet中,java
2.servlet獲取一系列的參數web
3.執行一系列的邏輯(花費時間所佔的比重也更大)ajax
4.返回結果異步
上面的問題出如今這一系列的操做都是同步的,因此這個請求一定是堵塞到因此任務都完成以後才返回的,async
這樣將會很浪費資源,由於線程堵塞在那裏,僅僅是等待任務的完成。可是在servlet3.0以後,咱們基本上能夠ide
是這樣作的測試
1.請求根據一個路徑路由到一個servlet中,url
2.將邏輯放入到異步隊列中去spa
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的引用,以後經過這個的輸出和頁面作交互就能夠了。