攔截器是Struts2的一個重要特性。Struts2框架的大多數核心功能都是通過攔截器來實現的,像避免表單重複提交、類型轉換、對象組裝、驗證、文件上傳等,都是在攔截器的幫助下實現的。
攔截器的一個重要特徵是:它可以在Action之前調用。
攔截器,在AOP(Aspect Oriented Programming)中用於在某個方法或字段被訪問之前,進行攔截。然後再之前或之後加入某些操作。攔截器是AOP的一種實現策略。
Struts2的攔截原理:
當請求到達Struts2的ServletDispatcher時,Struts2會查找相對應的配置信息,並實例化攔截器對象。串成一個列表(list),最後一個一個地調用列表中的攔截器。
- 配置攔截器
在struts.xml文件中定義攔截器只需爲攔截器指定一個攔截器名稱即可。使用<interceptor.../>元素來定義攔截器,最簡單的格式如下。
<interceptor name="攔截器名" class="攔截器類"></interceptor>
有時,按照上面的配置便可以完成一個簡單的攔截器,但是,如果還需要配置攔截器時傳入攔截器參數,則需要在<interceptor.../>元素中使用<param.../>子元素。下面是在配置攔截器時,同時傳入攔截器參數的配置形式。
<interceptor name="攔截器名" class="攔截器類">
<!-- 下面元素可以出現0次,也可以出現無數次,其中name屬性指定需要設置的參數名 -->
<param name="參數名">參數值</param>
</interceptor>
- 配置攔截器棧
struts2還支持把多個攔截器連在一起組成一個攔截器棧,比如:需要在Action執行前同時進行安全檢查,身份驗證,數據校驗等等操作,可以將這些動作鏈接成一個攔截器棧。
攔截器棧的定義形式:
<interceptor-stack name="攔截器棧名">
<interceptor-ref name="攔截器一"></interceptor-ref>
<interceptor-ref name="攔截器二"></interceptor-ref>
</interceptor-stack>
攔截器和攔截器棧的功能是一樣的,只不過攔截器棧定義了一組攔截器。
當然我們也可以在攔截器中定義攔截器棧,這樣都是可以的,主要是實現了代碼的複用。比如:
<interceptor name="a1" class="com.yao.SInterceptor">
</interceptor>
<interceptor name="a2" class="com.yao.DInterceptor">
</interceptor>
<interceptor name="a3" class="com.yao.Interceptor"></interceptor>
<interceptor-stack name="a4">
<interceptor-ref name="a1">
<param name="st">abc</param>
</interceptor-ref>
<interceptor-ref name="a2">
</interceptor-ref>
</interceptor-stack>
- 使用攔截器
攔截器在<package>目錄下聲明好了以後,下一步就是在Action中使用攔截器了
示例代碼如下:
<action name="user" class="com.yao.action.UserAction">
<result name="user">/user.jsp</result>
<result name="add_user">/add_user.jsp</result>
<interceptor-ref name="myfilter">
<param name="name">攔截器</param>
<!-- 指定execute方法不需要被攔截 -->
<param name="excludeMethods">execute</param>
</interceptor-ref>
</action>
通過<interceptor標籤直接引用攔截器。
如果是使用攔截器棧,那示例代碼如下:
<action name="ge">
<result name="success">/success.jsp</result>
<interceptor-ref name="a1"></interceptor-ref>
<!-- 定義使用的攔截器棧,和使用攔截器沒有什麼區別,都是通過interceptor-ref標籤完成 -->
<interceptor-ref name="a4"></interceptor-ref>
</action>
- 攔截器剖析
- 實現自定義攔截器類
- 實現自定義攔截器類
import com.opensymphony.xwork2.ActionInvocation;
public interface Interceptor {
//撤銷該攔截器之前的回調方法
void destory();
//初始化該攔截器的回調方法
void init();
//攔截器實現攔截的邏輯方法
String intercept(ActionInvocation invocation) throws Exception;
}
通過接口可以看出,該接口裏有三種方法。
init():攔截器初始化之後,在該攔截器執行攔截之前,系統將回調該方法,對於每一個攔截器而言,該init()方法只執行一次。因此,該方法的方法體主要用戶打開一些一次性資源,例如數據庫資源等。
destory():該方法與init()方法對應。在攔截實例被銷燬之前,系統將回調該攔截器的destory()方法,該方法用於銷燬在init()方法裏打開的資源。
interceptor(ActionInvocation invocation):該方法是用戶需要實現的攔截動作。就像Action執行execute()方法一樣,interceptor會返回一個字符串作爲邏輯視圖。如果該方法直接返回了一個字符串,系統將跳轉到該邏輯視圖隊形的實際的視圖資源,不會調用被攔截的Action類。該方法的ActionInvocation參數保護那了被攔截的Action的引用,可以通過調用該參數的invoke()方法,將控制權轉給下一個攔截器,或者轉給Action的execute方法。
除此之外,Struts2提供了一個AbstractInterceptor類,該類提供了空的init()和destory()方法的實現,也就是如果攔截器不需要申請資源,則可以無須實現這兩個方法。
注意:
當實現了intercept(ActionInvocation invocation)方法時,可以獲得ActionInvocation參賽,這個參數又可以獲得被攔截的Action實例,一旦取得了Action實例,幾乎得到了全部的控制權。
實例剖析:
通過上面的瞭解,我們來做一個攔截器的實例
需求:客戶端用戶名密碼登陸,攔截器對其進行攔截,如果用戶名和密碼滿足條件,進入執行execute方法,如果不滿足要求,返回index.jsp頁面
建立相應的index.jsp頁面
<%@ page language="java" import="java.util.*" pageEncoding="GB18030"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<s:form action="los" method="post">
<s:textfield label="請輸入用戶名" name="username"></s:textfield>
<s:password label="請輸入密碼" name="userpass"></s:password>
<s:submit/>
</s:form>
</body>
</html>
定義Action類
Action中定義了接受前臺兩個參數的屬性
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport {
private String username;
private String userpass;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpass() {
return userpass;
}
public void setUserpass(String userpass) {
this.userpass = userpass;
}
public String execute() throws Exception{
return SUCCESS;
}
}
該攔截器類繼承了AbstractInterceptor類,可以不寫init()和destory()方法,但是必須實現intercept(ActionInvocation invocation)方法
當攔截到內容後,系統首先打印出"攔截器開始工作......"
由於invocation中已經存在Action對象,所以按照我們說的得Action者得天下原則,爲Action中的兩個屬性username ,userpass賦值。
這裏存在一個很重要的問題:如果不爲Action的兩個元素賦值,那Action類不像我們以前沒有攔截器認識的一樣,Action中的username和userpass將不會在自動取得前臺index.jsp頁面傳送過來的值,即使滿足條件,如果Action類中還需要對username和userpass兩個屬性做進一步處理的話,username和userpass將會是空值。
解決辦法:
首先必須明白的是在提交後,Struts2將會自動調用LoginAction動作類中的setUsername方法,並將username文本框中的值通過setUserame方法的參數傳入。實際上,這個操作是由params攔截器完成的,params對應的類是com.opensymphony.xwork2.interceptor.ParametersInterceptor。由於params已經在defaultStack中定義,因此,在未引用攔截器的< action>中是會自動引用params的。
但是,如果引用了自定義攔截器,那我們就要現實的在Action中引用params攔截器。
如果滿足用戶名和密碼是user 和123,則調用invocation.invoke()方法,該方法的作用是調用下一個攔截器或Action(如果是最後一個攔截器了,則調用Action);
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.zhuxuli.action.LoginAction;
public class LoInterceptor extends AbstractInterceptor{
public String intercept(ActionInvocation invocation) throws Exception{
System.out.println("攔截器開始工作.....");
HttpServletRequest request=ServletActionContext.getRequest();
LoginAction action=(LoginAction)invocation.getAction();
action.setUsername(request.getParameter("username"));
action.setUserpass(request.getParameter("userpass"));
if(action.getUsername().equals("user")&&action.getUserpass().equals("123")){
String result=invocation.invoke();
System.out.println("result="+result);
return result;
}else{
return "input";
}
}
}
然後配置struts.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
"http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
<constant name="struts.i18n.encoding" value="gbk" />
<constant name="struts.devMode" value="true" />
<package name="zxl" extends="struts-default">
<interceptors>
<interceptor name="a1" class="com.zhuxuli.Iterceptors.LoInterceptor">
</interceptor>
</interceptors>
<action name="los" class="com.yao.action.LoginAction">
<result name="input">/index.jsp</result>
<result name="success">/success.jsp</result>
<interceptor-ref name="a1"></interceptor-ref>
</action>
</package>
</struts>
實例運行後,如果用戶名和密碼不是user和123,頁面返回index.jsp頁面繼續輸入,滿足條件,則返回爲success.jsp頁面。
必須注意的是:
在執行返回success.jsp頁面的時候,你會看到控制檯打印出了相應的語句,也就是下面的代碼執行了
System.out.println("result="+result);
return result;
也就是說,通過invocation調用invoke()方法執行Action後,Action執行完畢後,又返回到String result=invocation.invoke()代碼下面繼續執行,直到執行結束,也就說明了攔截器的攔截過程爲:如下圖。
說明:首先調用攔截器1,滿足要求,則通過Invocation.invoke()方法調用下一個攔截器或Action,因爲這裏有下一個攔截器,所以直接調用攔截器2,如果還是滿足條件,則繼續向下調用,知道返回結果,返回結果後,注意的是,還會繼續回到攔截器2執行未完成的代碼,執行完後,繼續執行攔截器1未完成的代碼,直到最後返回結果。
- Struct2常用的預定義攔截器
1:params攔截器
這個攔截器是必不可少的,因爲就是由它把請求參數設置到相應的Action的屬性去的,並自動進行類型轉換。
2:staticParams攔截器
將struts.xml配置文件裏定義的Action參數,設置到對應的Action實例中,Action參數使用<param>標籤,是<action>標籤的子元素。
struts.xml的示例如下:
<action name="helloworldAction" class="cn.javass.action.action.HelloWorldAction">
<param name="account">test</param>
</action>
這要求Action中一定要有一個account的屬性,並有相應的getter/setter方法。運行的時候,Action的account屬性在初始化過後,會接到這裏的賦值“test”。
注意:params攔截器和staticParams攔截器都會爲Action的屬性賦值,如果碰到了都要賦同一個值呢,比如request裏面有account參數,而struts.xml中也有account參數,最終的值是誰?
其實是Action初始化過後,就會把struts.xml中配置的數據設置到Action實例中相應的屬性上去。然後,把用戶請求的數據設置到Action實例中相應的屬性上去。
很明顯最後的值是用戶請求中account的數據。
3:prepare攔截器
在Action執行之前調用Action的prepare()方法,這個方法是用來準備Action執行之前要做的工作。它要求我們的Action必需實現com.opensymphony.xwork2.Preparable接口
4:modelDriven攔截器
如果Action實現ModelDriven接口,它將getModel()取得的模型對象存入OgnlValueStack中。
5:chain攔截器
將前一個執行結束的Action屬性設置到當前的Action中。它被用在ResultType爲“chain”所指定的結果的Action中,該結果Action對象會從值棧中獲得前一個Action對應的屬性,它實現Action鏈之間的數據傳遞。
6:execption攔截器
在拋出異常的時候,這個攔截器起作用。它是我們第五章講的Struts2的錯誤處理機制(<exception-mapping>)的基礎,任何應用都應該引用這個攔截器,而且引用的時候,最好把它放在第一位,讓它能捕獲所有的異常。
7:validation攔截器
調用驗證框架讀取 *-validation.xml文件,並且應用在這些文件中聲明的校驗。
8:token攔截器
覈對當前Action請求(request)的有效標識,防止重複提交Action請求 。使用標籤<s:token>可以生成表單令牌,該標籤會在session中設置一個預期的值並且在表單中創建一個隱藏的input字段。Token攔截器會檢查這個令牌,如果不合法,將不會執行action,注意這個攔截器需要手工添加,還需要配置一個invalid.token的result。
9:tokenSession攔截器
擴展了token攔截器的功能,當提交無效的Action請求標識時,它會跳轉回到第一次成功後的頁面。
10:conversionError攔截器
用來處理框架進行類型轉化(Type Conversion)時的出錯信息。它將存儲在ActionContext中的類型轉化(Type Conversion)錯誤信息轉化成相應的Action字段的錯誤信息,保存在堆棧中。根據需要,可以將這些錯誤信息在視圖中顯示出來。
11:fileUpload攔截器
用來處理文件上傳。
12:workflow攔截器
Action默認的工作流,如果action實現了Validateable接口,那麼interceptor會調用action的validate()方法;如果action實現了ValidationAware接口,那麼interceptor將會檢查action是否包含錯誤信息。如果包含任何錯誤信息,那麼interceptor將會返回input,而不讓action執行。
13:servletConfig攔截器
這個攔截器提供Action直接對Servlet API的訪問,把Servlet API的對象注入到Action中。包括:ServletRequestAware、ServletResponseAware、ParameterAware、SessionAware、ApplicationAware。
14:timer攔截器
記錄ActionInvocation餘下部分執行的時間,並做爲日誌信息記錄下來,便於尋找性能瓶頸。
15:logger攔截器
在日誌信息中輸出要執行的Action信息 ,這樣,在調試的時候,就能很快的定位到這個對應的Action了。