攔截器是現代開發之中最爲重要的特色,它是基於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>
這種的操作形式是以後在開發之中使用最多的一種形式的結構。