Servlet3.0的異步

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的引用,以後經過這個的輸出和頁面作交互就能夠了。

相關文章
相關標籤/搜索