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 + ":拦截前操作"); //调用下一个拦截器或者直接调用Action的execute方法 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> <! -- 定义login的Action,其实现类为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> <! -- 定义login的Action,其实现类为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> <! -- 定义login的Action,其实现类为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> <! -- 定义login的Action,其实现类为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元素,并指定参数名和参数值,代码如下所示。
<! -- 定义login的Action,其实现类为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属性为拦截器名+“.”+参数名,并指定该参数的参数值,就可以覆盖在配置拦截器时指定的参数值了,代码如下所示。
<! -- 定义login的Action,其实现类为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 + ":-----拦截前操作-----"); //调用下一个拦截器或者直接调用Action的execute方法 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> <! -- 定义login的Action,其实现类为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 + ":-----拦截前操作-----"); //调用下一个拦截器或者直接调用Action的execute方法 String result = invocation.invoke(); //拦截器执行后打印该语句 System.out.println(interceptorName + ":------拦截后操作------"); return result; } }
再打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从Tomcat服务器控制台的输出中可以看出监听器实现了监听功能,并打印出Action中execute()方法返回的结果,如图6.19所示。
图6.19 Tomcat服务器控制台输出显示
这里只是简单地打印出execute()方法返回的结果,其实该方法可以根据返回结果进行更多的操作。