攔截器實現原理


        攔截器(interceptor)是Struts2最強大的特性之一,也可以說是struts2的核心,攔截器可以讓你在Action和result被執行之前或之后進行一些處理。同時,攔截器也可以讓你將通用的代碼模塊化並作為可重用的類。Struts2中的很多特性都是由攔截器來完成的。攔截是AOP的一種實現策略。在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來實現的。代碼如下:

[java]  view plain copy print ?
 
  1. if (interceptors.hasNext()) {  
  2. Interceptor interceptor=(Interceptor)interceptors.next();  
  3. resultCode = interceptor.intercept(this);  
  4. else {  
  5. if (proxy.getConfig().getMethodName() == null) {  
  6. resultCode = getAction().execute();  
  7. else {  
  8. resultCode = invokeAction(getAction(), proxy.getConfig());  
  9. }  
  10. }  


可以發現Action並沒有與攔截器發生直接關聯,而完全是“代理”在組織Action與攔截器協同工作。如下圖:

                                                              

 

二.攔截器執行分析

     我們大家都知道,Interceptor的接口定義沒有什么特別的地方,除了init和destory方法以外,intercept方法是實現整個攔截器機制的核心方法。而它所依賴的參數ActionInvocation則是著名的Action調度者。我們再來看看一個典型的Interceptor的抽象實現類:

[java]  view plain copy print ?
 
  1. public abstract class AroundInterceptor extends AbstractInterceptor {  
  2.   
  3.   
  4. /* (non-Javadoc) 
  5.  
  6.  * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation) 
  7.  
  8.  */  
  9.   
  10. @Override  
  11.   
  12. public String intercept(ActionInvocation invocation) throws Exception {  
  13.   
  14. String result = null;  
  15.   
  16.   
  17.         before(invocation);  
  18.   
  19.         // 調用下一個攔截器,如果攔截器不存在,則執行Action  
  20.   
  21.         result = invocation.invoke();  
  22.   
  23.         after(invocation, result);  
  24.   
  25.   
  26.         return result;  
  27.   
  28. }  
  29.   
  30.   
  31. public abstract void before(ActionInvocation invocation) throws Exception;  
  32.   
  33.   
  34. public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;  
  35.   
  36.   
  37. }  


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

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


    所以,我們可以發現,invocation.invoke()這個方法其實是整個攔截器框架的實現核心。基於這樣的實現機制,我們還可以得到下面2個非常重要的推論:
1. 如果在攔截器中,我們不使用invocation.invoke()來完成堆棧中下一個元素的調用,而是直接返回一個字符串作為執行結果,那么整個執行將被中止。
2. 我們可以以invocation.invoke()為界,將攔截器中的代碼分成2個部分,在invocation.invoke()之前的代碼,將會在Action之前被依次執行,而在invocation.invoke()之后的代碼,將會在Action之后被逆序執行。
由此,我們就可以通過invocation.invoke()作為Action代碼真正的攔截點,從而實現AOP。


三.源碼解析


          下面我們通過查看源碼來看看Struts2是如何保證攔截器、Action與Result三者之間的執行順序的。之前我曾經提到,ActionInvocation是Struts2中的調度器,所以事實上,這些代碼的調度執行,是在ActionInvocation的實現類中完成的,這里,我抽取了DefaultActionInvocation中的invoke()方法,它將向我們展示一切。


 

  1. /** 
  2.  
  3.  * @throws ConfigurationException If no result can be found with the returned code 
  4.  
  5.  */  
  6.   
  7. public String invoke() throws Exception {  
  8.   
  9.     String profileKey = "invoke: ";  
  10.   
  11.     try {  
  12.   
  13.      UtilTimerStack.push(profileKey);  
  14.   
  15.        
  16.   
  17.      if (executed) {  
  18.   
  19.      throw new IllegalStateException("Action has already executed");  
  20.   
  21.      }  
  22.   
  23.         // 依次調用攔截器堆棧中的攔截器代碼執行  
  24.   
  25.      if (interceptors.hasNext()) {  
  26.   
  27.      final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();  
  28.   
  29.      UtilTimerStack.profile("interceptor: "+interceptor.getName(),   
  30.   
  31.      new UtilTimerStack.ProfilingBlock<String>() {  
  32.   
  33. public String doProfiling() throws Exception {  
  34.   
  35.                          // 將ActionInvocation作為參數,調用interceptor中的intercept方法執行  
  36.   
  37.      resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);  
  38.   
  39.      return null;  
  40.   
  41. }  
  42.   
  43.      });  
  44.   
  45.      } else {  
  46.   
  47.      resultCode = invokeActionOnly();  
  48.   
  49.      }  
  50.   
  51.   
  52.      // this is needed because the result will be executed, then control will return to the Interceptor, which will  
  53.   
  54.      // return above and flow through again  
  55.   
  56.      if (!executed) {  
  57.   
  58.             // 執行PreResultListener  
  59.   
  60.      if (preResultListeners != null) {  
  61.   
  62.      for (Iterator iterator = preResultListeners.iterator();  
  63.   
  64.      iterator.hasNext();) {  
  65.   
  66.      PreResultListener listener = (PreResultListener) iterator.next();  
  67.   
  68.        
  69.   
  70.      String _profileKey="preResultListener: ";  
  71.   
  72.      try {  
  73.   
  74.      UtilTimerStack.push(_profileKey);  
  75.   
  76.      listener.beforeResult(this, resultCode);  
  77.   
  78.      }  
  79.   
  80.      finally {  
  81.   
  82.      UtilTimerStack.pop(_profileKey);  
  83.   
  84.      }  
  85.   
  86.      }  
  87.   
  88.      }  
  89.   
  90.   
  91.      // now execute the result, if we're supposed to  
  92.   
  93.             // action與interceptor執行完畢,執行Result  
  94.   
  95.      if (proxy.getExecuteResult()) {  
  96.   
  97.      executeResult();  
  98.   
  99.      }  
  100.   
  101.   
  102.      executed = true;  
  103.   
  104.      }  
  105.   
  106.   
  107.      return resultCode;  
  108.   
  109.     }  
  110.   
  111.     finally {  
  112.   
  113.      UtilTimerStack.pop(profileKey);  
  114.   
  115.     }  
  116.   
  117. }  



 

      從源碼中,我們可以看到Action層的4個不同的層次,在這個方法中都有體現,他們分別是:攔截器(Interceptor)、Action、PreResultListener和Result。在這個方法中,保證了這些層次的有序調用和執行。由此我們也可以看出Struts2在Action層次設計上的眾多考慮,每個層次都具備了高度的擴展性和插入點,使得程序員可以在任何喜歡的層次加入自己的實現機制改變Action的行為。
在這里,需要特別強調的,是其中攔截器部分的執行調用:

[java]  view plain copy print ?
 
  1. resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);    




表面上,它只是執行了攔截器中的intercept方法,如果我們結合攔截器來看,就能看出點端倪來:

[java]  view plain copy print ?
 
  1. public String intercept(ActionInvocation invocation) throws Exception {     
  2.   
  3.     String result = null;     
  4.   
  5.          before(invocation);     
  6.   
  7.         // 調用invocation的invoke()方法,在這里形成了遞歸調用    
  8.   
  9.       result = invocation.invoke();     
  10.   
  11.         after(invocation, result);     
  12.   
  13.        return result;     
  14.   
  15. }    


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


免責聲明!

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



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