struts 拦截器 Interceptor

     拦截器是AOP中的概念,它本身是一段代码,可以通过定义“织入点”,来指定拦截器的代码在“织入点”的前后执行,从而起到拦截的作用。正如上面 Struts2的Reference中讲述的,Struts2的Interceptor,其拦截的对象是Action代码,可以定义在Action代码之前或者之后执行拦截器的代码。



接下来,我们将重点讨论一下Struts2中的拦截器的内部结构和执行顺序,并结合源码进行分析。

Interceptor结构

让我们再来回顾一下之前我们曾经用过的一张Action LifeCycle的图:



图中,我们可以发现,Struts2的Interceptor一层一层,把Action包裹在最里面。这样的结构,大概有以下一些特点:

1. 整个结构就如同一个堆栈,除了Action以外,堆栈中的其他元素是Interceptor

2. Action位于堆栈的底部。由于堆栈"先进后出"的特性,如果我们试图把Action拿出来执行,我们必须首先把位于Action上端的Interceptor拿出来执行。这样,整个执行就形成了一个递归调用

3. 每个位于堆栈中的Interceptor,除了需要完成它自身的逻辑,还需要完成一个特殊的执行职责。这个执行职责有3种选择:

1) 中止整个执行,直接返回一个字符串作为resultCode

2) 通过递归调用负责调用堆栈中下一个Interceptor的执行 (invocation.invoke())

3) 如果在堆栈内已经不存在任何的Interceptor,调用Action


Struts2的拦截器结构的设计,实际上是一个典型的责任链模式的应用。首先将整个执行划分成若干相同类型的元素,每个元素具备不同的逻辑责任,并将他们纳入到一个链式的数据结构中(我们可以把堆栈结构也看作是一个递归的链式结构),而每个元素又有责任负责链式结构中下一个元素的执行调用。

这样的设计,从代码重构的角度来看,实际上是将一个复杂的系统,分而治之,从而使得每个部分的逻辑能够高度重用并具备高度可扩展性。所以,Interceptor结构实在是Struts2/Xwork设计中的精华之笔。

Interceptor执行分析

Interceptor的定义

我们来看一下Interceptor的接口的定义:

Java代码 复制代码 收藏代码
  1. public interface Interceptor extends Serializable {   
  2.   
  3.     /**  
  4.      * Called to let an interceptor clean up any resources it has allocated.  
  5.      */  
  6.     void destroy();   
  7.   
  8.     /**  
  9.      * Called after an interceptor is created, but before any requests are processed using  
  10.      * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving  
  11.      * the Interceptor a chance to initialize any needed resources.  
  12.      */  
  13.     void init();   
  14.   
  15.     /**  
  16.      * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the  
  17.      * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.  
  18.      *  
  19.      * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.  
  20.      * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.  
  21.      */  
  22.     String intercept(ActionInvocation invocation) throws Exception;   
  23. }  

Interceptor的接口定义没有什么特别的地方,除了init和destory方法以外,intercept方法是实现整个拦截器机制的核心方法。而它所依赖的参数ActionInvocation则是我们之前章节中曾经提到过的著名的Action调度者

我们再来看看一个典型的Interceptor的抽象实现类:

Java代码 复制代码 收藏代码
  1. public abstract class AroundInterceptor extends AbstractInterceptor {   
  2.        
  3.     /* (non-Javadoc)  
  4.      * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation)  
  5.      */  
  6.     @Override  
  7.     public String intercept(ActionInvocation invocation) throws Exception {   
  8.         String result = null;   
  9.   
  10.         before(invocation);   
  11.         // 调用下一个拦截器,如果拦截器不存在,则执行Action   
  12.         result = invocation.invoke();   
  13.         after(invocation, result);   
  14.   
  15.         return result;   
  16.     }   
  17.        
  18.     public abstract void before(ActionInvocation invocation) throws Exception;   
  19.   
  20.     public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;   
  21.   
  22. }  


在这个实现类中,实际上已经实现了最简单的拦截器的雏形。或许大家对这样的代码还比较陌生,这没有关系。我在这里需要指出的是一个很重要的方法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。

Interceptor拦截类型

从上面的分析,我们知道,整个拦截器的核心部分是invocation.invoke()这个函数的调用位置。事实上,我们也正式根据这句代码的调用位置,来进行拦截类型的区分的。在Struts2中,Interceptor的拦截类型,分成以下三类:

1. before

before拦截,是指在拦截器中定义的代码,它们存在于invocation.invoke()代码执行之前。这些代码,将依照拦截器定义的顺序,顺序执行

2. after

after拦截,是指在拦截器中定义的代码,它们存在于invocation.invoke()代码执行之后。这些代码,将一招拦截器定义的顺序,逆序执行

3. PreResultListener

有的时候,before拦截和after拦截对我们来说是不够的,因为我们需要在Action执行完之后,但是还没有回到视图层之前,做一些事情。Struts2同样支持这样的拦截,这种拦截方式,是通过在拦截器中注册一个PreResultListener的接口来实现的。

Java代码 复制代码 收藏代码
  1. public interface PreResultListener {   
  2.   
  3.     /**  
  4.      * This callback method will be called after the Action execution and before the Result execution.  
  5.      *  
  6.      * @param invocation  
  7.      * @param resultCode  
  8.      */  
  9.     void beforeResult(ActionInvocation invocation, String resultCode);   
  10. }  

在这里,我们看到,Struts2能够支持如此多的拦截类型,与其本身的数据结构和整体设计有很大的关系。正如我在之前的文章中所提到的:

因为Action是一个普通的Java类,而不是一个Servlet类,完全脱离于Web容器,所以我们就能够更加方便地对Control层进行合理的层次设计,从而抽象出许多公共的逻辑,并将这些逻辑脱离出Action对象本身。


我们可以看到,Struts2对于整个执行的划分,从Interceptor到Action一直到Result,每一层都职责明确。不仅如此,Struts2还为每一个层次之前都设立了恰如其分的插入点。使得整个Action层的扩展性得到了史无前例的提升。

Interceptor执行顺序

Interceptor的执行顺序或许是我们在整个过程中最最关心的部分。根据上面所提到的概念,我们实际上已经能够大致明白了Interceptor的执行机理。我们来看看Struts2的Reference对Interceptor执行顺序的一个形象的例子。

如果我们有一个interceptor-stack的定义如下:

Xml代码 复制代码 收藏代码
  1. <interceptor-stack name="xaStack">  
  2.   <interceptor-ref name="thisWillRunFirstInterceptor"/>  
  3.   <interceptor-ref name="thisWillRunNextInterceptor"/>  
  4.   <interceptor-ref name="followedByThisInterceptor"/>  
  5.   <interceptor-ref name="thisWillRunLastInterceptor"/>  
  6. </interceptor-stack>  

那么,整个执行的顺序大概像这样:



在这里,我稍微改了一下Struts2的Reference中的执行顺序示例,使得整个执行顺序更加能够被理解。我们可以看到,递归调用保证了各种各样的拦截类型的执行能够井井有条。

请注意在这里,每个拦截器中的代码的执行顺序,在Action之前,拦截器的执行顺序与堆栈中定义的一致;而在Action和Result之后,拦截器的执行顺序与堆栈中定义的顺序相反。

源码解析

接下来我们就来看看源码,看看Struts2是如何保证拦截器、Action与Result三者之间的执行顺序的。

之前我曾经提到,ActionInvocation是Struts2中的调度器,所以事实上,这些代码的调度执行,是在ActionInvocation的实现类中完成的,这里,我抽取了DefaultActionInvocation中的invoke()方法,它将向我们展示一切。

Java代码 复制代码 收藏代码
  1. /**  
  2.  * @throws ConfigurationException If no result can be found with the returned code  
  3.  */  
  4. public String invoke() throws Exception {   
  5.     String profileKey = "invoke: ";   
  6.     try {   
  7.         UtilTimerStack.push(profileKey);   
  8.                
  9.         if (executed) {   
  10.             throw new IllegalStateException("Action has already executed");   
  11.         }   
  12.         // 依次调用拦截器堆栈中的拦截器代码执行   
  13.         if (interceptors.hasNext()) {   
  14.             final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();   
  15.             UtilTimerStack.profile("interceptor: "+interceptor.getName(),    
  16.                     new UtilTimerStack.ProfilingBlock<String>() {   
  17.                         public String doProfiling() throws Exception {   
  18.                          // 将ActionInvocation作为参数,调用interceptor中的intercept方法执行   
  19.                             resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);   
  20.                             return null;   
  21.                         }   
  22.             });   
  23.         } else {   
  24.             resultCode = invokeActionOnly();   
  25.         }   
  26.   
  27.         // this is needed because the result will be executed, then control will return to the Interceptor, which will   
  28.         // return above and flow through again   
  29.         if (!executed) {   
  30.             // 执行PreResultListener   
  31.             if (preResultListeners != null) {   
  32.                 for (Iterator iterator = preResultListeners.iterator();   
  33.                     iterator.hasNext();) {   
  34.                     PreResultListener listener = (PreResultListener) iterator.next();   
  35.                            
  36.                     String _profileKey="preResultListener: ";   
  37.                     try {   
  38.                             UtilTimerStack.push(_profileKey);   
  39.                             listener.beforeResult(this, resultCode);   
  40.                     }   
  41.                     finally {   
  42.                             UtilTimerStack.pop(_profileKey);   
  43.                     }   
  44.                 }   
  45.             }   
  46.   
  47.             // now execute the result, if we're supposed to   
  48.             // action与interceptor执行完毕,执行Result   
  49.             if (proxy.getExecuteResult()) {   
  50.                 executeResult();   
  51.             }   
  52.   
  53.             executed = true;   
  54.         }   
  55.   
  56.         return resultCode;   
  57.     }   
  58.     finally {   
  59.         UtilTimerStack.pop(profileKey);   
  60.     }   
  61. }  
 

从源码中,我们可以看到,我们之前提到的Struts2的Action层的4个不同的层次,在这个方法中都有体现,他们分别是:拦截器(Interceptor)、Action、PreResultListener和Result。在这个方法中,保证了这些层次的有序调用和执行。由此我们也可以看出Struts2在Action层次设计上的众多考虑,每个层次都具备了高度的扩展性和插入点,使得程序员可以在任何喜欢的层次加入自己的实现机制改变Action的行为。

在这里,需要特别强调的,是其中拦截器部分的执行调用:

Java代码 复制代码 收藏代码
  1. resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);  
表面上,它只是执行了拦截器中的intercept方法,如果我们结合拦截器来看,就能看出点端倪来:

Java代码 复制代码 收藏代码
  1. public String intercept(ActionInvocation invocation) throws Exception {   
  2.     String result = null;   
  3.   
  4.         before(invocation);   
  5.         // 调用invocation的invoke()方法,在这里形成了递归调用 ,执行下一个拦截器或antion
  6.         result = invocation.invoke();   
  7.         after(invocation, result);   
  8.   
  9.         return result;   
  10. }  

原来在intercept()方法又对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke()执行结束。这样,Interceptor又会按照刚开始执行的逆向顺序依次执行结束。

一个有序链表,通过递归调用,变成了一个堆栈执行过程,将一段有序执行的代码变成了2段执行顺序完全相反的代码过程,从而巧妙地实现了AOP。这也就成为了Struts2的Action层的AOP基础。
 
随笔 Struts 2, 值在验证失败回到原来页面的时候会丢失的解决方案 中就用到了 prepare 拦截器(如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法。)
 
 

拦截器

名字

说明

Alias Interceptor

alias

在不同请求之间将请求参数在不同名字件转换,请求内容不变

Chaining Interceptor

chain

让前一个Action的属性可以被后一个Action访问,现在和chain类型的result)结合使用。

Checkbox Interceptor

checkbox

添加了checkbox自动处理代码,将没有选中的checkbox的内容设定为false,而html默认情况下不提交没有选中的checkbox

Cookies Interceptor

cookies

使用配置的name,value来是指cookies

Conversion Error Interceptor

conversionError

将错误从ActionContext中添加到Action的属性字段中。

Create Session Interceptor

createSession

自动的创建HttpSession,用来为需要使用到HttpSession的拦截器服务。

Debugging Interceptor

debugging

提供不同的调试用的页面来展现内部的数据状况。

Execute and Wait Interceptor

execAndWait

在后台执行Action,同时将用户带到一个中间的等待页面。

Exception Interceptor

exception

将异常定位到一个画面

File Upload Interceptor

fileUpload

提供文件上传功能

I18n Interceptor

i18n

记录用户选择的locale

Logger Interceptor

logger

输出Action的名字

Message Store Interceptor

store

存储或者访问实现ValidationAware接口的Action类出现的消息,错误,字段错误等。

Model Driven Interceptor

model-driven

如果一个类实现了ModelDriven,将getModel得到的结果放在Value Stack中。

Scoped Model Driven

scoped-model-driven

如果一个Action实现了ScopedModelDriven,则这个拦截器会从相应的Scope中取出model调用ActionsetModel方法将其放入Action内部。

Parameters Interceptor

params

将请求中的参数设置到Action中去。

Prepare Interceptor

prepare

如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法。

Scope Interceptor

scope

Action状态存入sessionapplication的简单方法。

Servlet Config Interceptor

servletConfig

提供访问HttpServletRequestHttpServletResponse的方法,以Map的方式访问。

Static Parameters Interceptor

staticParams

struts.xml文件中将中的

中的内容设置到对应的Action中。

Roles Interceptor

roles

确定用户是否具有JAAS指定的Role,否则不予执行。

Timer Interceptor

timer

输出Action执行的时间

Token Interceptor

token

通过Token来避免双击

Token Session Interceptor

tokenSession

Token Interceptor一样,不过双击的时候把请求的数据存储在Session

Validation Interceptor

validation

使用action-validation.xml文件中定义的内容校验提交的数据。

Workflow Interceptor

workflow

调用Actionvalidate方法,一旦有错误返回,重新定位到INPUT画面

Parameter Filter Interceptor

N/A

从参数列表中删除不必要的参数

Profiling Interceptor

profiling

通过参数激活profile

 
posted @ 2013-09-05 10:36  宅山仔  阅读(278)  评论(0编辑  收藏  举报