struts2攔截器的實現原理及源碼剖析


攔截器(interceptor)是Struts2最強大的特性之一,也可以說是struts2的核心,攔截器可以讓你在Action和result被執行之前或之后進行一些處理。同時,攔截器也可以讓你將通用的代碼模塊化並作為可重用的類。Struts2中的很多特性都是由攔截器來完成的。攔截是AOP(Aspect Objected Programing:面向切面編程)的一種實現策略。在Webwork的中文文檔的解釋為:攔截器是動態攔截Action調用的對象。它提供了一種機制可以使開發者可以定義在一個action執行的前后執行的代碼,也可以在一個action執行前阻止其執行同時也是提供了一種可以提取action中可重用的部分的方式。談到攔截器,還有一個詞大家應該知道——攔截器鏈(Interceptor Chain,在Struts 2中稱為攔截器棧Interceptor Stack)。攔截器鏈就是將攔截器按一定的順序聯結成一條鏈。在訪問被攔截的方法或字段時,攔截器鏈中的攔截器就會按其之前定義的順序被調用。

一.攔截器的實現原理:

  大部分時候,攔截器方法都是通過代理的方式來調用的。Struts 2的攔截器實現相對簡單。當請求到達Struts 2的ServletDispatcher時,Struts 2會查找配置文件,並根據其配置實例化相對的攔截器對象,然后串成一個列表(list),最后一個一個地調用列表中的攔截器。事實上,我們之所以能夠如此靈活地使用攔截器,完全歸功於“動態代理”的使用。動 態代理是代理對象根據客戶的需求做出不同的處理。對於客戶來說,只要知道一個代理對象就行了。那Struts2中,攔截器是如何通過動態代理被調用的呢? 當Action請求到來的時候,會由系統的代理生成一個Action的代理對象,由這個代理對象調用Action的execute()或指定的方法,並在 struts.xml中查找與該Action對應的攔截器。如果有對應的攔截器,就在Action的方法執行前(后)調用這些攔截器;如果沒有對應的攔截 器則執行Action的方法。其中系統對於攔截器的調用,是通過ActionInvocation來實現的。代碼如下:
if (interceptors.hasNext()) {
Interceptor interceptor=(Interceptor)interceptors.next();
resultCode = interceptor.intercept(this);
} else {
if (proxy.getConfig().getMethodName() == null) {
resultCode = getAction().execute();
} else {
resultCode = invokeAction(getAction(), proxy.getConfig());
}
}

可以發現Action並沒有與攔截器發生直接關聯,而完全是代理在組織Action與攔截器協同工作。如下圖:struts2攔截器的實現原理及源碼剖析    我們只知道攔截器調用棧的最底層,是Action方法的調用,卻不知道Result的調用也是在棧底調用,之后才返回給上一個攔截器,層層退出
 

     Struts2的攔截器結構的設計,實際上是一個典型的責任鏈模式的應用。首先將整個執行划分成若干相同類型的元素,每個元素具備不同的邏輯責任,並將他們納入到一個鏈式的數據結構中(我們可以把堆棧結構也看作是一個遞歸的鏈式結構),而每個元素又有責任負責鏈式結構中下一個元素的執行調用。

這樣的設計,從代碼重構的角度來看,實際上是將一個復雜的系統,分而治之,從而使得每個部分的邏輯能夠高度重用並具備高度可擴展性。所以,Interceptor結構實在是Struts2/Xwork設計中的精華之筆。

二.攔截器執行分析

     我們大家都知道,Interceptor的接口定義沒有什么特別的地方,除了init和destory方法以外,intercept方法是實現整個攔截器 機制的核心方法。而它所依賴的參數ActionInvocation則是著名的Action調度者。我們再來看看一個典型的Interceptor的抽象 實現類:
public abstract class AroundInterceptor extends AbstractInterceptor {
//com.opensymphony.xwork2.interceptor.AbstractInterceptor#int//ercept(com.opensymphony.xwork2.ActionInvocation)
@Override

public String intercept(ActionInvocation invocation) throws Exception {
String result = null;
    before(invocation);
// 調用下一個攔截器,如果攔截器不存在,則執行Action

        result = invocation.invoke();

        after(invocation, result);


        return result;
}


public abstract void before(ActionInvocation invocation) throws Exception;
public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;
}
 在這個實現類中,實際上已經實現了最簡單的攔截器的雛形。這里需要指出的是一個很重要的方法invocation.invoke()。這是 ActionInvocation中的方法,而ActionInvocation是Action調度者,所以這個方法具備以下4層含義:

1. 如果攔截器堆棧中還有其他的Interceptor,那么invocation.invoke()將調用堆棧中下一個Interceptor的執行。
2. 如果攔截器堆棧中只有Action了,那么invocation.invoke()將調用Action執行。

3、如果最后一個攔截器中intercept()方法沒有調用invocation.invoke()方法,那么將根據intercept()方法返回的字符串,去Result中尋找相應的視圖.  (其實如果攔截沒有調用invocation.invoke方法,毋庸置疑,他就是最后一個攔截器中(共兩種)的一種了!!!!
4.由責任鏈更深層次的剖析發現:上一個攔截器intercept()方法中invocation.invoke()方法的返回值(注意:是invoke方法的返回值)都是該攔截器調用的下一個攔截器intercept()方法的返回值(注意是intercept方法的返回值)。
  特殊情況:最后一個攔截器的
intercept()方法中invocation.invoke()方法的返回值 就是它調用Action中方法的返回值。
其實如果攔截調用invocation.invoke方法,即調用了Action中的方法,毋庸置疑,他就是最后一個攔截器的第二種了!!!!
由此可見,上圖內涵很豐富呀!!!!
 
    所以,我們可以發現,invocation.invoke()這個方法其實是整個攔截器框架的實現核心。基於這樣的實現機制,我們還可以得到下面2個非常重要的推論:
1. 如果在攔截器中,我們不使用invocation.invoke()來完成堆棧中下一個元素的調用(即此攔截器就是最后一個攔截器,而且沒有調用Action),而是直接返回一個字符串,那么Struts2將根據此字符串去尋找result中相應的視圖顯示,那么整個執行將被中止。呵呵,這也是Intercept接口中intercept方法有一個返回值的原因所在!!!

2. 攔截器的分類:
  我們可以以invocation.invoke()為界,將攔截器中的代碼分成2個部分:
  1)before攔截器:在invocation.invoke()之前的代碼,將會在invocation.invoke()代碼執行之前執行,即在Action之前被依次順序執行
  2)after攔截器:在invocation.invoke()之后的代碼,
,將會在invocation.invoke()代碼執行之后執行,即在Action之后被逆序執行
   注意:
invocation.invoke()代碼執行后,也就是說:不僅執行類Action,也執行類Result。因而,等退回到攔截器的調用代碼時,Result已經生成,View已經確定,這時你再修改模型(Action的屬性)或請求對象的屬性,對視圖不會有任何影響。 

  3)PreResultListener攔截器:
    有的時候,before攔截和after攔截對我們來說是不夠的,因為我們需要在Action執行完之后,但是還沒有回到視圖層之前,做一些事情。 Struts2同樣支持這樣的攔截,這種攔截方式,是通過在攔截器中注冊一個PreResultListener的接口來實現的。
 
下面是接口示例:
    public interface PreResultListener { 
         
        void beforeResult(ActionInvocation invocation, String resultCode); 
    }

下面是攔截器注冊示例:

  
  public String intercept(ActionInvocation invocation)
        throws Exception {
        invocation.addPreResultListener(new PreResultListener() {
            public void beforeResult(ActionInvocation invocation,
                                            String
resultCode) {
                        /********** 要添加的代碼  ***********/       
            }
        });
        String returnString = invocation.invoke();
        return returnString;
    }

注意:
在addPreResultListener里的異常,不會被Struts的框架捕獲
由此,我們就可以通過invocation.invoke()作為Action代碼真正的攔截點,從而實現AOP。
   
三.源碼解析

    下面我們通過查看源碼來看看Struts2是如何保證攔截器、Action與Result三者之間的執行順序的。之前我曾經提 到,ActionInvocation是Struts2中的調度器,所以事實上,這些代碼的調度執行,是在ActionInvocation的實現類中完 成的,這里,我抽取了DefaultActionInvocation中的invoke()方法,它將向我們展示一切
// * @throws ConfigurationException If no result can be //found with the returned code
public String invoke() throws Exception {

    String profileKey = "invoke: ";

    try {

     UtilTimerStack.push(profileKey);
     if (executed) {
      throw new IllegalStateException("Action has already executed");

     }
// 依次調用攔截器堆棧中的攔截器代碼執行
    if (interceptors.hasNext()) {

     final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();

     UtilTimerStack.profile("interceptor: "+interceptor.getName(),

     new UtilTimerStack.ProfilingBlock<String>() {

public String doProfiling() throws Exception {

                         // 將ActionInvocation作為參數,調用interceptor中的intercept方法執行

     resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

     return null;

}
     });

     } else {

     resultCode = invokeActionOnly();

     }
     // this is needed because the result will be executed, then control will return to the Interceptor, which will

     // return above and flow through again

     if (!executed) {

            // 執行PreResultListener

     if (preResultListeners != null) {

     for (Iterator iterator = preResultListeners.iterator();

     iterator.hasNext();) {

     PreResultListener listener = (PreResultListener) iterator.next();
     String _profileKey="preResultListener: ";

     try {

     UtilTimerStack.push(_profileKey);

     listener.beforeResult(this, resultCode);

     }

     finally {

     UtilTimerStack.pop(_profileKey);

     }

     }

     }
   // now execute the result, if we're supposed to

            // action與interceptor執行完畢,執行Result

     if (proxy.getExecuteResult()) {

     executeResult();

     }
   executed = true;

     }
 return resultCode;

    }

    finally {

     UtilTimerStack.pop(profileKey);

    }
}
  從源碼中,我們可以看到Action層的4個不同的層次,在這個方法中都有體現,他們分別是:攔截器(Interceptor)、Action、PreResultListener和Result。在這個方法中,保證了這些層次的有序調用和執行。由此我們也可以看出Struts2在Action層次設計上的眾多考慮,每個層次都具備了高度的擴展性和插入點,使得程序員可以在任何喜歡的層次加入自己的實現機制改變Action的行為。在這里,需要特別強調的,是其中攔截器部分的執行調用:
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); 
表面上,它只是執行了攔截器中的intercept方法,如果我們結合攔截器來看,就能看出點端倪來:
  public String intercept(ActionInvocation invocation) throws Exception {  

    String result = null;  

         before(invocation);  

        // 調用invocation的invoke()方法,在這里形成了遞歸調用 

      result = invocation.invoke();  

        after(invocation, result);  

       return result;  


原來在intercept()方法又對ActionInvocation的invoke()方法進行遞歸調用,ActionInvocation循環嵌套在intercept()中,一直到語句result = invocation.invoke()執行結束。這樣,Interceptor又會按照剛開始執行的逆向順序依次執行結束。一個有序鏈表,通過遞歸調用,變成了一個堆棧執行過程,將一段有序執行的代碼變成了2段執行順序完全相反的代碼過程,從而巧妙地實現了AOP。這也就成為了Struts2的Action層的AOP基礎。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM