下面这段话能完美诠释拦截器的含义:
拦截器是AOP中的概念,它本身是一段代码,可以通过定义“织入点”,来指定拦截器的代码在“织入点”的前后执行,从而起到拦截的作用。Struts2的Interceptor,其拦截的对象是Action代码,可以定义在Action代码之前或者之后执行拦截器的代码。
这是Struts2.3.4中Interceptor结构图:
图中,我们可以发现,Struts2的Interceptor一层一层,把Action包裹在最里面。这样的结构,大概有以下一些特点:
1. 整个结构就如同一个堆栈,除了Action以外,堆栈中的其他元素是Interceptor
2. Action位于堆栈的底部。由于堆栈"先进后出"的特性,如果我们试图把Action拿出来执行,我们必须首先把位于Action上端的Interceptor拿出来执行。这样,整个执行就形成了一个递归调用
3. 每个位于堆栈中的Interceptor,除了需要完成它自身的逻辑,还需要完成一个特殊的执行职责。这个执行职责有3种选择:
1) 中止整个执行,直接返回一个字符串作为resultCode
2) 通过递归调用负责调用堆栈中下一个Interceptor的执行
3) 如果在堆栈内已经不存在任何的Interceptor,调用Action
一个典型的Interceptor的抽象实现类
public abstract class AroundInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { String result = null; before(invocation); // 调用下一个拦截器,如果拦截器不存在,则执行Action result = invocation.invoke(); after(invocation, result); return result; } public abstract void before(ActionInvocation invocation) throws Exception; public abstract void after(ActionInvocation invocation, String resultCode) throws Exception; }
在这个实现类中,实际上已经实现了最简单的拦截器的雏形。或许大家对这样的代码还比较陌生,这没有关系。我在这里需要指出的是一个很重要的方法invocation.invoke()。这是ActionInvocation中的方法,而ActionInvocation是Action调度者,所以这个方法具备以下2层含义:
1. 如果拦截器堆栈中还有其他的Interceptor,那么invocation.invoke()将调用堆栈中下一个Interceptor的执行。
2. 如果拦截器堆栈中只有Action了,那么invocation.invoke()将调用Action执行。
所以,我们可以发现,invocation.invoke()这个方法其实是整个拦截器框架的实现核心。基于这样的实现机制,我们还可以得到下面2个非常重要的推论:
1. 如果在拦截器中,我们不使用invocation.invoke()来完成堆栈中下一个元素的调用,而是直接返回一个字符串作为执行结果,那么整个执行将被中止。
2. 我们可以以invocation.invoke()为界,将拦截器中的代码分成2个部分,在invocation.invoke()之前的代码,将会在Action之前被依次执行,而在invocation.invoke()之后的代码,将会在Action之后被逆序执行。
由此,我们就可以通过invocation.invoke()作为Action代码真正的拦截点,从而实现AOP。
Struts2的interceptor工作原理:
从源码中,我们可以看到,Struts2的Action层的4个不同的层次,他们分别是:拦截器(Interceptor)、Action、PreResultListener和Result。每个层次都具备了高度的扩展性和插入点,使得程序员可以在任何喜欢的层次加入自己的实现机制改变Action的行为。
interceptor是由actionInvocation驱动执行的。AroundInterceptor的子类会按照顺序被ActionInvocation调用执行。
intercept()方法对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke()执行结束。这样,Interceptor又会按照刚开始执行的逆向顺序依次执行结束。
一个有序链表,通过递归调用,变成了一个堆栈执行过程,将一段有序执行的代码变成了2段执行顺序完全相反的代码过程,从而巧妙地实现了AOP。这也就成为了Struts2的Action层的AOP基础。
interceptor是一个栈结构,先进后出,递归与否与执行顺序无关。
当interceptor1执行调用interceptor2的时候,interceptor1并没有执行完毕,而是被暂时搁置了,因此等到action执行完毕的时候,必然是逆序执行之前被搁置的所有代码。
这是数据结构的知识。
下面是struts2.3.4中struts-default.xml里配置的interceptor:
拦截器名称
|
功能描述
|
alias
|
在不同请求之间将请求参数在不同名字间进行转换,请求内容不变
|
chain
|
让前一个Action的属性可以被后一个Action访问,需要和 |
checkbox
|
添加了checkbox自动处理代码,将没有选中的checkbox的内容设定为false,而html默认情况下不提交没有选中的checkbox
|
cookie
|
使用配置的name,value来是指cookies
|
conversionError
|
将类型转换错误从ActionContext中取出,并转换成Action实例中FieldError错误
|
createSession
|
自动的创建HttpSession,用来为需要使用到HttpSession的拦截器服务
|
debugging
|
提供不同的调试用的页面来展现内部的数据状况。
|
execAndWait
|
在后台执行Action,同时将用户带到一个中间的等待页面。
|
exception
|
将捕捉到的异常信息输出到指定的异常显示页面
|
fileUpload
|
负责解析表单中文件域的内容从而提供文件上传功能
|
i18n
|
记录用户选择的locale到session中,从而实现消息资源的国际化
|
logger
|
记录日志输出Action的名字
|
store
|
存储或者访问实现ValidationAware接口的Action类出现的消息,错误,字段错误等
|
modelDriven
|
如果一个类实现了ModelDriven,将getModel得到的结果压入ValueStack中
|
scopedModelDriven
|
如果一个Action实现了ScopedModelDriven,则这个拦截器会从相应的Scope中取出model调用Action的setModel方法将其放入Action内部
|
params
|
将表单或者HTTP请求中的参数值赋给Action实例中相应的属性
|
prepare
|
如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法
|
scope
|
将Action状态存入session和application的简单方法
|
servletConfig
|
提供访问HttpServletRequest和HttpServletResponse的方法,以Map的方式访问
|
staticParams
|
从struts.xml文件中将 |
roles
|
确定用户是否具有JAAS指定的Role,否则不予执行
|
timer
|
输出Action执行的时间,在做Action性能分析时很有用
|
token
|
通过Token来避免双击
|
tokenSession
|
和Token Interceptor一样,不过双击的时候把请求的数据存储在Session中
|
validation
|
使用action-validation.xml文件中定义的内容校验提交的数据
|
workflow
|
调用Action的validate方法,一旦有错误返回,重新定位到INPUT画面
|
N/A
|
从参数列表中删除不必要的参数
|
profiling
|
通过参数激活profile
|
multiselect
|
像复选框checkbox拦截器一样,程序检测到没有值被选中,将没有选中的option的值设定为空的参数
|
下面是一个登录的interceptor:
package com.chou.jtsms.ui.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; /** * @author chou * @version 1.0 2012/04/21 * 拦截除登录以外所有请求判断是否已经登陆过 * */ public class LoginInterceptor implements Interceptor { private static final long serialVersionUID = -3224907200600094135L; public void destroy() { } public void init() { } /** * 如果是/login.action或者/login请求就跳转到LoginAction处理 * 如果不是登录请求就判断是否登陆过,如果登陆成功过还需要再验证一遍,通过namespace来判断该用户应有的权限 */ public String intercept(ActionInvocation invocation) throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpSession session = ServletActionContext.getRequest().getSession(); String path = request.getServletPath(); String[] namespaces = StringUtils.split(request.getRequestURI(), "/"); String namespace = ""; if (namespaces.length>1) {//截取的namespace应该除去项目名,所以至少数组长度为2 namespace = namespaces[1]; } if ("/login".equals(path) || "/login.action".equals(path)){ return invocation.invoke(); }else { UserDTO userDTO = (UserDTO) session.getAttribute("logininfo"); if(userDTO == null){ return "noSession"; } if(userDTO.getCansend().equals("1")){//必须有发短信权限 if(namespace.equals("admin") && userDTO.getKind().equals(UserDTO.ADMIN)){ return invocation.invoke(); } if(namespace.equals("safeadmin") && userDTO.getKind().equals(UserDTO.SAFE_ADMIN)){ return invocation.invoke(); } if(namespace.equals("safe") && userDTO.getKind().equals(UserDTO.SAFE_USER)){ return invocation.invoke(); } if(namespace.equals("common") && userDTO.getKind().equals(UserDTO.COMMON)){ return invocation.invoke(); } } } return "noSession"; } }
......