天天看點

servlet3.0 新特性——異步處理

Servlet 3.0 之前,一個普通 Servlet 的主要工作流程大緻如下:

首先,Servlet 接收到請求之後,可能需要對請求攜帶的資料進行一些預處理;

接着,調用業務接口的某些方法,以完成業務處理;

最後,根據處理的結果送出響應,Servlet 線程結束。

其中第二步的業務處理通常是最耗時的,這主要展現在資料庫操作,以及其它的跨網絡調用等,在此過程中,Servlet 線程一直處于阻塞狀态,直到業務方法執行完畢。在處理業務的過程中,Servlet 資源一直被占用而得不到釋放,對于并發較大的應用,這有可能造成性能的瓶頸。即使在業務類中開啟一個線程,線程處理後的結果是無法傳回給頁面的,因為servlet執行完畢後,response就關閉了,無法将背景更新資料即時更新到頁面端

注:servlet3.0 以前通常是采用私有解決方案來提前結束 Servlet 線程,并及時釋放資源。

Servlet 3.0 針對這個問題做了開創性的工作,現在通過使用 Servlet 3.0 的異步處理支援,之前的 Servlet 處理流程可以調整為如下的過程:

首先,Servlet 接收到請求之後,可能首先需要對請求攜帶的資料進行一些預處理;

接着,Servlet 線程将請求轉交給一個異步線程來執行業務處理,線程本身傳回至容器,此時 Servlet 還沒有生成響應資料,異步線程處理完業務以後,可以直接生成響應資料(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用),或者将請求繼續轉發給其它 Servlet。

如此一來, Servlet 線程不再是一直處于阻塞狀态以等待業務邏輯的處理,而是啟動異步線程之後可以立即傳回。

1、異步處理特性可以應用于 Servlet 和過濾器兩種元件,由于異步處理的工作模式和普通工作模式在實作上有着本質的差別,是以預設情況下,Servlet 和過濾器并沒有開啟異步處理特性,如果希望使用該特性,則必須按照如下的方式啟用:

  1. 對于使用傳統的部署描述檔案 (web.xml) 配置 Servlet 和過濾器的情況,Servlet 3.0 為 <servlet> 和 <filter> 标簽增加了 <async-supported> 子标簽,該标簽的預設取值為 false,要啟用異步處理支援,則将其設為 true 即可。以 Servlet 為例,其配置方式如下所示:

    <servlet>

    <servlet-name>DemoServlet</servlet-name>

    <servlet-class>footmark.servlet.Demo Servlet</servlet-class>

    <async-supported>true</async-supported>

    </servlet>

  2. 對于使用 Servlet 3.0 提供的 @WebServlet 和 @WebFilter 進行 Servlet 或過濾器配置的情況,這兩個注解都提供了 asyncSupported 屬性,預設該屬性的取值為 false,要啟用異步處理支援,隻需将該屬性設定為 true 即可。以 @WebFilter 為例,其配置方式如下所示:
@WebFilter(urlPatterns = "/demo",asyncSupported = true) 
public class DemoFilter implements Filter{...}      

2、Servlet 3.0 還為異步處理提供了一個監聽器,使用 AsyncListener 接口表示。它可以監控如下四種事件:

  1. 異步線程開始時,調用 AsyncListener 的 onStartAsync(AsyncEvent event) 方法;
  2. 異步線程出錯時,調用 AsyncListener 的 onError(AsyncEvent event) 方法;
  3. 異步線程執行逾時,則調用 AsyncListener 的 onTimeout(AsyncEvent event) 方法;
  4. 異步執行完畢時,調用 AsyncListener 的 onComplete(AsyncEvent event) 方法;

要注冊一個 AsyncListener,隻需将準備好的 AsyncListener 對象傳遞給 AsyncContext 對象的 addListener() 方法即可,如下所示:

AsyncContext ctx = req.startAsync();

ctx.addListener(new AsyncListener() {

public void onComplete(AsyncEvent asyncEvent) throws IOException {

// 做一些清理工作或者其他

}

...

});

執行個體:

package com.darren.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * servlet3.0預設是不支援異步的通過asyncSupported=true,打開
 * 
 */
@WebServlet(name="SecondServlet",urlPatterns={"/secondServlet"},asyncSupported=true)
public class SecondServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    PrintWriter out = null;
    
    resp.setContentType("text/html");
    try {
      out = resp.getWriter();
      out.print("servlets starts:"+new Date()+"<br>");
      out.flush();
      
      AsyncContext asyncContext = req.startAsync();
      
      
    
      /**
       * AsyncListener為什麼沒有擴充卡呢?需要各個廠家實作?
       */
      asyncContext.addListener(new AsyncListener(){

        public void onComplete(AsyncEvent asyncEvent) throws IOException {
          //将流在這裡關閉
          asyncEvent.getSuppliedResponse().getWriter().close();
          System.out.println("asynContext finished....");
        }

        public void onError(AsyncEvent arg0) throws IOException {
          // TODO Auto-generated method stub
          
        }

        public void onStartAsync(AsyncEvent arg0) throws IOException {
          // TODO Auto-generated method stub
          
        }

        public void onTimeout(AsyncEvent arg0) throws IOException {
          // TODO Auto-generated method stub
          
        }
        
      });
      
      
      
      new Thread(new MyThread(asyncContext)).start();
      
      out.print("servlets ends:"+new Date()+"<br>");
      out.flush();
      
    } finally {
      
      /**
       * 一開是在這裡關閉了,關了,後面就用不成了 :)
       * 
       */
      
      /*if(null != out){
        out.close();
        out = null;
      }*/
    }
    
  }
}      

MyThread.java:

package com.darren.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.AsyncContext;

public class MyThread implements Runnable {

  private AsyncContext asyncContext;
  
  
  public MyThread(AsyncContext asyncContext) {
    this.asyncContext = asyncContext;
  }


  public void run() {
    
    PrintWriter out = null;
    
    try {
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      out = asyncContext.getResponse().getWriter();
      out.println("myTask starts:"+new Date()+"<br>");
      out.flush();
      
      
      out.print("myTask ends:"+new Date()+"<br>");
      out.flush();
      asyncContext.complete();
      
    } catch (IOException e) {
      e.printStackTrace();
    }finally{
      
      /*if(null != out){
        out.close();
        out = null;
      }*/
    }
  }

}