一、淺談Servlet
在Servlet 3.0 之前,一個普通 Servlet 的主要工作流程大緻如下:
其中黃色階段通常是最耗時的,因為業務處理一般涉及資料庫操作,還會受到網絡等的影響,而在此過程中,Servlet 線程一直處于阻塞狀态,直到業務處理完畢。在處理業務的過程中,Servlet 資源一直被占用而得不到釋放,對于并發較大的應用,這有可能造成性能的瓶頸。對此,在以前通常是采用私有解決方案來提前結束 Servlet 線程,并及時釋放資源。
為解決這個問題,Servlet 3.0 就開始支援異步處理了,這與 Ajax 異步不一樣,之前的 Servlet 處理流程可以調整為如下的過程:
首先,Servlet 接收到請求之後,可能首先需要對請求攜帶的資料進行一些預處理;接着,Servlet 線程将請求轉交給一個異步線程來執行業務處理,線程本身傳回至容器,此時 Servlet 還沒有生成響應資料,異步線程處理完業務以後,可以直接生成響應資料(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用),或者将請求繼續轉發給其它 Servlet。如此一來, Servlet 線程不再是一直處于阻塞狀态以等待業務邏輯的處理,而是啟動異步線程之後可以立即傳回。
二、啟用異步處理
異步處理特性可以應用于 Servlet 和過濾器兩種元件。在預設情況下,Servlet 和過濾器并沒有開啟異步處理特性,因為異步處理的工作模式和普通工作模式在實作上有着本質的差別,是以如果希望使用該特性,則必須按照如下的方式啟用:
1. 在 web.xml 檔案中啟動
Servlet3.0 預設是沒有 web.xml 檔案的,但 Servlet3.0 也是支援 web.xml 檔案的,較 Servlet之前的版本,Servlet 3.0 在
<servlet>
和
<filter>
标簽中增加了
<async-supported>
子标簽,該标簽預設是 false 。如果想啟用異步支援,隻需要置為 true 即可。例如:
<!-- servlet -->
<servlet>
<servlet-name>asynServlet</servlet-name>
<servlet-class>com.servlet.AsynServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
<!-- Filter -->
<filter>
<filter-name>asynFilter</filter-name>
<filter-class>com.filter.AsynFilter</filter-class>
<async-supported>true</async-supported>
</filter>
2. 通過注解開啟異步支援
Servlet 3.0 提供的 @WebServlet 和 @WebFilter 進行 Servlet 或 Filter 配置的情況,這兩個注解都提供了 asyncSupported 屬性,預設該屬性的取值為 false。如果想啟用異步支援,隻需要置為 true 即可。例如:
@WebServlet(value="/asyn-servlet",asyncSupported=true)
public class ServletAsyn extends HttpServlet {...}
@WebFilter(value="/*",asyncSupported=true)
public class FilterAsyn implements Filter {...}
三、異步處理的代碼實作方式
Servlet 和 Filter 使用異步支援的實作方式是一模一樣的操作。這裡隻以 Servlet 的實作方式為例。
1. 模拟注冊發資訊到郵件的簡單的異步處理 Servlet 示例代碼:
package com.servlet;
import java.io.IOException;
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(value="/asyn-servlet",asyncSupported=true)
public class ServletAsyn extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Servlet is start");
//1.獲得異步上下文對象
AsyncContext ac = request.startAsync();
//2.啟動一個耗時的子線程
ThreadTask tt = new ThreadTask(ac);
//3.可設定異步逾時對象,需在啟動異步上下文對象前設定
/*
* 設定逾時後,在逾時時間内子線程沒有結束,主線程則會停止等待,繼續往下執行
*/
ac.setTimeout(3000);
//4.開啟異步上下文對象
ac.start(tt);
//主線程結束向用戶端發送消息
System.out.println("Servlet is end");
response.setContentType("text/html;charset=utf-8");
response.getWriter().append("資訊已發送到郵箱");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
異步線程的執行類:
package com.servlet;
import java.util.Date;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class ThreadTask implements Runnable{
private AsyncContext ac; //定義一個異步上下文
public ThreadTask(AsyncContext ac) {
super();
this.ac = ac;
}
@Override
public void run() {
/*
* 服務端異步典型應用是注冊時向郵箱發送驗證碼
*/
try {
//進行異步的一些處理
HttpServletRequest requst = (HttpServletRequest) ac.getRequest();
HttpSession session = requst.getSession();
System.out.println("asyn-task start" + new Date());
for(int i=5;i>0; i--) {
System.out.println(i);
Thread.sleep(1000);
}
//将結果放到session等方式
session.setAttribute("message", "This is the result of asyn");
System.out.println("asyn-task end" + new Date());
//通知主線程已經處理完成
/*
* 除了使用 ac.complete() 方法通知主線程已經處理外
* 還可以使用 ac.dispatch() 方法重定向到一個頁面
*/
ac.dispatch("/show.jsp");
} catch (Exception e) {
e.printStackTrace();
}
}
}
這裡的 show.jsp 模拟為指定郵箱。show.jsp頁面body标簽内代碼為:
<p>郵箱内容:${message }</p>
2. 模拟注冊發資訊到郵件的簡單的異步處理 Servlet 測試過程
先通路 show.jsp 頁面,結果如下:
然後我們通路servlet:http://localhost:8080/Test2/asyn-servlet,在頁面顯示的是
然後我們看一下控制台列印的結果:
最後我們再回過去看一下 show.jsp 頁面
三、對異步處理過程的監聽
除此之外,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 ac = request.startAsync();
ac.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void onError(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void onStartAsync(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void onTimeout(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
}
});