攔截器是AOP(Aspect-Oriented Programming 面向方面編程)中的一種實現策略。
1 用代理模式與動態代理編寫攔截器
所謂Dynamic Proxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,
然後該class就宣稱它實現了這些interface。你當然可以把該class的實例當作這些interface中的任何一個來用。
當然啦,這個Dynamic Proxy其實就是一個Proxy,它不會替你作實質性的工作,
在生成它的實例時你必須提供一個handler,由它接管實際的工作
那麼如何實現動態代理呢
1 寫一個動態代理,implements InvocationHandler接口,實現方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(sub,args);
return null;
}
2 調用 ,
真實實現類名 xxx = new 真實實現類名(); // 在這裏指定被代理類
InvocationHandler ds = new DynamicSubject(xxx); // 初始化代理類
接口 接口變量= (接口) Proxy.newProxyInstance(xxx.getClass().getClassLoader(), xxx.getClass().getInterfaces(), ds);
接口變量.方法
/**
* 從以上可以看出,動態代理可以任意指定被代理的類,即真實對象類,而代理模式則無法實現該功能,
* 因爲他把真實對象的寫死在代理類裏,如果要按照上述的方法使用代理模式,那麼真實角色必須是事先已經存在的,並將其作爲代理對象的內部屬性。但是
* 實際使用時,一個真實角色或其接口必須對應一個代理角色,如果大量使用會導致類的急劇膨脹
*/
2 struts2攔截器
解壓strut2的核心包,根目錄下struts-default.xml中有struts2的攔截器配置
攔截器幾乎完成了Struts2框架70%的工作,包括解析請求參數、將請求參數賦值給Action屬性、執行數據校驗、
文件上傳……,Struts2設計的靈巧性,更大程度地得益於攔截器設計,當需要擴展Struts2功能時,只需要提供對應攔截器,
並將它配置在Struts2容器中即可;如果不需要該功能時,也只需要取消該攔截器的配置即可。
這種可插拔式的設計,正是軟件設計領域一直孜孜以求的目標。
實際上,Struts2的精髓就在於攔截器,掌握了Struts2的攔截器機制,你就可以說精通了Struts2。
從某個角度來看,我們可以把Struts2框架理解成一個空殼,而這些攔截器像一個一個抽屜,隨時可以
插進入,也可以拔出來——這是軟件產品一直追求的目標。
如果你喜歡,你可以把Struts2的全部插件拔出,那麼Struts2就成了一個空容器——
而這種空,正是 Struts2的魅力,你可以把任何自己想要的東西填入進去,甚至包括自己完全實現這個框架。
另一方面,因爲Struts2的插件機制,Struts2提供了無限擴展的可能性,你可以把自己想要的任何
東西做成插件,然後填入Struts2——這樣的結果是:一個企業,一個團隊,可以把自己業務相關的東西
做成插件,隨時隨地地複用。
也就是說:如果你想要,你可以把Struts2改造成屬於自己的框架。
當然,Struts2也內建了大量的攔截器,這些攔截器以name-class對的形式配置在struts-default. xml文件中,其中name是攔截器的名字,就是以後使用該攔截器的唯一標識;class則指定了該攔截器的實現類,如果我們定義的package繼承了Struts2的默認struts-default包,則可以自由使用下面定義的攔截器,否則必須自己定義這些攔截器。
下面是Struts2內建攔截器的簡要介紹:
alias:實現在不同請求中相似參數別名的轉換。
autowiring:這是個自動裝配的攔截器,主要用於當Struts2和Spring整合時,Struts2可以使用自動裝配的方式來訪問Spring容器中的Bean。
chain:構建一個Action鏈,使當前Action可以訪問前一個Action的屬性,一般和<result type="chain" .../>一起使用。
conversionError:這是一個負責處理類型轉換錯誤的攔截器,它負責將類型轉換錯誤從ActionContext中取出,並轉換成Action的FieldError錯誤。
createSession:該攔截器負責創建一個HttpSession對象,主要用於那些需要有HttpSession對象才能正常工作的攔截器中。
debugging:當使用Struts2的開發模式時,這個攔截器會提供更多的調試信息。
execAndWait:後臺執行Action,負責將等待畫面發送給用戶。
exception:這個攔截器負責處理異常,它將異常映射爲結果。
fileUpload:這個攔截器主要用於文件上傳,它負責解析表單中文件域的內容。
i18n:這是支持國際化的攔截器,它負責把所選的語言、區域放入用戶Session中。
logger:這是一個負責日誌記錄的攔截器,主要是輸出Action的名字。
model-driven:這是一個用於模型驅動的攔截器,當某個Action類實現了ModelDriven接口時,它負責把getModel()方法的結果堆入ValueStack中。
scoped-model-driven:如果一個Action實現了一個ScopedModelDriven接口,該攔截器負責從指定生存範圍中找出指定的Modol,並將通過setModel方法將該Model傳給Action實例。
params:這是最基本的一個攔截器,它負責解析HTTP請求中的參數,並將參數值設置成Action對應的屬性值。
prepare:如果action實現了Preparable接口,將會調用該攔截器的prepare()方法。
static-params:這個攔截器負責將xml中<action>標籤下<param>標籤中的參數傳入action。
scope:這是範圍轉換攔截器,它可以將Action狀態信息保存到HttpSession範圍,或者保存到ServletContext範圍內。
servlet-config:如果某個Action需要直接訪問Servlet API,就是通過這個攔截器實現的。
注意:儘量避免在Action中直接訪問Servlet API,這樣會導致Action與Servlet的高耦合。
roles:這是一個JAAS(Java Authentication and Authorization Service,Java授權和認證服務)攔截器,只有當瀏覽者取得合適的授權後,纔可以調用被該攔截器攔截的Action。
timer:這個攔截器負責輸出Action的執行時間,這個攔截器在分析該Action的性能瓶頸時比較有用。
token:這個攔截器主要用於阻止重複提交,它檢查傳到Action中的token,從而防止多次提交。
token-session:這個攔截器的作用與前一個基本類似,只是它把token保存在HttpSession中。
validation:通過執行在xxxAction-validation.xml中定義的校驗器,從而完成數據校驗。
workflow:這個攔截器負責調用Action類中的validate方法,如果校驗失敗,則返回input的邏輯視圖。
大部分時候,開發者無需手動控制這些攔截器,因爲struts-default.xml文件中已經配置了這些攔截器,只要我們定義的包繼承了系統的struts-default包,就可以直接使用這些攔截器。
當然,Struts2的攔截器機制並不是來自於Struts1,而是來自於webWork。
3 自定義攔截器
(1)編寫攔截器類,繼承AbstractInterceptor類 重寫intercept(ActionInvocation arg0)方法
調用用參數類ActionInvocation的invoke方法,即 String result= arg0.invoke(); 返回該result=
invoke就是回調使用了該攔截器的action得相應方法,此時可在該方法執行前後加入我們想要的代碼,達到我們攔截action的目的
利用 arg0.getAction()方法還可以得到攔截器攔截的action實例
public String intercept(ActionInvocation arg0) throws Exception {
// LoginAction loginaction=LoginAction(arg0.getAction());
System.out.println("執行ction之前");
String result= arg0.invoke();
System.out.println("執行ction之後");
return result;
}
、(2)在struts.xml配置攔截器
<package name="default" extends="struts-default" namespace="/">
<!--配置攔截器 -->
<interceptors>
<interceptor name="攔截器名" class="包名.攔截器類名">
<!--設置攔截器類屬性值,如沒有則不用設置 -->
<param name="攔截器類屬性名">屬性值</param>
</interceptor>
</interceptors>
<action name="login" class="com.LoginAction" >
<result name="success">/welcome.jsp</result>
<result name="error">/error.jsp</result>
<interceptor-ref name="defaultStack"></interceptor-ref><!--struts-default包默認的攔截器棧 -->
<!--使用攔截器 -->
<interceptor-ref name="攔截器名"></interceptor-ref>
</action>
</package>
多個攔截器在一起組成一個攔截器棧
<interceptor-stack name="攔截器棧">
<interceptor-ref name="攔截器名1"/>
<interceptor-ref name="攔截器名2"/>
<interceptor-ref name="攔截器名3"/>
</interceptor-stack>
比如:<package name="default" extends="struts-default">
<interceptors>
<interceptor name="MyInterceptor" class="com.lxitedu.test.InterceptorCustom"></interceptor>
</interceptors>
<action name="register" class="com.lxitedu.test.Register">
<interceptor-ref name="MyInterceptor"></interceptor-ref>
<interceptor-ref name="defaultStack" />
<result name="fail">/index.jsp </result>
<result name="success">/success.jsp</result>
</action>
</package>
當配置一個包時,可以爲其指定默認的攔截器,每個包只能有一個默認的攔截器,一旦爲某個包指定了默認的攔截器,
如果該包中的action沒有指定自己的攔截器,則action使用包指定的默認攔截器,但是一旦爲action指定了自己攔截器,
則包的默認攔截器將會失效,如果還想使用原來包的默認攔截器,則必須顯示的指定,如
<action name="login" class="com.LoginAction" >
<interceptor-ref name="defaultStack"></interceptor-ref><!--struts-default包默認的攔截器棧 -->
<!--使用我們的攔截器 -->
<interceptor-ref name="攔截器名"></interceptor-ref>
</action>
我們的包繼承另一個包時,也繼承了另一個包的默認攔截器,以及包裏面的其他如(<result-types>)
當然我們可以定義自己包的默認攔截器覆蓋之
從這裏可以解釋爲什麼我們剛剛開始學struts2的時候老是extends="struts-default"
因爲struts-default包中的默認攔截器以及result-types是個好東東
如果不繼承它,將失去result-types以及裏面定義的攔截器功能,可以試着extends="struts-default"去掉,此時如果struts.xml裏有
result的配置的話,則tomcat啓動的時候No result type specified for result named 。。。。。錯誤。我們可以把裏面的 result配置去掉
重新啓動tomcat,去測試頁面屬性值是否能夠封裝到action屬性中,答案不會 因爲我們沒有繼承struts-default
配置默認攔截器 在 包package中 <default-interceptor-ref name="攔截器名或攔截器棧"></default-interceptor-ref> 放後面,action配置的前面
上面攔截器會攔截action中的所有方法 要想攔截某個方法怎麼辦 攔截指定方法
編寫攔截器類,extends MethodFilterInterceptor MethodFilterInterceptor是AbstractInterceptor的子類
重寫doIntercept方法
public String doIntercept(ActionInvocation invocation)
throws Exception
{
// LoginAction loginaction=LoginAction(arg0.getAction());
System.out.println("執行action方法之前");
String result= arg0.invoke();
System.out.println("執行action方法之後");
return result;
}
使用配置指定攔截的方法
<!-- 攔截器一般配置在result元素之後! -->
<interceptor-ref name="攔截器名1">
<param name="excludeMethods">execute,haha</param> <!-- 不攔截! -->
<param name="includeMethods">execute</param> <!-- 攔截! -->
</interceptor-ref>
可以重複使用一個攔截器或配置多個攔截器與攔截器棧,攔截器的執行順序是在方法執行前,先配先執行,在方法執行後,後配先執行
攔截結果的監聽 是在action結束後,返回result之前的一個監聽器,可以在該監聽器裏寫我們的代碼,
以便在返回結果前執行,
這個監聽器通過手動註冊在攔截器內部的
監聽器示例代碼 implements PreResultListener
public class MyPreResultListener implements PreResultListener
{
public void beforeResult(ActionInvocation invocation,String resultCode)
{
System.out.println("返回的邏輯視圖爲:" + resultCode);
}
}
攔截器示例代碼
public class BeforeResultInterceptor extends AbstractInterceptor
{
public String intercept(ActionInvocation invocation) throws Exception
{
invocation.addPreResultListener(new MyPreResultListener()); //註冊監聽器
System.out.println("execute方法執行之前的攔截...");
String result = invocation.invoke();
System.out.println("execute方法執行之後的攔截......");
return result;
}
}
監聽器代碼在action結束後,返回結果前執行,和在action結束後在攔截器內部寫的代碼相比, 監聽器代碼把action結束後的代碼
放到監聽器似乎更精確和清晰些
給攔截器棧傳遞參數時會出現一個問題,當攔截器棧中攔截器類中的屬性名相同時,不知道這個參數到底要傳給那個攔截器,爲了
解決這個問題,可作如下配置
<interceptor-ref name="攔截器棧名">
<param name="攔截器名(不是類名,注意哦).屬性名">屬性值</param>
</interceptor-ref>