零基础学Struts
上QQ阅读APP看书,第一时间看更新

6.4 深入拦截器

前面介绍了拦截器的基本配置以及使用,但是对于一些深入拦截器的配置方面,还有许多地方值得注意,如传递参数、配置拦截器栈、方法过滤等。

6.4.1 传递参数

前面提到过,可以在配置拦截器时,为其指定参数。现在修改自定义拦截器类,为其增加一个属性interceptorName,并为其添加setter方法,代码如下所示。

        package net.hncu.interceptor;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
        public class MyInterceptor   extends AbstractInterceptor{
              //拦截器的名称
              private String interceptorName;
              public void setInterceptorName(String interceptorName) {
                    this.interceptorName = interceptorName;
              }
              //实现拦截的方法
              public String intercept(ActionInvocation invocation) throws Exception {
                    //拦截器执行时首先打印该语句
                    System.out.println(interceptorName + ":拦截前操作");
                    //调用下一个拦截器或者直接调用Actionexecute方法
                    String result = invocation.invoke();
                    //拦截器执行后打印该语句
                    System.out.println(interceptorName + ":拦截后操作");
                    return result;
              }
        }

在“struts.xml”文件中配置该拦截器,在interceptor元素中添加param元素用来传递参数。其参数名为拦截器类中属性interceptorName,设定其参数值为“拦截器一”,代码如下所示。

        <interceptors>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter" class="net.hncu.interceptor.MyInterceptor">
                      <param name="interceptorName">拦截器一</param>
              </interceptor>
        </interceptors>

再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。Tomcat服务器控制台输出了在拦截器中定义的两个拦截方法,如图6.8所示。

图6.8 Tomcat服务器控制台输出显示(1)

下面再来看使用拦截器如何添加传递参数,修改代码如下所示。

        <! -- 配置所有的拦截器 -->
        <interceptors>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter" class="net.hncu.interceptor.MyInterceptor">
                    <param name="interceptorName">拦截器一</param>
              </interceptor>
        </interceptors>
        <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
        <action name="login" class="net.hncu.struts2.action.LoginAction">
              <! -- 定义处理结果与视图资源之间的关系-->
              <result name="success">/login_success.jsp</result>
              <result name="error">/login_failure.jsp</result>
              <result name="input">login.jsp</result>
              <! -- 使用myInter拦截器 -->
              <interceptor-ref name="myInter">
                    <param name="interceptorName">自定义拦截器一</param>
              </interceptor-ref>
              <! -- 加入Struts 2的默认拦截器栈 -->
              <interceptor-ref name="defaultStack"></interceptor-ref>
        </action>

这时再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。这是发现控制台打印的语句。发现拦截器使用的是使用拦截器时配置的参数,如图6.9所示。

图6.9 Tomcat服务器控制台输出显示(2)

从这里可以看出使用时配置的参数会覆盖前面配置的参数。这时要注意的是,配置时指定的参数也起到了传递参数的作用,只是如果在使用拦截器时也指定了参数,那么它会给拦截器属性重新赋值。

6.4.2 配置拦截器栈

下面来看如何配置拦截器栈。添加一个拦截器类MyInterceptor2,并为其添加拦截器方法,代码如下所示。

        package net.hncu.interceptor;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
        public class MyInterceptor2   extends AbstractInterceptor{
              //拦截器的名称
              private String interceptorName;
              public void setInterceptorName(String interceptorName) {
                    this.interceptorName = interceptorName;
              }
              //实现拦截的方法
              public String intercept(ActionInvocation invocation) throws Exception {
                    //拦截器执行时首先打印该语句
                    System.out.println(interceptorName + ":-----拦截前操作-----");
                    //拦截器执行时首先打印该语句
                    String result = invocation.invoke();
                    //拦截器执行后打印该语句
                    System.out.println(interceptorName + ":------拦截后操作------");
                    return result;
              }
        }

使用interceptor-stack元素来配置一个拦截器,这里配置一个名为myInterStack的拦截器栈。使用interceptor-ref元素来定义拦截器引用,这里添加两个拦截器的引用,代码如下所示。

        <! -- 配置所有的拦截器 -->
        <interceptors>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter1" class="net.hncu.interceptor.MyInterceptor">
                      <param name="interceptorName">拦截器一</param>
              </interceptor>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter2" class="net.hncu.interceptor.MyInterceptor2">
                      <param name="interceptorName">拦截器二</param>
              </interceptor>
              <! -- 配置myInterStack拦截器栈 -->
              <interceptor-stack name="myInterStack">
                      <interceptor-ref name="myInter1"></interceptor-ref>
                      <interceptor-ref name="myInter2"></interceptor-ref>
              </interceptor-stack>
        </interceptors>
        <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
        <action name="login" class="net.hncu.struts2.action.LoginAction">
              <! -- 定义处理结果与视图资源之间的关系-->
              <result name="success">/login_success.jsp</result>
              <result name="error">/login_failure.jsp</result>
              <result name="input">login.jsp</result>
              <! -- 使用myInterStack拦截器栈 -->
              <interceptor-ref name="myInterStack"></interceptor-ref>
              <! -- 加入Struts 2的默认拦截器栈 -->
              <interceptor-ref name="defaultStack"></interceptor-ref>
        </action>

再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从Tomcat服务器控制台输出的语句中可以看出,这两个拦截器都拦截了该Action,如图6.10所示。

图6.10 Tomcat服务器控制台输出显示

同样也可以将默认拦截器加入到自定义的拦截器栈中,代码如下所示。

              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter1" class="net.hncu.interceptor.MyInterceptor">
                    <param name="interceptorName">拦截器一</param>
              </interceptor>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter2" class="net.hncu.interceptor.MyInterceptor2">
                    <param name="interceptorName">拦截器二</param>
              </interceptor>
              <interceptor-stack name="myInterStack">
                    <interceptor-ref name="myInter1"></interceptor-ref>
                    <interceptor-ref name="myInter2"></interceptor-ref>
                    <interceptor-ref name="defaultStack"></interceptor-ref>
              </interceptor-stack>
        </interceptors>
        <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
        <action name="login" class="net.hncu.struts2.action.LoginAction">
              <! -- 定义处理结果与视图资源之间的关系-->
              <result name="success">/login_success.jsp</result>
              <result name="error">/login_failure.jsp</result>
              <result name="input">login.jsp</result>
              <! -- 使用myInterStack拦截器栈 -->
              <interceptor-ref name="myInterStack"></interceptor-ref>
        </action>

将默认拦截器加入到自定义的拦截器栈中,这样使用起来就比较方便。代码显得更加整洁,使用起来也方便多了。不用再为多个Action重复定义该默认拦截器,只需使用自定义的拦截器栈就可以了。

在引入拦截器时也可以给拦截器配置参数。比如在引入myInter1拦截器和myInter2拦截器时为其配置参数,代码如下所示。

        <! -- 配置所有的拦截器 -->
        <interceptors>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter1" class="net.hncu.interceptor.MyInterceptor">
                    <param name="interceptorName">拦截器一</param>
              </interceptor>
              <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
              <interceptor name="myInter2" class="net.hncu.interceptor.MyInterceptor2">
                    <param name="interceptorName">拦截器二</param>
              </interceptor>
              <! -- 配置myInterStack拦截器栈 -->
              <interceptor-stack name="myInterStack">
                    <interceptor-ref name="myInter1">
                            <param name="interceptorName">自定义拦截器一</param>
                    </interceptor-ref>
                    <interceptor-ref name="myInter2">
                            <param name="interceptorName">自定义拦截器二</param>
                    </interceptor-ref>
                    <interceptor-ref name="defaultStack"></interceptor-ref>
              </interceptor-stack>
        </interceptors>
        <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
        <action name="login" class="net.hncu.struts2.action.LoginAction">
              <! -- 定义处理结果与视图资源之间的关系-->
              <result name="success">/login_success.jsp</result>
              <result name="error">/login_failure.jsp</result>
              <result name="input">login.jsp</result>
              <! -- 使用myInterStack拦截器栈 -->
              <interceptor-ref name="myInterStack"></interceptor-ref>
        </action>

再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。这时查看控制台输出的语句,发现拦截器中使用的是在引入拦截器时也可以给拦截器配置参数,如图6.11所示。

图6.11 Tomcat服务器控制台输出显示

6.4.3 覆盖拦截器栈中指定拦截器的参数

前面介绍了在使用拦截器时也可以为拦截器指定参数。同样在使用拦截器栈时也可以为每个拦截器指定参数,从而覆盖在配置拦截器时指定的参数值。

首先来看在interceptor-ref元素中param元素,并指定参数名和参数值,代码如下所示。

          <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
          <action name="login" class="net.hncu.struts2.action.LoginAction">
                <! -- 定义处理结果与视图资源之间的关系-->
                <result name="success">/login_success.jsp</result>
                <result name="error">/login_failure.jsp</result>
                <result name="input">login.jsp</result>
                <! -- 使用myInterStack拦截器栈 -->
                <interceptor-ref name="myInterStack">
                      <param name="interceptorName">使用自定义拦截器</param>
                </interceptor-ref>

打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。查看Tomcat服务器控制台的输出,如图6.12所示。

图6.12 Tomcat服务器控制台输出显示

奇怪,好像控制台的输出没变化。这是因为拦截器栈中有多个拦截器。这样笼统地定义参数,并为其指定参数值,到底是为哪个拦截器指定参数值呢?应该如何配置,才能覆盖在配置拦截器时指定的参数值?

只需在param元素中指定其name属性为拦截器名+“.”+参数名,并指定该参数的参数值,就可以覆盖在配置拦截器时指定的参数值了,代码如下所示。

          <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
          <action name="login" class="net.hncu.struts2.action.LoginAction">
                <! -- 定义处理结果与视图资源之间的关系-->
                <result name="success">/login_success.jsp</result>
                <result name="error">/login_failure.jsp</result>
                <result name="input">login.jsp</result>
                <! -- 使用myInterStack拦截器栈 -->
                <interceptor-ref name="myInterStack">
                      <param name="myInter1.interceptorName">使用自定义拦截器一</param>
                      <param name="myInter2.interceptorName">使用自定义拦截器二</param>
                </interceptor-ref>
          </action>

再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。这时查看控制台输出的语句,默认的参数值已经被使用拦截器配置的参数值覆盖,如图6.13所示。

这么多的地方可以配置参数,如何来判断在哪个地方配置参数呢?这个根据具体情况而定,不过必须知道参数传递的先后顺序。参数传递顺序图如图6.14所示。

了解参数传递的先后顺序,还必须记得后执行传递的参数值是否会覆盖前面传递的参数值。

图6.13 Tomcat服务器控制台输出显示

图6.14 参数传递的先后顺序

6.4.4 拦截器执行顺序

现在查看下Tomcat服务器控制台的输出如图6.15所示,留意一下这些语句输出的顺序。

图6.15 Tomcat服务器控制台输出显示

在拦截器中先引入的拦截器会先执行,后引入的拦截器后执行。还有就是拦截器会先执行拦截器前操作,然后执行下一个拦截器的拦截器前操作,依次类推。同样当这些拦截器操作完成后,又会反向执行这些拦截后操过。

下面来看下拦截器执行的流程图,如图6.16所示。其实可以将每个拦截器看成是一个大的容器,而下一个执行的拦截器则包含在该拦截器中,依此类推。拦截器示意图如图6.17所示。

图6.16 拦截器执行流程图

图6.17 拦截器示意图

6.4.5 方法过滤

如果需要为某个Action定义拦截器,那么这个拦截器会拦截该Action内所有的方法。那如果不想让拦截器拦截所有的方法,而只是拦截某些方法,那怎么办呢?这时就可以使用Struts 2提供的拦截器方法过滤特性了。

要实现方法过滤,首先必须让拦截器类继承MethodFilterInterceptor类,该类继承了AbstractInterceptor抽象类,MethodFilterInterceptor类代码如下所示。

        package com.opensymphony.xwork2.interceptor;
        import java.util.Collections;
        import java.util.Set;
        import org.apache.commons.logging.Log;
        import org.apache.commons.logging.LogFactory;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.util.TextParseUtil;
        public abstract class MethodFilterInterceptor extends AbstractInterceptor {
            protected transient Log log = LogFactory.getLog(getClass());
            protected Set excludeMethods = Collections.EMPTY_SET;
            protected Set includeMethods = Collections.EMPTY_SET;
            public void setExcludeMethods(String excludeMethods) {
                this.excludeMethods = TextParseUtil.commaDelimitedStringToSet(excludeMethods);
            }
            public Set getExcludeMethodsSet() {
                    return excludeMethods;
            }
            public void setIncludeMethods(String includeMethods) {
                this.includeMethods = TextParseUtil.commaDelimitedStringToSet(includeMethods);
            }
            public Set getIncludeMethodsSet() {
                    return includeMethods;
            }
            public String intercept(ActionInvocation invocation) throws Exception {
                  if (applyInterceptor(invocation)) {
                      return doIntercept(invocation);
                  }
                  return invocation.invoke();
            }
            protected boolean applyInterceptor(ActionInvocation invocation) {
                  String method = invocation.getProxy().getMethod();
                  // ValidationInterceptor
                 boolean applyMethod = MethodFilterInterceptorUtil.applyMethod(excludeMethods,
                 includeMethods, method);
                 if (log.isDebugEnabled()) {
                    if (! applyMethod) {
                            log.debug("Skipping Interceptor... Method [" + method + "] found in exclude list.");
                    }
                  }
                  return applyMethod;
              }
              protected abstract String doIntercept(ActionInvocation invocation) throws Exception;
        }

继承MethodFilterInterceptor类就必须实现其doIntercept方法。doIntercept方法与intercept方法功能完全相同,用来实现用户的拦截逻辑操作。下面为前面的拦截器添加方法过滤功能,代码如下所示。

        package net.hncu.interceptor;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
        public class MyInterceptor3   extends MethodFilterInterceptor{
              //拦截器的名称
              private String interceptorName;
              public void setInterceptorName(String interceptorName) {
                    this.interceptorName = interceptorName;
              }
              //实现拦截的方法
              protected String doIntercept(ActionInvocation invocation) throws Exception {
                    //拦截器执行时首先打印该语句
                    System.out.println(interceptorName + ":-----拦截前操作-----");
                    //调用下一个拦截器或者直接调用Actionexecute方法
                    String result = invocation.invoke();
                    //拦截器执行后打印该语句
                    System.out.println(interceptorName + ":------拦截后操作------");
                    return result;
              }
        }

MethodFilterInterceptor类中提供了两个方法setExcludeMethods()方法和setIncludeMethods()方法。

❑ setExcludeMethods()方法:设置不需要拦截的方法,将这些方法设置到excludeMethods属性中。excludeMethods属性中的方法都不会被拦截。

❑ setIncludeMethods()方法:设置需要拦截的方法,将这些方法设置到includeMethods属性中。includeMethods属性中的方法都会被拦截。

MethodFilterInterceptor包括includeMethods属性和excludeMethods属性这两个参数,并提供了这两个参数的setter方法。这时就可以通过设定参数值的方式来指定哪些方法不用拦截,哪些方法需要执行,代码如下所示。

          <! -- 配置所有的拦截器 -->
          <interceptors>
                <! -- 配置myInter拦截器,并制定该拦截器实现类 -->
                <interceptor name="myInter3" class="net.hncu.interceptor.MyInterceptor3">
                        <param name="interceptorName">过滤拦截器</param>
                </interceptor>
                <! -- 配置myInterStack拦截器栈 -->
                <interceptor-stack name="myInterStack">
                        <interceptor-ref name="myInter3">
                              <param name="interceptorName">自定义过滤拦截器</param>
                        </interceptor-ref>
                              <interceptor-ref name="defaultStack"></interceptor-ref>
                </interceptor-stack>
          </interceptors>
          <! -- 定义loginAction,其实现类为net.hncu.struts2.action.LoginAction-->
          <action name="login" class="net.hncu.struts2.action.LoginAction">
                <! -- 定义处理结果与视图资源之间的关系-->
                <result name="success">/login_success.jsp</result>
                <result name="error">/login_failure.jsp</result>
                <result name="input">login.jsp</result>
                <! -- 使用myInterStack拦截器栈 -->
                <interceptor-ref name="myInterStack">
                    <param name="myInter3.excludeMethods">execute</param>
                </interceptor-ref>
          </action>

通过excludeMethods参数指定的execute方法不需要被拦截。这时再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从Tomcat服务器控制台看不到该拦截器方法的输出,表示该拦截器并没有拦截Action的execute方法。那如果要设置多个方法不被拦截呢?设置多个方法时,则在方法之间使用逗号分隔,代码如下所示。

          <param name="myInter3.excludeMethods">execute,方法二,方法三</param>

同样可以通过includeMethods参数指定哪些方法需要被拦截,代码如下所示。

          <param name="myInter3.includeMethods">execute</param>

上面的配置表示execute方法需要被拦截。这时再次打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从Tomcat服务器控制台看到该拦截器方法的输出,表示该拦截器拦截了Action的execute方法,如图6.18所示。

图6.18 Tomcat服务器控制台输出显示

如果既在includeMethods参数中指定了该方法需要被拦截,而且又在excludeMethods参数中指定了该方法不需要被拦截呢?代码如下所示。

        <param name="myInter3.includeMethods">execute</param>
        <param name="myInter3.excludeMethods">execute</param>

这时以在includeMethods参数指定为准,也就是该方法会被拦截。

6.4.6 拦截结果监听器

Struts 2还提供了用来拦截结果的监听器,通过该监听器能够得到Action中execute方法返回的结果。前面曾讲过Action中execute方法返回的结果就是一个逻辑视图名,比如success、error等。

可以通过添加一个监听器,得到execute方法返回的结果。首先看如何来创建这个监听器,该监听器类必须实现PreResultListener接口,代码如下所示。

        public interface PreResultListener {
            //提供用来在处理Result之前的操作
              void beforeResult(ActionInvocation invocation, String resultCode);
        }

PreResultListener接口中只有一个方法beforeResult。在自定义的监听器类中必须实现该方法。该方法中有两个参数,其中ActionInvocation类型的参数功能同前面拦截器中的一样,使用该参数可以调用下一个拦截器或Action中的execute方法。因为监听器监听的是execute()方法返回的结果,显然execute()方法已经执行结束了,所以没必要再使用该参数来控制Action。这里重要的是resultCode参数,通过该参数可以得到execute()方法执行返回的结果。下面来新建监听器类,代码如下所示。

        package net.hncu.listener;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.PreResultListener;
        public class MyListener implements PreResultListener{
              //提供用来在处理Result之前的操作
              public void beforeResult(ActionInvocation invocation, String resultCode) {
                    //打印execute返回的结果
                    System.out.println("返回逻辑视图名" + resultCode);
              }
        }

创建完监听器后,还必须在拦截器中注册该监听器。这里选择在MyInterceptor3拦截器中注册该监听器,代码如下所示。

        package net.hncu.interceptor;
        import net.hncu.listener.MyListener;
        import com.opensymphony.xwork2.ActionInvocation;
        import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
        public class MyInterceptor3   extends MethodFilterInterceptor{
              //拦截器的名称
              private String interceptorName;
              public void setInterceptorName(String interceptorName) {
                    this.interceptorName = interceptorName;
              }
              //实现拦截的方法
              protected String doIntercept(ActionInvocation invocation) throws Exception {
                    //将该监听器注册给拦截器
                    invocation.addPreResultListener(new MyListener());
                    //拦截器执行时首先打印该语句
                    System.out.println(interceptorName + ":-----拦截前操作-----");
                    //调用下一个拦截器或者直接调用Actionexecute方法
                    String result = invocation.invoke();
                    //拦截器执行后打印该语句
                    System.out.println(interceptorName + ":------拦截后操作------");
                    return result;
              }
        }

再打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从Tomcat服务器控制台的输出中可以看出监听器实现了监听功能,并打印出Action中execute()方法返回的结果,如图6.19所示。

图6.19 Tomcat服务器控制台输出显示

这里只是简单地打印出execute()方法返回的结果,其实该方法可以根据返回结果进行更多的操作。