Servlet 異步處理


web容器會為每個請求分配一個線程,Servlet3.0新增了異步處理,解決多個線程不釋放占據內存的問題。可以先釋放容器分配給請求的線程與相關資源,減輕系統負擔,原先釋放了容器所分配線程的請求,其響應將被延后,可以在處理完成后再對客戶端進行響應。

一、AsyncContex簡介

    為了支持異步處理,在ServletRequest上提供了startAsync()方法。可以通過AsyncContext的getRequest()和getResponse()方法取得請求、響應對象,此次對客戶端的響應將暫緩至調用AsyncContext的complete()或dispatch()方法為止。

    首先要告知此容器支持Servlet異步處理,如:

  1: @WebServlet(urlPatterns="/some.do", asyncSupported = true)
  2: public class AsyncServlet extends HttpServlet{
  3: 
  4: }

    例1:異步處理的例子

AsyncServlet.java

  1: package ServletAPI;
  2: 
  3: import java.io.IOException;
  4: import java.util.concurrent.ExecutorService;
  5: import java.util.concurrent.Executors;
  6: import javax.servlet.AsyncContext;
  7: import javax.servlet.ServletException;
  8: import javax.servlet.annotation.WebServlet;
  9: import javax.servlet.http.HttpServlet;
 10: import javax.servlet.http.HttpServletRequest;
 11: import javax.servlet.http.HttpServletResponse;
 12: 
 13: /**
 14:  * Servlet implementation class AsyncServlet
 15:  */
 16: @WebServlet(name = "AsyncServlet", urlPatterns = { "/async.do" },asyncSupported=true)
 17: public class AsyncServlet extends HttpServlet {
 18:     private static final long serialVersionUID = 1L;
 19:     private ExecutorService executorService=Executors.newFixedThreadPool(10);   
 20:     /**
 21:      * @see HttpServlet#HttpServlet()
 22:      */
 23:     public AsyncServlet() {
 24:         super();
 25:         // TODO Auto-generated constructor stub
 26:     }
 27: 
 28:     /**
 29:      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 30:      */
 31:     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 32:       // TODO Auto-generated method stub
 33:       response.setContentType("text/html;charset=UTF-8");
 34:       AsyncContext ctx=request.startAsync();//開始異步處理,釋放請求線程
 35:       executorService.submit(new AsynvRequest(ctx)); //創建AsyncRequest,調度線程
 36:     
 37:     }
 38: 
 39:     /**
 40:      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 41:      */
 42:     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 43:       // TODO Auto-generated method stub
 44:     }
 45:     public void destroy(){
 46:       executorService.shutdown();//關閉線程池
 47:     }
 48: 
 49: }
 50: 

    首先告訴容器,這個Servlet支持異步處理,對於每個請求,Servlet會取得其AsyncContext,並釋放容器所分配的線程,響應被延遲后。對於這些被延遲后響應的請求,創建一個實現Runnable接口的AsyncRequest對象,並將其調度一個固定數量的線程池,讓這些必須長時間處理的請求,在線程池中完成,不用每次分配線程。

    例2:AsyncRequest是個實現Runnable的類,其模擬了長時間處理。

AsyncRequest.java

  1: package ServletAPI;
  2: 
  3: import java.io.PrintWriter;
  4: import javax.servlet.AsyncContext;
  5: public class AsynvRequest implements Runnable{
  6:   private AsyncContext ctx;
  7: 
  8:   public AsynvRequest(AsyncContext ctx) {
  9:     super();
 10:     this.ctx = ctx;
 11:   }
 12: 
 13:   @Override
 14:   public void run() {
 15:     // TODO Auto-generated method stub
 16:     try {
 17:       Thread.sleep(10000);//模擬冗長請求
 18:       PrintWriter out=ctx.getResponse().getWriter();
 19:       out.println("久等了...XD");//輸出結果
 20:       ctx.complete();//對客戶端完成響應
 21:     } catch (Exception e) {
 22:       // TODO Auto-generated catch block
 23:       throw new RuntimeException(e);
 24:     }
 25:     
 26:   }
 27:   
 28: }
 29: 

    以暫停線程的方式來模擬長時間處理,並輸出簡單的文字,最后調用complete()對客戶端完成響應。

二、模擬服務器推播

    HTTP是基於請求、響應模型,如果客戶端要獲得服務器的最新狀態,就必須以定期方式發送請求,查詢服務器端的最新狀態。

    Servlet 3.0提供的異步處理技術,可以解決每個請求占用線程的問題,再結合Ajax異步請求技術,就可以達到類似服務器主動通知瀏覽器的行為。這就是所謂的服務器端推播。

    例3:模擬應用程序不定期產生最新數據,這個部分由實現ServletContextListener的類負責,會在程序啟動時進行。

WebInitListener.java

  1: package ServletAPI;
  2: 
  3: import java.util.ArrayList;
  4: import java.util.List;
  5: import javax.servlet.AsyncContext;
  6: import javax.servlet.ServletContextEvent;
  7: import javax.servlet.ServletContextListener;
  8: import javax.servlet.annotation.WebListener
  9: 
 10: /**
 11:  * Application Lifecycle Listener implementation class WebInitListener
 12:  *
 13:  */
 14: @WebListener
 15: public class WebInitListener implements ServletContextListener {
 16:     private List<AsyncContext> asyncs=new ArrayList<>();//所有的異步請求AsyncContext將存儲在這個List中。
 17: 
 18:     public void contextDestroyed(ServletContextEvent arg0) {
 19:         // TODO Auto-generated method stub
 20:     }
 21: 
 22:   /**
 23:      * @see ServletContextListener#contextInitialized(ServletContextEvent)
 24:      */
 25:     public void contextInitialized(ServletContextEvent arg0) {
 26:         // TODO Auto-generated method stub
 27:       new Thread(new Runnable(){
 28:         public void run(){
 29:           while(true){
 30:             try {//模擬產生隨機數字
 31:             Thread.sleep((int)(Math.random()*10000));
 32:             double num=Math.random()*10;
 33:             synchronized (asyncs) {
 34:               for(AsyncContext ctx:asyncs){
 35:                 ctx.getResponse().getWriter().println(num);
 36:                 ctx.complete();
 37:               }
 38:             }
 39:           } catch (Exception e) {
 40:             // TODO Auto-generated catch block
 41:             throw new RuntimeException();
 42:           }
 43:           }
 44:         }
 45:       }).start();
 46:     }
 47:   
 48: }
 49: 

    有個List會存儲所有的異步請求的AsyncContext,並在不定時產生數字后,逐一對客戶端響應,並調用AsyncContext的conmplete()來完成請求。

    負責接收請求的Servlet,一收到請求,就將之加入到List中。

AsyncNumServlet.java

  1: package ServletAPI;
  2: 
  3: import java.io.IOException;
  4: import java.util.List;
  5: import javax.servlet.AsyncContext;
  6: import javax.servlet.ServletException;
  7: import javax.servlet.annotation.WebServlet;
  8: import javax.servlet.http.HttpServlet;
  9: import javax.servlet.http.HttpServletRequest;
 10: import javax.servlet.http.HttpServletResponse;
 11: 
 12: @WebServlet(name = "AsyncNumServlet", urlPatterns = { "/asyncNum.do" }, asyncSupported=true)
 13: public class AsyncNumServlet extends HttpServlet {
 14:     private static final long serialVersionUID = 1L;
 15:     private List<AsyncContext> asyncs; 
 16: 
 17:     public void init() throws ServletException{
 18:       asyncs=(List<AsyncContext>)getServletContext().getAttribute("asyncs");
 19:       
 20:     }
 21:     /**
 22:      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 23:      */
 24:     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 25:       // TODO Auto-generated method stub
 26:       AsyncContext ctx=request.startAsync();//開始異步處理
 27:       synchronized (asyncs) {
 28:         asyncs.add(ctx);//加入維護AsyncContext的List中
 29:       }
 30:     }
 31: 
 32: }
 33: 

    由於List是儲存為ServletContext屬性,所以在Servlet中,必須從ServletContext中取出,每次請求到來時,調用HttpServletRequest的startAsync()進行異步處理,並取得AsyncContext加入維護AsyncContext的List中。

    可以使用一個簡單的HTML,使用Ajax技術,發送異步請求值服務器端,這個請求會被延遲,直到服務器端完成響應后,更新網頁上的資料,並再度發送異步請求:

async.html

  1: <!DOCTYPE html>
  2: <html>
  3: <head>
  4: <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5: <title>實時資料</title>
  6: <script>
  7:   function asyncUpdate(){
  8:     var xhr;
  9:     if(window.XMLHttpRequest){
 10:       xhr=new XMLHttpRequest();
 11:     }else
 12:     if(window.ActiveXObject){
 13:       xhr=new ActiveXObject('Microsoft.XMLHTTP');
 14:     }
 15:     xhr.onreadystatechange=function(){
 16:       if(xhr.readyState==4){
 17:         if(xhr.status==200){
 18:           document.getElementById("data").innerHTML=xhr.responseText;
 19:           asyncUpdate();
 20:         }
 21:       }
 22:     };
 23:     xhr.open('GET','asyncNum.do?timestamp='+new Date().getTime());
 24:     xhr.send(null);
 25:   }
 26:   window.onload=asyncUpdate;
 27: </script>
 28: </head>
 29: <body>
 30:   實時資料:<span id="data">0</span>
 31: </body>
 32: </html>

    可以試着用多個瀏覽器請求這個頁面,會看到每個瀏覽器的資料是同步的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM