程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Servlet3.0的異步,Servlet3.0異步

Servlet3.0的異步,Servlet3.0異步

編輯:JAVA綜合教程

Servlet3.0的異步,Servlet3.0異步


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

 

 

 









  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved