拦截器简述
拦截器是一组动态拦截Action调用的对象。拦截器的处理代码可以定义在action执行前或者执行后。同时,拦截器能够拦截一个Action的执行。拦截器可以将一些通用功能封装成可重用形式以供一个Action或多个 Actions使用。
拦截器必须是无状态的,原因是Struts 2不能保证为每一个请求或者Action创建一个实例,所以如果拦截器带有状态,会引发并发问题。不要使用在API提供的ActionInvocation之外的任何东西。
在概念上,interceptors相近于Servlet 的filters或者JDKs的Proxy类。可以把struts2理解成一个空容器,而大量的内建拦截器完成该框架的大部分操作。比如,params拦截器负责解析HTTP请求的参数,并设置Action的属性;servlet-config拦截器直接将HTTP请求中的HttpServletRequest实例和HttpServletResponse实例传给Action;fileUpload拦截器负责解析请求参数中的文件域,并将一个文件域设置为Action的三个属性等等。这些通用操作是通过struts2的内建拦截器完成的。
struts2拦截器是可插拔式的,如果需要使用某个拦截器,只需要在配置文件中应用该拦截器即可,如果不使用该拦截器只需要在配置文件中取消该拦截器,不用重新部署应用。
struts2拦截器由struts-default.xml、struts.xml等配置文件进行管理,开发者可以很容易地扩展自己的拦截器。
如前面所说开发中存在一些通用逻辑,如解析请求参数、类型转换、将请求参数封装成DTO(Data Transfer Object)、执行输入校验、解析文件上传表单中的文件域、防止表单的多次提交等。struts2把大部分核心控制器需要完成的工作功能分开定义,每个拦截器完成一个功能。这些拦截器可以自由选择,灵活组合(甚至不用struts2的任何拦截器),需要哪些拦截器只需在struts.xml文件中指定使用该拦截器即可。
在struts.xml文件中配置Action时,在package里继承struts-default包,包内的大量通用功能的拦截器就会起作用。
<package name="default" extends="struts-default">
<interceptors>
<interceptor name="timer" class=".."/>
<interceptor name="logger" class=".."/>
</interceptors>
<action name="login"
class="tutorial.Login">
<interceptor-ref name="timer"/>
<interceptor-ref name="logger"/>
<result name="input">login.jsp</result>
<result name="success"
type="redirectAction">/secure/home</result>
</action>
</package>
在web.xml文件中定义核心Filter。
<!-- 定义struts2核心Filter -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<!-- 让struts2的核心Filter拦截所有action请求 -->
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
当StrutsPrepareAndExecuteFilter拦截到用户请求后,大量拦截器将会对用户请求进行处理,然后才会调用用户开发的Action实例的方法处理请求。拦截器与Action之间的关系如下图所示。
struts2内建的拦截器
struts2内建了大量的拦截器,这些拦截器以name-class对的形式配置在struts-default.xml文件中,其中name是拦截器的名字,就是以后使用该拦截器的唯一表示;class指定了该拦截器的实现类,程序定义的package继承了struts2的默认struts-default包,就可以使用包内定义的拦截器,不用自己定义拦截器。
自定义拦截器
内建拦截器实现了struts2大部分功能,大部分Web应用的通用功能,都可以通过直接使用这些内建拦截器完成,但有一些系统逻辑相关的通用相关功能,要通过自定义拦截器来实现。
Interceptor接口
开发自定义拦截器类,要实现com.opensymphony.xwork2.interceptor.Interceptor接口,该接口的定义代码如下。
public interface Interceptor extends Serializable {
//销毁该拦截器之前的回调方法
void destroy();
//初始化该拦截器的回调方法
void init();
//拦截器实现拦截的逻辑方法
String intercept(ActionInvocation invocation) throws Exception;
}
init()方法在该拦截器被实例化之后,执行拦截之前被回调。每个拦截器的init()方法只执行一次。所以该方法体主要用于初始化资源,例如数据库连接等。
destroy()对应于init()方法,在拦截器实例被销毁之前,系统将回调该拦截器的destroy方法,用于销毁在init()方法里打开的资源。
inercept(ActionInvocation invocation)方法是用户需要实现的拦截动作,像Action的execute方法,intercept方法返回一个字符串作为逻辑视图。如果该方法直接返回一个字符串,系统会跳转到该逻辑视图对应的实际 视图资源,不会调用被拦截的Action。该方法的ActionInvocation参数包含了被拦截的Action引用,可以通过调用该参数的invoke方法,将控制权转给下一个拦截器,或者转给Action的execute方法。
AbstractInterceptor类
AbstractInterceptor类实现了Interceptor接口。
AbstractInterceptor类提供了init()和destory()方法的空实现,如果实现的拦截器不需要打开资源则可以无需实现这两个方法。
定义和使用拦截器
自定义一个拦截器需要三步:
1. 自定义一个实现Interceptor接口(或者继承自AbstractInterceptor)的类。
2. 在strutx.xml中注册上一步中定义的拦截器。
3. 在需要使用的Action中引用上述定义的拦截器,为了方便也可将拦截器定义为默认的拦截器,这样在不加特殊声明的情况下所有的Action都被这个拦截器拦截。
自定义拦截器
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import java.util.*;
import com.afy.app.action.*;
public class SimpleInterceptor extends AbstractInterceptor{
// 简单拦截器的名字
private String name;
// 为该简单拦截器设置名字的setter方法
public void setName(String name){
this.name = name;
}
public String intercept(ActionInvocation invocation) throws Exception{
// 取得被拦截的Action实例
LoginAction action = (LoginAction)invocation.getAction();
// 打印执行开始的时间
System.out.println(name + " 拦截器的动作---------" +
"开始执行登录Action的时间为:" + new Date());
// 取得开始执行Action的时间
long start = System.currentTimeMillis();
// 执行该拦截器的后一个拦截器
// 如果该拦截器后没有其他拦截器,则直接执行Action的被拦截方法
String result = invocation.invoke();
// 打印执行结束的时间
System.out.println(name + " 拦截器的动作-------" + "执行完登录Action的时间为:" + new Date());
long end = System.currentTimeMillis();
System.out.println(name + " 拦截器的动作---------" + "执行完该Action的时间为" + (end - start) + "毫秒");
return result;
}
}
在strutx.xml中注册上一步中定义的拦截器,在需要使用的Action中引用上述定义的拦截器。
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- 通过常量配置该应用所使用的字符集-->
<constant name="struts.i18n.encoding" value="GBK"/>
<!-- 配置本系统所使用的包 -->
<package name="lee" extends="struts-default">
<!-- 应用所需使用的拦截器都在该元素下配置 -->
<interceptors>
<!-- 配置mySimple拦截器 -->
<interceptor name="mySimple"
class="org.crazyit.app.interceptor.SimpleInterceptor">
<!-- 为拦截器指定参数值 -->
<param name="name">简单拦截器</param>
</interceptor>
</interceptors>
<action name="login" class="org.crazyit.app.action.LoginAction">
<result name="error">/WEB-INF/content/error.jsp</result>
<result>/WEB-INF/content/welcome.jsp</result>
<!-- 配置系统的默认拦截器 -->
<interceptor-ref name="defaultStack"/>
<!-- 应用自定义的mySimple拦截器 -->
<interceptor-ref name="mySimple">
<param name="name">改名后的拦截器</param>
</interceptor-ref>
</action>
<action name="*">
<result>/WEB-INF/content/{1}.jsp</result>
</action>
</package>
</struts>
拦截器的实现原理
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。
我们之所以能够如此灵活地使用拦截器,完全归功于“动态代理”的使用。动态代理是代理对象根据客户的需求做出不同的处理。对于客户来说,只要知道一个代理对象就行了。那Struts2中,拦截器是如何通过动态代理被调用的呢?当Action请求到来的时候,会由系统的代理生成一个Action的代理对象,由这个代理对象调用Action的execute()或指定的方法,并在struts.xml中查找与该Action对应的拦截器。如果有对应的拦截器,就在Action的方法执行前(后)调用这些拦截器;如果没有对应的拦截器则执行Action的方法。其中系统对于拦截器的调用,是通过ActionInvocation来实现的。
与过滤器的区别
过滤器可以简单理解为“取你所想取”,忽视掉那些你不想要的东西;拦截器可以简单理解为“拒你所想拒”,关心你想要拒绝掉哪些东西,比如一个BBS论坛上拦截掉敏感词汇。
1. 拦截器是基于java反射机制的,而过滤器是基于函数回调的。
2. 过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3. 拦截器只对action起作用,而过滤器几乎可以对所有请求起作用。
4. 拦截器可以访问action上下文、值栈里的对象,而过滤器不能。
5. 在action的生命周期里,拦截器可以多起调用,而过滤器只能在容器初始化时调用一次。