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) 方法;
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;
- }*/
- }
- }
- }
