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 和過濾器并沒有開啟異步處理特性,如果希望使用該特性,則必須按照如下的方式啟用:
-
對于使用傳統的部署描述檔案 (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>
- 對于使用 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 接口表示。它可以監控如下四種事件:
- 異步線程開始時,調用 AsyncListener 的 onStartAsync(AsyncEvent event) 方法;
- 異步線程出錯時,調用 AsyncListener 的 onError(AsyncEvent event) 方法;
- 異步線程執行逾時,則調用 AsyncListener 的 onTimeout(AsyncEvent event) 方法;
- 異步執行完畢時,調用 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;
}*/
}
}
}