JavaWeb从入门到精通(视频实战版)
上QQ阅读APP看书,第一时间看更新

5.7 拦截器介绍

拦截器(Interceptor)是Struts2的一个强有力的工具,有许多功能都是构建在它之上,如国际化、转换器、校验等。Interceptor是Struts2的一大特色,在执行action之前和之后可以使请求一个或多个Interceptor。多个连接器组合在一起实现某一个功能称为Interceptor链(Interceptor Chain),在Struts2中称为拦截器栈(Interceptor Stack)。Interceptor链就是将Interceptor按一定的顺序联结成一条链。在访问被拦截的方法或字段时,Interceptor链中的Interceptor就会按其之前定义的顺序被调用。

5.7.1 拦截器的原理

Struts2的Interceptor实现相对简单。当请求到达Struts2的ServletDispatcher时,Struts2会查找配置文件,并根据其配置实例化相对的Interceptor对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器,如图5.14所示。

图5.14 拦截器调用序列图

拦截器与action和result的关系如图5.15所示。

Action和Interceptor在框架中的执行是由ActionInvocation对象调用的。它是用方法“String invoke() throws Exception;”来实现的,它首先会依次调用Action对应的Interceptor ,执行完所有的Interceptor之后,再去调用Action的方法,如实例5-31所示。

【实例5-31】Action的interceptor:ActionInvocation片段

01     /*
02     *拦截器组, 一个一个被执行
03     */
04     if (interceptors.hasNext()) {
05     Interceptor interceptor = (Interceptor) interceptors.next();
06     resultCode = interceptor.intercept(this);
07     } else {
08            if (proxy.getConfig().getMethodName() == null) {
09                resultCode = getAction().execute();
10            } else {
11                resultCode = invokeAction(getAction(), proxy.getConfig());
12            }
13     }

图5.15 拦截器与action、result的关系图

【代码剖析】上述代码说明会在Interceptor栈中遍历所有的Interceptor,调用每个拦截器的intercept方法。定义如下:

String intercept(ActionInvocation invocation) throws Exception;

Interceptor是在action前后执行,那么在action之后的处理如何实现呢?Struts2提供了一个抽象类AroundInterceptor,如实例5-32所示。即通过原来的intercept()方法对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result=invocation.invoke();执行结束,即:Action执行完并返回结果result。这时Interceptor对象会按照刚开始执行的逆向顺序依次执行结束。这样before()方法将在Action执行前调用,after()方法在Action执行之后运行。

【实例5-32】AroundInterceptor的intercept():AroundInterceptor.java

01     public abstract class AroundInterceptor implements Interceptor {
02          //析构函数
03          public void destroy() {
04          }
05          //初始化
06          public void init() {
07          }
08          public String intercept(ActionInvocation invocation) throws Exception {
09               String result = null;                             //创建字符串变量result
10                before(invocation);
11                result = invocation.invoke();
12                after(invocation, result);
13               return result;                                 //返回字符串result
14          }
15          protected abstract void after(ActionInvocation dispatcher, String result) throws
16     Exception;
17          protected abstract void before(ActionInvocation invocation) throws Exception;
18     }

5.7.2 内置拦截器介绍

Struts2包含了许多内置的Interceptor,它们提供了很多核心功能和可选的高级特性。Interceptor在struts.default.xml文件中被定义,而一些默认的Interceptor栈及Interceptor的命名也被定义其中。框架中提供了很多实用的Interceptor,可以随时使用它们的名字来调用这些Interceptor。它们的具体功能如表5.7所示。

表5.7 内置拦截器表

5.7.3 使用内置拦截器

本节将介绍几种常用interceptor的用法。

1. 使用timer为action计时

执行过程计时是最简单却又最常用的interceptor,同时它也是interceptor模式使用的一个经典范例。timer interceptor是interceptor接口的最简单实现,实例5-33所示是它的interceptor()方法,该方法将action和result执行所用时间记录下来。

【实例5-33】timer interceptor实现:interceptor片段

01     /*
02     * 实现intercept方法
03     */
04     public String intercept(ActionInvocation invocation) throws Exception {
05      long startTime =        System.currentTimeMillis();
06      String result = invocation.invoke();
07      long executionTime = System.currentTimeMillis() - startTime;
08      //记录时间
09      return result
10     }

2. 使用logger为action提供日志

Log interceptor是扩展了AroundInterceptor类来实现的,绝大多数内置的拦截器都是扩展了com.opensymphony.xwork2.interceptor. AroundInterceptor,代码如实例5-34所示。这里使用了模板模式,将intercept函数拆分为两个before()和after()函数。

【实例5-34】AroundInterceptor定义:AroundInterceptor片段

01     public abstract class AroundInterceptor implements Interceptor {
02          protected transient Log log = LogFactory.getLog(getClass());
03          public void destroy() {
04          }
05          public void init() {
06          }
07          public String intercept(ActionInvocation invocation) throws Exception {
08                String result = null;
09                before(invocation);
10                result = invocation.invoke();
11                after(invocation, result);
12                return result;
13          }
14          /**
15           * 在invocation 执行完毕之后被调用
16           * * @param result invocation返回的值
17           */
18          protected abstract void after(ActionInvocation dispatcher, String result) throws
19     Exception;
20
21          /**
22           * 在invocation 执行之前被调用
23           */
24          protected abstract void before(ActionInvocation invocation) throws Exception;
25     }

有了这个基类,实现日志功能就很容易了,如实例5-35所示,LoggingInterceptor可以实现在action之前和之后各打印一句日志。

【实例5-35】LoggingInterceptor定义:LoggingInterceptor.java

01     public class LoggingInterceptor extends AroundInterceptor {
02          private static final Log log = LogFactory.getLog(LoggingInterceptor.class);
0 3          private static final String FINISH_MESSAGE = "Finishing execution stack for action ";
04          private static final String START_MESSAGE = "Starting execution stack for action ";
05          //后处理
06          protected void after(ActionInvocation invocation, String result) throws Exception {
07                logMessage(invocation, FINISH_MESSAGE);
08          }
09          //前处理
10          protected void before(ActionInvocation invocation) throws Exception {
11                logMessage(invocation, START_MESSAGE);
12          }
13          //写日志动作
14          private void logMessage(ActionInvocation invocation, String baseMessage) {
15          //略
16          }
17     }

【代码剖析】上述代码重新实现了类AroundInterceptor的方法after()和before(),然后实现了logMessage()方法,在该方法中实现了业务逻辑。

3. 使用校验

DefaultWorkflowInterceptor和Vaildateable接口是在执行action之前校验用户输入的一种方法。校验用户输入的另外一种途径是使用ValidationInterceptor。这个interceptor调用的是校验框架,该框架允许在外部XML文件中定义校验过程,从而将校验过程从代码中分离出来。DefaultWorkflowInterceptor和ValidationInterceptor这两个类都是继承于MethodFilterInterceptor,它利用反射方式取得action的每个属性,根据条件再加以判断。

关于以上这两个Interceptor的配置方法在前面的章节中已有叙述。两种用于校验的方法是可以一起使用的。

4. 准备action

PrepareInterceptor与SevletConfigInterceptor相似,如果action实现了恰当的接口,它将调用action的某个合适的方法。PrepareInterceptor只会作用于实现com.opensymphony.xwork2.Preparable接口的action。Preparable接口只定义了一个方法:

void prepare() throws Exception;

PrepareInterceptor找出实现Preparable接口的类,然后调用它们的prepare ()方法,如实例5-36所示。

【实例5-36】PrepareInterceptor定义:PrepareInterceptor.java

01     /*
02     * 实现before方法
03     */
04     protected void before(ActionInvocation invocation) throws Exception {
05        Object action = invocation.getAction();
06          if (action instanceof Preparable) {
07              ((Preparable) action).prepare();
08          }
09     }

【代码剖析】Preparable接口对于在action执行之前设置的资源或者数值是十分有用的。譬如,如果使用了一个下拉列表,而该列表包含了从数据查找获得的可用数值,则可以在prepared()方法中完成这个查询工作。因此,这些数值将会被填充至下拉列表框并显示,即使在这个action由于DefaultWorkflowInterceptor发现了错误而没有被执行的情况下也同样有效。

5. 实现ModelDriven

如前所述,利用ModelDrivenInterceptor可以实现action的ModelDriven模式输入,ModelDrivenInterceptor查找实现了ModelDriven接口的action,然后调用接口定义的getModel()方法获得被压入值栈的对象,从而使该对象的属性能够被OGNL表达式直接访问。

6. token和token-session

token和token-session这两个interceptor可以实现,诸如防止表单重复提交和在不让用户等待的情况下,执行耗时长的action等功能。这将在第7章中详细说明。

5.7.4 内置拦截器栈介绍

除了内置的interceptor之外,struts.xml还包含了内置的interceptor组合,可以通过具体的命名的interceptor栈来使用它们。表5.8列出了在struts.xml中定义的interceptor栈。

表5.8 内置interceptor栈表

图5.16所示是一个interceptor栈的构成示意图。

图5.16 interceptor栈示意图

内置的栈都是一些经典的组合,非常实用,而且这些栈为程序员开发自己的栈提供了一个很好的起点,可以了解interceptor执行顺序及栈的设计原则。在第7章中会介绍一些栈的具体应用例子。

注意

interceptor在栈中的执行顺序十分重要,注意其在struts.xml中是如何配置的。

5.7.5 自定义拦截器

自定义一个拦截器需要3个步骤:

1)自定义一个实现Interceptor接口的类。

2)在struts.xml中注册上一步中定义的拦截器。

3)在需要使用的Action中引用上述定义的拦截器,为了方便也可将拦截器定义为默认的拦截器,这样在不加特殊声明的情况下,所有的Action都被这个拦截器拦截。

Interceptor接口声明了三个方法,如实例5-37所示。

【实例5-37】PrepareInterceptor定义:PrepareInterceptor.java

01     /*
02     *PrepareInterceptor 接口定义
03     */
04     public interface Interceptor extends Serializable {
05          void destroy();                                    //关于销毁方法
06          void init();                                       //关于初始化方法
07          String intercept(ActionInvocation invocation) throws Exception; //关于拦截方法
08     }

【代码剖析】上面第5行到第7行定义了3个方法,分别为销毁时调用的方法destroy()、初始化时调用的方法init()和拦截时调用的拦截方法intercept()。

init方法在拦截器类被创建之后,在对action镜像拦截之前调用,相当于一个post-constructor方法,使用这个方法可以给拦截器类做必要的初始化操作。destroy()方法在拦截器被垃圾回收之前调用,用来回收init()方法初始化的资源。

intercept是拦截器的主要拦截方法,如果需要调用后续的Action或者拦截器,只需要在该方法中调用invocation.invoke()方法即可,在该方法调用的前后可以插入action调用前后拦截器需要做的方法。

如果不需要调用后续的方法,则返回一个String类型的对象即可,例如Action.SUCCESS。另外,AbstractInterceptor提供了一个简单的Interceptor的实现,在不需要编写init()和destroy()方法的时候,只需要从AbstractInterceptor继承而来,实现intercept方法即可。

实例5-38给出了一个Session过滤用的拦截器,该拦截器查看用户Session中是否存在特定的属性(LOGIN属性),如果不存在,中止后续操作定位到LOGIN转向登录界面,让用户重新登录,否则执行原定操作。

【实例5-38】CheckLoginInterceptor实现:CheckLoginInterceptor.java

01     /*
02     *PrepareInterceptor 实现方式
03     */
04     public class CheckLoginInterceptor extends AbstractInterceptor {
05
06          public static final String LOGIN_KEY = "LOGIN";
07          public static final String LOGIN_PAGE = "global.login";
08          public String intercept(ActionInvocation actionInvocation) throws Exception {
09                System.out.println("begin check login interceptor!");
10               // 对LoginAction不做该项拦截
11                Object action = actionInvocation.getAction();
12                if (action instanceof LoginAction) {
13                     System.out.println("exit check login, because this is login action.");
14                     return actionInvocation.invoke();
15                }
16               // 确认Session中是否存在LOGIN
17                Map session = actionInvocation.getInvocationContext().getSession();
18                String login = (String) session.get(LOGIN_KEY);
19                if (login != null && login.length() > 0) {
20                    // 存在的情况下进行后续操作
21                     System.out.println("already login!");
22                     return actionInvocation.invoke();
23                } else {
24                    // 否则终止后续操作, 返回LOGIN
25                     System.out.println("no login, forward login page!");
26                     return LOGIN_PAGE;
27                }
28          }
29     }

【代码剖析】上面的代码重写了接口AbstractInterceptor的方法intercept(),在该方法中查看用户Session中是否存在特定的属性(LOGIN属性)。

注册拦截器配置,如果给action定义了interceptor,那么默认的interceptor将不会被执行。一般情况下,是选择需要把basicStack加到自定义的栈中去,如实例5-39所示。

【实例5-39】CheckLoginInterceptor配置文件:struts.xml

01     <interceptors>
02       <!--PrepareInterceptor在struts.xml中的定义-->
03        <interceptor name="login" class="tutorial.CheckLoginInterceptor"/>
04            <interceptor-stack name="teamwareStack">
05                <interceptor-ref name="login"/>
06                <interceptor-ref name=" basicStack "/>
07            </interceptor-stack>
08     </interceptors>

【代码剖析】上面代码实现了名为login的拦截器在struts.xml文件中的配置。

将上述拦截器设定为默认拦截器:

<default-interceptor-ref name="teamwareStack"/>

这样在后续同一个package内部的所有Action执行之前都会被login拦截。

注意

Struts2不能保证为每一个请求或者action创建一个实例,开发自定义interceptor应该保证是interceptor无状态的(不要用实例的属性来保存变量),否则会引起并发问题。