![JavaWeb从入门到精通(视频实战版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/964/602964/b_602964.jpg)
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所示。
![](https://epubservercos.yuewen.com/7905DB/3590815803508101/epubprivate/OEBPS/Images/figure_0113_0002.jpg?sign=1739001841-qivayQGCt06n5eTwKoBlUSQDqOSGnGeK-0-f85818dde1264fb9c904533b9c2abf49)
图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 }
![](https://epubservercos.yuewen.com/7905DB/3590815803508101/epubprivate/OEBPS/Images/figure_0114_0001.jpg?sign=1739001841-woyui6dJF0pd16pf4oVKlKQUXMpsxSKG-0-cfca09cbb8d72b6a104bc2bc444e3e59)
图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 内置拦截器表
![](https://epubservercos.yuewen.com/7905DB/3590815803508101/epubprivate/OEBPS/Images/figure_0115_0001.jpg?sign=1739001841-9C6IUCTdqaRTKsYYlVGDgnLtVBdFqpmU-0-47a4f6bad82af6a2f4518dbcee1767e8)
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栈表
![](https://epubservercos.yuewen.com/7905DB/3590815803508101/epubprivate/OEBPS/Images/figure_0119_0001.jpg?sign=1739001841-YRIvc6hrB6qakpGVEQHzSTUXt0Xkcv5R-0-765cc7d915acca6bfdae034f206e1f01)
图5.16所示是一个interceptor栈的构成示意图。
![](https://epubservercos.yuewen.com/7905DB/3590815803508101/epubprivate/OEBPS/Images/figure_0120_0002.jpg?sign=1739001841-lYvW4c8r2K545TUkjXrulJKD1HyA4hq7-0-e2f9953aa856f1873e23939623941331)
图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无状态的(不要用实例的属性来保存变量),否则会引起并发问题。