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

lytao123發表於2018-09-23

一、淺談Servlet

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

Servlet 的主要工作流程  其中黃色階段通常是最耗時的,因為業務處理一般涉及資料庫操作,還會受到網路等的影響,而在此過程中,Servlet 執行緒一直處於阻塞狀態,直到業務處理完畢。在處理業務的過程中,Servlet 資源一直被佔用而得不到釋放,對於併發較大的應用,這有可能造成效能的瓶頸。對此,在以前通常是採用私有解決方案來提前結束 Servlet 執行緒,並及時釋放資源。
為解決這個問題,Servlet 3.0 就開始支援非同步處理了,這與 Ajax 非同步不一樣,之前的 Servlet 處理流程可以調整為如下的過程:
Servlet3.0 處理流程
  首先,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,在頁面顯示的是資訊已傳送到郵箱
  然後我們看一下控制檯列印的結果:
先列印出servlet的內容再列印非同步執行緒的內容
  最後我們再回過去看一下 show.jsp 頁面
郵箱內容:This is the result of asyn

三、對非同步處理過程的監聽

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

相關文章