08 06Struts 2.x攔截器

攔截器是現代開發之中最爲重要的特色,它是基於AOP的設計思想(AOP是基於代理設計思想)面向切面編程的設計思想實現的。

在Struts 2.x裏面爲了方便用戶進行數據的驗證,專門提供有validate()方法以及驗證框架,但是這兩個驗證操作都有一個最致命的問題——永遠都要在其賦值完成之後才能夠驗證,賦值之前無法驗證,所以後臺會一直出現錯誤。爲了解決這樣的問題,或者說爲了解決所有輔助性的檢測的功能,那麼可以利用攔截器完成。

1 認識攔截器

在Struts 2.x裏面所有的客戶端發送來的請求都交給Filter進行處理,而後由Filter再去決定執行哪個Action。而攔截器是在執行某一個Action之前做的數據攔截操作。

實際上在之前也有攔截器出現,表單參數自動變爲VO類型對象,這個就是一個攔截器的功能。
範例:定義一個Action,手工配置一個攔截器

package org.lks.action;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class MessageAction extends ActionSupport {
	public void insert(){
		System.out.println("*********** insert");
	}
}

範例:配置一個計算處理事件的攔截器

<package name="root" namespace="/" extends="struts-default">
	<action name="MessageAction" class="org.lks.action.MessageAction">
		<interceptor-ref name="timer"></interceptor-ref>
	</action>
</package>

此時會計算出當前操作的執行時間,而後臺的輸出效果如下:

此時攔截器自動執行了,但是timer這個攔截器默認是在Action之後執行。

2 開發自定義攔截器

如果要想開發自定義的攔截器,那麼在Struts 2.x裏面是有要求的,這個攔截器的類必須繼承自com.opensymphony.xwork2.interceptor.AbstractInterceptor父類。這個類是一個抽象類。

public abstract class AbstractInterceptor
extends Object
implements Interceptor

在這個類之中存在有三個方法,但是有一個抽象方法:
(1)攔截:public abstract String intercept(ActionInvocation invocation) throws Exception
這個方法的參數裏面接收了一個ActionInvocation的對象,這個類對象可以取得一切的發送及執行信息。
範例:實現一個最基礎的攔截器

package org.lks.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

@SuppressWarnings("serial")
public class MyInterceptor extends AbstractInterceptor {

	@Override
	public void init() {
		System.out.println("====================== 攔截器初始化 ===================");
	}
	
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		System.out.println("====================== 攔截器執行 ===================");
		return invocation.invoke();  //將請求向下傳遞
	}
	
	@Override
	public void destroy() {
		System.out.println("====================== 攔截器銷燬 ===================");
	}

}

攔截器定義完成固然是一個好事,但是攔截器必須在struts.xml文件中進行配置。
範例:配置struts.xml文件

<interceptors>
	<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
</interceptors>
<action name="MessageAction" class="org.lks.action.MessageAction">
	<interceptor-ref name="timer"/>
	<interceptor-ref name="lks"/>
</action>

此時在MessageAction執行的時候掛了兩個攔截器。此時執行效果如下:

此時的攔截器是在Action執行之前進行了處理,等於是說當前已經攔截下來了請求。
範例:現在在MessageAction中定義News類型進行數據接收

private News news = new News();
	
public News getNews() {
	return news;
}

發現一旦編寫了自定義的攔截器之後,非常遺憾的問題出現了,自動賦值的操作不會出現了。因爲如果在Struts 2.x裏面你沒有使用攔截器,那麼會自動使用賦值的攔截器,而一旦你使用了攔截器,那麼就需要自己手工來配置攔截器。
範例:修改配置

<action name="MessageAction" class="org.lks.action.MessageAction">
	<interceptor-ref name="timer"/>
	<interceptor-ref name="lks"/>
	<interceptor-ref name="defaultStack"></interceptor-ref>
</action>

如果開始編寫自定義攔截器,所有的操作必須採用手工的模式進行。

3 利用攔截器檢測登錄信息

如果要進行登錄檢測,那麼肯定使用過濾器是最合適的,不過遺憾的是Struts 2.x(WebWork)爲了與Struts 1.x做區別所以使用了過濾器進行了整個的分發處理。那麼自己寫的過濾器自然就無法進行session的登錄檢查。如果要想實現登錄檢查,那麼只能夠依靠攔截器完成,也就是說現在必須要對ActionInvocation做進一步的深入研究。

這是一個接口com.opensymphony.xwork2.ActionInvocation。在這個接口中本次主要使用兩個操作:
(1)請求繼續向下傳遞給Action:public String invoke() throws Exception
(2)取得上下文對象內容:public ActionContext getInvocationContext()
使用getInvocationContext()方法返回的是一個com.opensymphony.xwork2.ActionContext類的對象,這個類包含有如下的方法:
(1)取得全部的參數:public Map<String,Object> getParameters()
|————Map集合中中的key爲參數名稱、value爲參數內容;
(2)取得全部Session保存的屬性數據:public Map<String,Object> getSession()
|————Map集合中的key爲session屬性名稱、value爲session屬性的內容;
(3)取得全部Application保存的屬性數據:``
|————|————Map集合中的key爲application屬性名稱、value爲application屬性的內容;
範例:假設登錄的session屬性名稱爲mid,所以本次驗證如下:

package org.lks.interceptor;

import java.util.Map;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

@SuppressWarnings("serial")
public class LoginInterceptor extends AbstractInterceptor {
	
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		//取得所有的session屬性
		Map<String,Object> map = invocation.getInvocationContext().getSession();
		if(map.get("mid") != null){ //登錄過了,在session設置屬性了
			return invocation.invoke();  //正常訪問
		}else{  //如果現在沒有session屬性,那麼表示無法操作
			ServletActionContext.getRequest().setAttribute("msg", "未登錄,請先登錄!");
			ServletActionContext.getRequest().setAttribute("url", "login.jsp");
			return "forward.page"; //全局跳轉提示頁面
		}
	}
}

但是現在使用了一個forward.page的頁面,對於這個頁面需要提醒注意,幾乎所有的開發之中都需要使用一個統一的信息提示頁,那麼可以將其定義爲全局跳轉資源。
範例:修改struts.xml文件,配置攔截器使用

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<package name="root" namespace="/" extends="struts-default">
		<interceptors>
			<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
			<interceptor name="login" class="org.lks.interceptor.LoginInterceptor"></interceptor>
		</interceptors>
		<global-results>
			<result name="forward.page">forward.jsp</result>
		</global-results>
		<action name="MessageAction" class="org.lks.action.MessageAction">
			<interceptor-ref name="timer"/>
			<interceptor-ref name="lks"/>
			<interceptor-ref name="login"/>
			<interceptor-ref name="defaultStack"></interceptor-ref>
		</action>
	</package>
</struts>    

此時已經徹底實現了攔截器的實際使用。

所有的攔截器都會按照既定的順序依次向下執行,這一點的控制是非常方便的,因爲很多時候一定是先進行登錄驗證而後在進行其它操作。

4 實現服務器端數據驗證

對於數據驗證的操作一定是分爲兩端進行,一端是客戶端驗證(雖然無用,但是必須提供),另外一端是基於服務端的應用,但是所有的Struts 2.x的操作有一點很麻煩,給出的驗證方法或者是驗證框架都必須在數據已經轉換爲VO類對象後纔可以正常執行驗證,而攔截器使用之後,發現可以在攔截其中針對數據進行驗證處理。

如果要想進行驗證,,那麼必須滿足於如下的幾點:
(1)可以爲每一個Action設置指定的驗證規則,例如:現在編寫一個NewsAction的驗證規則,那麼這個類裏面要接收的參數名稱:news.nid、news.ntitle、news.ncontent、news.npubdate,那麼很明顯沒一種數據都有自己的類型。
範例:在NewsAction.java類裏面定義規則

private String insertRule = "news.nid:int|news.ntitle:string|news.ncontent:string|news.npubdate:date";
public String insert(){
	System.out.println(this.news);
	return "news.show";
}

(1)可以驗證的數據類型最多隻有兩大類:數組、普通類型,但是不管如何劃分,常見類型:String、int、double、date。
(2)但是這個規則現在是保存在每一個Action裏面的,而攔截器需要知道這個Action,這個時候就需要觀察攔截器方法裏面所具備的參數:ActionInvocation,在這個接口裏面定義有如下操作方法:
(1)取得要操作的Action類對象:public Object getAction()

Object actionObject = invocation.getAction();

此時取得了整個的Action對象,那麼就意味着可以利用反射進行這個類屬性或者是方法的調用。
(1)實際上取得了NewsAction類對象後,就必須要想辦法取出一個適合於我們自己的操作類型。
範例:取得驗證規則


@Override
public String intercept(ActionInvocation invocation) throws Exception {
	//取得要操作的Action,根據發送的提交路徑不同,Action對象也不同
	Object actionObject = invocation.getAction();
	//爲了可以確定要使用的驗證操作成員,必須從裏面取出分發的操作代碼
	String uri = ServletActionContext.getRequest().getRequestURI();
	if(uri != null){ //爲了保險起見,可以再增加一個null的驗證
		//取出關鍵的業務方法的名稱,那麼就相當於取得了“業務方法名稱Rule”的驗證規則
		uri = uri.substring(uri.lastIndexOf('!') + 1, uri.lastIndexOf('.'));
		//有了Action對象,又有了名稱,就可以取得成員了
		String fileName = uri + "Rule"; //驗證規則的成員
		//根據成員名稱取得對象的具體內容,那麼只能夠依靠反射完成
		Field fieldRule = actionObject.getClass().getDeclaredField("fieldName");
		fieldRule.setAccessible(true);//取消掉封裝處理
		String rule = (String) fieldRule.get(actionObject);
		System.out.println("[Rule]" + rule);
	}
	return invocation.invoke();
}

(2)所有的驗證規則必須要和具體的輸入參數進行捆綁纔是最有用的。爲了方便代碼的維護,專門實現一個驗證的具體操作類,這個類只是負責驗證,那麼這個類裏面不需要保存任何的屬性內容,那麼很明顯,使用全部的static方法就可以了。
(3)在Struts 2.x中爲了省事,將所有的參數取得都不再去區分getParameter()或者是getParameterValues(),而是統一按照了getParameterValues()方法取得,也就是說所有取得的內容都是字符串數組。

public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
		//需要知道有哪些參數
		Iterator<Entry<String,Object>> iter = params.entrySet().iterator();
		while(iter.hasNext()){
			Entry<String,Object> entry = iter.next();
			String[] str = (String[]) entry.getValue();
			System.out.println(entry.getKey() + " : " + Arrays.toString(str));
		}
		return false;
	}

所有的參數輸出是沒有任何意義的,因爲必須要針對於我們給出的參數進行驗證。

/**
 * 進行數據驗證的方法
 * @param actionObject 表示要觸發此操作的Action類
 * @param rule 每個Action類裏面定義的規則
 * @param pamars 表示所有的輸入參數
 * @return 驗證成功返回true,驗證失敗返回false
 */
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
	//所有的驗證操作都應該由rule發起,裏面的組成“參數名稱:類型”
	String[] result = rule.split("\\|"); //取出每一組驗證操作
	for(int i = 0; i < result.length; i++){ //表示此處循環每一個驗證
		String temp[] = result[i].split(":"); //取出參數名稱以及驗證規則
		String[] paramValue = (String[]) params.get(temp[0]);
		for(int j = 0; j < paramValue.length; j++){ //循環每一個數組內容
			switch(temp[1]){
			case "string":{
				System.out.println(temp[0] + "string validate");
				break;	
			}
			case "int":{
				System.out.println(temp[0] + "int validate");
				break;	
			}
			case "double":{
				System.out.println(temp[0] + "double validate");
				break;
			}
			case "date":{
				System.out.println(temp[0] + "date validate");
				break;
			}
			default:{
				break;
			}
			}
		}
	}
	return false;
}

(4)隨後在每一種規則中進行具體的驗證操作。

package org.lks.util.interceptor;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class ValidateUtil {

	/**
	 * 進行數據驗證的方法
	 * @param actionObject 表示要觸發此操作的Action類
	 * @param rule 每個Action類裏面定義的規則
	 * @param pamars 表示所有的輸入參數
	 * @return 驗證成功返回true,驗證失敗返回false
	 */
	public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
		//所有的驗證操作都應該由rule發起,裏面的組成“參數名稱:類型”
		String[] result = rule.split("\\|"); //取出每一組驗證操作
		for(int i = 0; i < result.length; i++){ //表示此處循環每一個驗證
			String temp[] = result[i].split(":"); //取出參數名稱以及驗證規則
			String[] paramValue = (String[]) params.get(temp[0]);
			for(int j = 0; j < paramValue.length; j++){ //循環每一個數組內容
				switch(temp[1]){
				case "string":{
					if(ValidateUtil.validateString(paramValue[j])){
						System.out.println(temp[0] + "string validate pass");
					}
					break;	
				}
				case "int":{
					if(ValidateUtil.validateInt(paramValue[j])){
						System.out.println(temp[0] + "int validate pass");
					}
					break;	
				}
				case "double":{
					if(ValidateUtil.validateDouble(paramValue[j])){
						System.out.println(temp[0] + "double validate pass");
					}
					break;
				}
				case "date":{
					if(ValidateUtil.validateDate(paramValue[j])){
						System.out.println(temp[0] + "date validate pass");
					}
					break;
				}
				default:{
					break;
				}
				}
			}
		}
		return false;
	}
	
	/**
	 * 進行字符串的操作驗證
	 * @param str 要驗證的內容
	 * @return 如果字符串爲空或者長度爲0,那麼返回false,否則返回true
	 */
	public static boolean validateString(String str){
		if(str == null || "".equals(str)){
			return false;
		}
		return true;
	}
	
	/**
	 * 進行數字的操作驗證,驗證之前首先要判斷數據是否爲空
	 * @param str 要驗證的內容
	 * @return 如果是由數字所組成返回true,否則返回false
	 */
	public static boolean validateInt(String str){
		if(validateString(str)){ //首先判斷數據是否合法
			return str.matches("\\d+");
		}else{
			return false;
		}
	}
	
	/**
	 * 進行浮點數的驗證操作,驗證之前要先判斷數據是否爲空
	 * @param str 要驗證的內容
	 * @return 如果字符串由小數所組成返回true,否則返回false
	 */
	public static boolean validateDouble(String str){
		if(validateString(str)){ //首先判斷數據是否合法
			return str.matches("\\d+(\\.\\d+)?");
		}else{
			return false;
		}
	}
	
	/**
	 * 驗證字符串是否是日期類型或者是日期時間類型
	 * @param str 要驗證的內容
	 * @return 如果字符串是日期或者日期時間返回true,否則返回false
	 */
	public static boolean validateDate(String str){
		if(validateString(str)){ //首先判斷數據是否合法
			return str.matches("\\d{4}-\\d{2}-\\d{2}") || str.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
		}else{
			return false;
		}
	}
}

信息都在攔截器中存在了,可以一旦驗證失敗的話,那麼怎麼進行返回呢?

所有的錯誤信息實際上都會在前臺以fieldErrors形式進行保存,而且保存的都是Map集合,這一切都要源自於addFiledError()這個方法的使用,但是同時還需要定義好錯誤信息內容。
範例:定義一個Messages.properties文件,在這個文件裏面編寫錯誤提示信息

string.validate.error.msg=數據不允許爲空!
number.validate.error.msg=數據格式錯誤!
date.validate.error.msg=數據格式錯誤!(示例:yyyy-MM-dd)
/**
 * 進行數據驗證的方法
 * @param actionObject 表示要觸發此操作的Action類
 * @param rule 每個Action類裏面定義的規則
 * @param pamars 表示所有的輸入參數
 * @return 驗證成功返回true,驗證失敗返回false
 */
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
	boolean flag = true;
	try {
		//取得增加錯誤信息的操作方法,通過此方法保存錯誤信息
		Method addFieldErrorMethod = actionObject.getClass().getMethod("addFieldError", String.class, String.class);
		//通過此方法取得錯誤的提示信息
		Method getTextMethod = actionObject.getClass().getMethod("getText", String.class);
		//所有的驗證操作都應該由rule發起,裏面的組成“參數名稱:類型”
		String[] result = rule.split("\\|"); //取出每一組驗證操作
		String text = ""; //保存每一組錯誤信息
		for(int i = 0; i < result.length; i++){ //表示此處循環每一個驗證
			String temp[] = result[i].split(":"); //取出參數名稱以及驗證規則
			String[] paramValue = (String[]) params.get(temp[0]);
			for(int j = 0; j < paramValue.length; j++){ //循環每一個數組內容
				switch(temp[1]){
				case "string":{
					if(!ValidateUtil.validateString(paramValue[j])){
						text = (String) getTextMethod.invoke(actionObject, "string.validate.error.msg");
					}
					break;	
				}
				case "int":{
					if(!ValidateUtil.validateInt(paramValue[j])){
						text = (String) getTextMethod.invoke(actionObject, "number.validate.error.msg");
					}
					break;	
				}
				case "double":{
					if(!ValidateUtil.validateDouble(paramValue[j])){
						text = (String) getTextMethod.invoke(actionObject, "number.validate.error.msg");
					}
					break;
				}
				case "date":{
					if(!ValidateUtil.validateDate(paramValue[j])){
						text = (String) getTextMethod.invoke(actionObject, "date.validate.error.msg");
					}
					break;
				}
				default:{
					break;
				}
				}
				if(!("".equals(text))){
					addFieldErrorMethod.invoke(actionObject, temp[0], text);
					flag = false;
				}
			}
		}
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return flag;
}

以最基礎的CRUD爲主,如果是增加出現了問題,那麼應該返回到增加的錯誤頁上,如果要是修改出現了問題,應該返回到修改的錯誤頁,同樣,依次類推…所有的錯誤頁都應該在struts.xml文件裏面進行配置。爲每一個驗證失敗定義一個錯誤頁,例如:insert業務驗證規則是insertRule,那麼它的錯誤的跳轉頁面就定義爲:insertVF。

<result name="insertVF">news_insert.jsp</result>

如果這個類完成了,那麼以後服務器端的驗證,只需要將攔截器配置上。而後在需要的地方編寫驗證規則,在struts.xml文件裏面編寫驗證失敗的跳轉,那麼就可以實現服務器端的操作驗證了。

5 定義攔截器棧

正如在之前所編寫的代碼一樣,可以發現,在一個項目裏面至少需要以下幾種攔截器:驗證攔截、登錄攔截、defaultStack攔截,可是如果每一次都這樣分別去寫:

<action name="MessageAction" class="org.lks.action.MessageAction">
	<interceptor-ref name="timer"/>
	<interceptor-ref name="lks"/>
	<interceptor-ref name="login"/>
	<interceptor-ref name="defaultStack"></interceptor-ref>
</action>

如果說要同時編寫多個這樣的攔截器引用,以上的代碼就明顯感覺到重複了,而且也不方便管理。所以爲了方便的進行多個攔截器的操作管理,可以直接定義一個攔截器棧,在這個攔截器棧中可以引用多個攔截器。
範例:定義攔截器棧

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<package name="root" namespace="/" extends="struts-default">
		<interceptors>
			<interceptor-stack name="lksStack">
				<interceptor-ref name="timer"/>
				<interceptor-ref name="lks"/>
				<interceptor-ref name="login"/>
				<interceptor-ref name="defaultStack"/>
			</interceptor-stack>
			<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
			<interceptor name="login" class="org.lks.interceptor.LoginInterceptor"></interceptor>
		</interceptors>
		<global-results>
			<result name="forward.page">forward.jsp</result>
		</global-results>
		<action name="MessageAction" class="org.lks.action.MessageAction">
			<interceptor-ref name="lksStack" />
		</action>
	</package>
</struts>    

這種的操作形式是以後在開發之中使用最多的一種形式的結構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章