拦截器(Interceptor)是Struts2的核心和基础,有许多功能都是构建于它之上,如国际化、转换器、校验等。
截拦器是AOP的一种实现,底层通过动态代理模式完成
struts2拦截器初识
客户端向Web应用发送HttpServletRequest请求,请求经过各种过滤器的过滤并传递给核心控制器FilterDispatcher;
FilterDispatcher会调用Action映射器ActionMapper,将用户请求转发到对应的业务逻辑控制器,此业务逻辑控制器并不是用户实现的业务控制器,而是Struts2创建的 Action代理ActionProxy,用户实现的Action类仅仅是Struts 2的ActionProxy的代理目标;
ActionProxy通过Configuration Manager在struts.xml配置文件中搜索被请求的Action类。 ActionProxy创建一个被请求Action的实例,用来处理客户端请求信息;
如果在struts.xml配置文件存在于被请求Action相关的拦截器配置,那么该Action实例被调用的前后,这些拦截器将会被执行;
Action对请求处理完毕后返回一个逻辑视图,由此逻辑视图找到相应物理视图(可以说JSP、Velocity模板、FreeMarker模板等)返回给客户端。
用户实现的Action类仅仅是Struts 2的ActionProxy的代理目标;用户的Action类无需访问HttpServletRequest对象——因为用户实现的业务控制器并没有与Servlet API耦合,但用户的请求数据又是包含在HttpServletRequest对象里,怎么办呢?
Struts 2框架则提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入到Action中,并回调Action的execute方法来处理用户请求
拦截器可以让你在Action被执行之前或之后进行一些处理。同时,拦截器也可以让你将通用的代码模块化并作为可重用的类。
官方定义:拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义一段代码,使其在一个Action执行之前或之后被调用执行,也可以在一个Action执行前阻止其执行。同时也是提供了一种可以提取Action中可重用部分的方式。
简单地说:拦截器(Interceptor)是一个实现了一定功能的类,以一种可插拔的方式被定义在某个Action执行的之前或之后,用来完成特定的功能。当为Action添加该功能的时候就配置该拦截器,当为Action减少该功能的时候就取消配置该拦截器。
拦截器栈(Interceptor Stack,在WebWork中称为拦截器链Interceptor Chain)。拦截器栈就是一堆拦截器的集合体,它将这些拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器栈中的拦截器就会按其之前定义的顺序被调用。
拦截器原理
•ActionInvocation是Action调度者,invoke()是ActionInvocation中的方法,所以这个方法具备以下两层含义:
1. 如果拦截器堆栈中还有其他的Interceptor,那么invoke()将调用堆栈中下一个Interceptor的执行。
2. 如果拦截器堆栈中只有Action了,那么invoke()将调用Action执行。
图中,Struts2的Interceptor一层一层,把Action包裹在最里面。这样的结构,大概有以下一些特点:
整个结构就如同一个堆栈,除了Action以外,堆栈中的其他元素是Interceptor
Action位于堆栈的底部。由于堆栈“先进后出”的特性,如果我们试图把Action拿出来执行,我们必须首先把位于Action上端的Interceptor拿出来执行。这样,整个执行就形成了一个递归调用。
每个位于堆栈中的Interceptor,除了需要完成它自身的逻辑,还需要完成一个特殊的执行职责。这个执行职责有3种选择:
1) 中止整个执行,直接返回一个字符串作为resultCode
2) 通过递归调用负责调用堆栈中下一个Interceptor的执行
3) 如果在堆栈内已经不存在任何的Interceptor,调用Action
Struts 2的拦截器实现相对简单。当请求Struts 2的某Action时,Struts 2会查找配置文件,并根据配置实例化其相对应的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。
Struts2的拦截器结构的设计,实际上是一个典型的责任链模式的应用。首先将整个执行划分成若干相同类型的元素,每个元素具备不同的逻辑责任,并将它们纳入到一个链式的数据结构中(我们可以把堆栈结构也看作是一个递归的链式结构),而每个元素又有责任负责链式结构中下一个元素的执行调用。
这样的设计,从代码重构的角度来看,实际上是将一个复杂的系统,分而治之,从而使得每个部分的逻辑能够高度重用并具备高度可扩展性。所以,Interceptor结构实在是Struts2/Xwork设计中的精华之笔。
struts2内置拦截器
Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特性。这些内置的拦截器在struts-default.xml中配置。
配置和使用拦截器
在struts.xml中部署拦截器,通过<interceptors> </interceptors> 标签对内使用<interceptor/> 标签引入具体拦截器。
部署好拦截器后,就可以在<action> </action>标签对内使用<interceptor-ref…/> 标签引入所需拦截器为Action应用添加相应的功能了。
需要注意的是,如果为Action指定了一个拦截器,则系统默认的拦截器栈将会失去作用,导致Struts2无法正常运行,为了继续使用默认拦截器,所以需在配置文件中手动引入默认拦截器。
使用<param> </param> 标签对可为拦截器指定其所需参数。】
<package name="default" extends="struts-default">
<interceptors>
<interceptor name=“拦截器名1” class=“拦截器实现类1"/>
<interceptor name=“拦截器名2” class=“拦截器实现类2”>
< param name= “参数名” >参数值</param>
</interceptor>
// …………其他拦截器
</interceptors>
<action name=“Action名” class=“Action类">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name=“拦截器名1”/>
<interceptor-ref name="拦截器名2">
< param name= “参数名” >参数值</param>
</interceptor-ref>
// …………Action其他配置
</action>
</package>
配置和使用拦截栈
将多个截拦器组合成一个截拦器栈
拦截器栈部署与拦截器略有不同,一个拦截器栈可以包括一个或多个拦截器,也可以包括其他拦截器栈。通过<interceptor-stack> </ interceptor-stack> 标签部署拦截器栈。
与使用拦截器一样,在<action> </action>标签对内使用<interceptor-ref…/> 标签引入所需拦截器栈为Action应用添加相应的功能。
<package name="default" extends="struts-default">
<interceptors>
<interceptor name=“拦截器名1” class=“拦截器实现类1"/>
<interceptor name=“拦截器名2” class=“拦截器实现类2”/>
<interceptor name=“拦截器名3” class=“拦截器实现类3”/>
<interceptor name=“拦截器名3” class=“拦截器实现类4”/>
// …………其他拦截器
<interceptor-stack name=“myStack1”> //自定义拦截器栈1
<interceptor-ref name=“拦截器名1”/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
<interceptor-stack name=“myStack2”> //自定义拦截器栈2
<interceptor-ref name=“拦截器名2”/>
<interceptor-ref name=“拦截器名3”/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack1"/>
<action name=“Action名” class=“Action类">
<interceptor-ref name=“myStack2"/>
<interceptor-ref name=“拦截器名4”/>
// …………Action其他配置
</action>
</package>
修改
package的默认拦截器会应用到package中的所有Action。
自定义拦截器
做为“框架(framework)”,可扩展性是不可或缺的。虽然,Struts 2为我们提供了丰富的拦截器实现,但是这并不意味我们失去创建自定义拦截器的能力,恰恰相反,在Struts 2自定义拦截器非常容易。
自定义拦截器
拦截器对Action中方法进行拦截
自定义拦截器及其使用:
1.直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor
2.或者继承类com.opensymphony.xwork2.interceptor.AbstractInterceptor
3.通过<interceptor>元素来定义拦截器
4.通过<interceptor-ref>元素来使用拦截器
Interceptor接口中定义了三个方法:
void init():在拦截器初始化之后,在执行拦截之前,系统调用该方法。对于一个拦截器而言,init方法只会被调用一次。
String intercept(ActionInvocation invocation) throws Exception:该方法是拦截器的拦截方法,返回一个字符串,系统将会跳转到该字符串对应的视图资源。该方法的ActionInvocation参数包含了被拦截的Action的引用,可以通过该对象的invoke方法,将控制权转给下一个拦截器或者转给Action的execute方法。
void destroy():该方法与init方法对应,在拦截器示例被销毁之前,系统将会调用该方法。
public class CustomInterceptor1 implements Interceptor {
public String intercept(ActionInvocation invocation) throws Exception {
/*Action前置拦截
*/
System.out.println(“========我是一个拦截器========”);
String result = invocation.invoke();
/*Action后置拦截
*/
System.out.println("========我是一个拦截器========");
return result;
}
}
•以invocation.invoke()为界,将拦截器中的代码分成2个部分,在invocation.invoke()之前的代码,将会在Action之前被依次执行,而在invocation.invoke()之后的代码,将会在Action之后被逆序执行。
所有的Struts 2的拦截器都直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor。
除了Interceptor接口外,Struts2中还提供了一个AbStractInterceptor类,该类提供了一个init和destroy方法的空实现。如果不需要就不用重写这两个方法,可见继承AbstractInterceptor类可以让我们构建拦截器时变得更简单。
拦截器对Action中的方法进行拦截
某Action添加拦截器后,则该Action中所有的方法都要受到此拦截器的影响,有时候并不是所有的方法都需要添加拦截器功能,此时可以通过设置相应参数来对Action中的某些方法指定拦截器。
Struts2提供MethodFilterInterceptor类,该类是AbstractInerceptor的子类,可以实现对Action方法的拦截.
自定义拦截器时必须继承MethodFilterinterceptor,它接受两个参数:
excludeMethods:设置不会被拦截器所拦截的方法,多个方法间用逗号隔开
includeMethods:设置被拦截器拦截的方法,多个方法间用逗号隔开
对运行时间较长的Action 视图界面的处理
通过executeandwait拦截器来解决请求处理时间过长的问题。注意此拦截器必须放在所有拦截器的最后。
1、增加拦截器
<interceptor-ref name="execAndWait">
<!-- 等待时间,执行时间没有超过此值,将不显示等待画面 (毫秒)-->
<param name="delay">1000</param>
<!-- 间隔检查时间,检查后台进程有没有执行完毕,如果完成了它就立刻返回,不用等到等待,用户不会看到等待画面 -->
<param name="delaySleepInterval">50</param>
</interceptor-ref>
includeMethods比excludeMethods优先级高
2、增加result
<result name=“wait”>wait.jsp</result>
如果没有找到“wait”结果,WebWork会自动生成一个wait结果(\com\opensymphony\webwork\interceptor\wait.ftl).这个结果是用FreeMarker做的,所以需要Freemarker支持才能正常工作。
如果你不想在程序中加入FreeMarker,那就必须自己实现一个wait结果。这一般来说是有必要的,因为默认的wait页面很简单。
重复提交的解决办法
一、使用tokenInterceptor拦截器
1、在提交页面如登录页面中加入token标记<s:token></s:token>
2、在struts.xml 中配置tokenInterceptor拦截器和配置invoke.token信息。
<result name="success">/login/welcome.jsp</result>
<result name="invalid.token">/login/wrong.jsp</result>
<interceptor-ref name="defaultStack"/>
<!-- 配置token拦截器-->
<interceptor-ref name="token"/>
二、使用tokenSessionInterceptor拦截器
1、在提交页面如登录页面中加入token标记<s:token></s:token>
2、在struts.xml 中配置tokenInterceptor拦截器:
<interceptor-ref name= "tolenSession"/>
当检查到重复提交后,总会返回到第一次成功提交的页面。