天天看點

Servlet3.0新特性-異步處理Servlet、Filter

一、淺談Servlet

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

Servlet3.0新特性-異步處理Servlet、Filter

  其中黃色階段通常是最耗時的,因為業務處理一般涉及資料庫操作,還會受到網絡等的影響,而在此過程中,Servlet 線程一直處于阻塞狀态,直到業務處理完畢。在處理業務的過程中,Servlet 資源一直被占用而得不到釋放,對于并發較大的應用,這有可能造成性能的瓶頸。對此,在以前通常是采用私有解決方案來提前結束 Servlet 線程,并及時釋放資源。

為解決這個問題,Servlet 3.0 就開始支援異步處理了,這與 Ajax 異步不一樣,之前的 Servlet 處理流程可以調整為如下的過程:

Servlet3.0新特性-異步處理Servlet、Filter

  首先,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 頁面,結果如下:

Servlet3.0新特性-異步處理Servlet、Filter

  然後我們通路servlet:http://localhost:8080/Test2/asyn-servlet,在頁面顯示的是

Servlet3.0新特性-異步處理Servlet、Filter

  然後我們看一下控制台列印的結果:

Servlet3.0新特性-異步處理Servlet、Filter

  最後我們再回過去看一下 show.jsp 頁面

Servlet3.0新特性-異步處理Servlet、Filter

三、對異步處理過程的監聽

  除此之外,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
			}
			
		});