struts2提供面向切面(AOP)编程的机制,拦截器便是一种成熟的AOP编程思想的实现,它提供一种机制使开发者能把相对独立的代码抽象出来,配置到action前后执行。拦截器interceptor类似于Filter,在执行action方法前后执行。
就像多个Filter组成了Filter链,多个拦截器也组成了拦截器链。struts2中称拦截器链为拦截器栈。拦截器栈是按顺序配置的多个拦截器,在执行action前后被依次调用。
拦截器是用于在Action或者某个方法调用之前进行特定的处理的对象,类似于过滤器,是一个特殊的Servlet。拦截器主要用于在调用Action或Action方法之前做输入校验、权限验证,字符编码转换等一些通用操作。多个拦截器构成拦截器栈。Struts2中有一个默认的拦截器栈,在每个Action调用之前执行。
拦截器有三个对象:被拦截对象,拦截器对象本身,代理。
真正执行的不想目标对象,而代理。
拦截器要实现Interceptor接口,默认实现拦截器时继承AbstractInterceptor类,而很少直接继承Interceptor接口。
1、类拦截器和方法拦截器:
(1)、针对Action进行拦截处理的拦截器是类拦截器,是一种粗粒度的拦截器。
拦截器是一种特殊的Servlet,与过滤器类似,其init()和destroy()方法只执行一次。而intercepte()方法是action每次执行时都会执行一次。
(2)、只对Action中某个特定方法进行拦截处理的拦截器就是方法拦截器,方法拦截器与类拦截器不同,它继承MethodFilterInterceptor类,实现doIntercepte()方法。
2、配置类拦截器
(1)、在Struts2的配置文件struts.xml中的<package>
元素标签下添加:
<interceptors>
<interceptor name=”拦截器名称” class=”拦截器的全路径”>
<!--给拦截器配置参数,如果拦截器不需要参数,下面这句就不需要了-->
<param name=”参数名称”>参数值</param>
</interceptor >
…
</interceptors>
注意:在配置拦截器时传递了参数,需要在拦截器中定义和参数名称相同的属性,并且生成set和get方法。定义时为默认参数,拦截器引用时参数为动态参数,动态参数覆盖默认参数。
(2)、对Action引入拦截器:
在Struts2的配置文件struts.xml中要引入拦截器的Action的<action>
元素标签下添加:
<interceptor-ref name=”拦截器名称”/>
<!-- 默认拦截器-->
<interceptor-ref name="defaultStack" />
3、配置方法拦截器:
(1).在Struts2的配置文件struts.xml中的<package>
元素标签下添加内容,与配置类拦截器相同。
(2)、Action中引入方法拦截器:
在Struts2的配置文件struts.xml中要引入拦截器的Action的<action>
元素标签下添加:
<interceptor-ref name=”方法拦截器名称”>
<!--对该Action要引入的方法拦截器的方法-->
<param name=”includeMethods”>Action方法1,Action方法2,…</param>
<!-- 对该Action要排除的方法拦截器的方法-->
<param name=”excludeMethods”>Action方法3,Action方法4,…</param>
</interceptor-ref>
注意:include的优先级高于exclude。
4、配置拦截器栈
多个拦截器构成拦截器栈。
(1).拦截器栈定义方法:
在Struts2的配置文件struts.xml中的<package>
元素标签下添加:
<interceptor-stack name=”拦截器栈名称”>
<interceptor-ref name=”拦截器名称”>
<interceptor-stack>
注意:拦截器栈中可以包含拦截器和拦截器栈。
(2)、在Action中引入拦截器栈:
和引入拦截器相同
<interceptor-ref name=”拦截器栈名称”/>
<!-- 默认拦截器-->
<interceptor-ref name="defaultStack" />
注意:,Struts2的Action配置了一个默认的拦截器栈,如果在Action中引入了自定义的拦截器或者拦截器栈之后,默认的Struts2默认的拦截器和拦截器栈就不会自动添加到该Action上,如果想使用默认的拦截器和拦截器栈就必须手动添加。
==========================================
实例一、timer计时拦截器
==========================================
timer拦截器能够统计每个action方法运行所需要的时间。它的原理是在action执行前开启记录一下时间,action执行后再记录一下时间,然后计算两个时间差,并将时间差打印出来(在Filter中也能实现类似的功能)。下面使用timer拦截器统计一个action的运行时间。
TimerAction.java
package com.lmb.struts2.action;
import com.opensymphony.xwork2.ActionSupport;
public class TimerAction extends ActionSupport{
@Override
public String execute() throws Exception {
Thread.sleep(1000);//线程沉睡一秒钟
return SUCCESS;//返回成功页面
}
}
该action非常简单,让当前线程沉睡1秒钟,模拟一下繁忙的业务。action配置中要配置上timer拦截器:
struts.xml
<package name="main" extends="struts-default"><!-- 定义一个package-->
<!-- 使用timer.action测试拦截器 -->
<action name="timer" class="com.lmb.struts2.action.TimerAction" >
<interceptor-ref name="timer"></interceptor-ref><!-- 配置Timer拦截器 -->
<interceptor-ref name="defaultStack" />
<result name="success">/timerSuccess.jsp</result>
</action>
</package>
注意:package需要继承struts-default才能使用struts2里的拦截器,因为time计时器是配置在struts-default包里的。
部署后访问http://localhost:8080/struts2/timer.action,控制台会输出如下信息:
信息:Executed action [//timer!execute] took 1032 ms.
显示的是执行timer!execute所消耗的时间(1032毫秒)。第一次访问时由于需要加载拦截器,消耗的时间可能要多一些。显示action执行的时间,在粗略的估计系统性能时很有用。
==========================================
实例二、token防重复提交拦截器
==========================================
如果网络不好,提交数据后可能就没有反应了。这时候用户不知道数据是否提交到服务器了,往往还会单击提交按钮再提交一次。对于某一些应用,例如:网络购物、银行转账等,重复提交会导致非常严重的后果。
token拦截器用于保证表单只被提交一次,如果再次提交表单,拦截器会拦截该请求,防止提交重复数据。它的原理是,显示表单的时候,在session中对该表单做一个标记,该标记只能使用一次,使用过后就失效了,从而保证表单最多只提交一次数据。重复提交数据会因为标记已失效而提交失败。
下面用token拦截器测试下面的TokenAction,保证在提交该表单数据时只能提交一次。TokenAction中有一个NAMES属性,用来存储提交过的所有数据。重复提交数据时,如果能提交进来,NAMES中将会积累重复的数据,以此来判断数据时候被重复提交。
TokenAction.java
package com.lmb.struts2.action;
import java.util.ArrayList;
import java.util.List;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;
public class TokenAction extends ActionSupport{
//static属性,模拟数据库,用来存储提交过的所有数据
public static final List<String> NAMES=new ArrayList<String>();
private String name;
@Override
public String execute(){
NAMES.add(name);//添加到NAMES中
return Action.SUCCESS;//返回成功页面
}
public void setName(String name){//name属性的setter方法
this.name=name;
}
public static List<String> getNAMES(){//name属性的getter方法
return NAMES;
}
}
action代码没有什么特别之处,就是普通的action,没有任何特殊处理。token拦截器是配置式使用的,只需要在action中配置一下即可。
struts.xml
<action name="token" class="com.lmb.struts2.action.TokenAction" >
<!-- token拦截器 -->
<interceptor-ref name="token" />
<!-- basicStack拦截 -->
<interceptor-ref name="basicStack" />
<interceptor-ref name="defaultStack" />
<result>/tokenSuccess.jsp</result>
<result name="input">/tokenInput.jsp</result>
<result name="invalid.token">/tokenInvalid.jsp</result><!-- 如果重复提交数据,struts2将转到该页面 -->
</action>
代码中配置了token拦截器和basicStack拦截器栈。basicStack是几个常用的拦截器的集合。basicStack是必须配置的拦截器,如果不配置,struts2就不会自动向action赋值。action中还必须配置一个名为invalid.token的result页面。如果重复提交数据,struts2将转到该页面中,该结果也是必须要配置的。
tokenInput.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="/struts-tags" prefix="struts" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<body>
<struts:form action="token">
<struts:token /> <!-- 非常重要的token标记 -->
<struts:label value="避免重复输入"></struts:label>
<struts:textfield name="name" label="请输入姓名"></struts:textfield>
<struts:submit name="提交"></struts:submit>
</struts:form>
</body>
</html>
JSP输入页面也需要添加一个<struts:token />
标签。该标签会在session中生成所在表单的标识,该标签一定要添加在Form中,否则该拦截器无效。
输入姓名那个,然后单击“提交“按钮提交数据后,会转到成功页面,并提示已经提交的所有数据。成功页面的代码如下:
tokenSuccessjsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="/struts-tags" prefix="struts" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<body>
已输入的姓名:<struts:property value="NAMES"/><br><br>
<a href="tokenInput.action"><<重新输入</a>
</body>
</html>
提交完成后,单击浏览器的”刷新“按钮。浏览器会弹出对话框,询问是否要重新提交数据。单击”是“按钮,浏览器会重新提交数据,然后转到tokenInvalid.jsp页面,提示输入无效。
tokenInvalid.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="/struts-tags" prefix="struts" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<body>
输入无效。<br>
<a href="tokenInput.action"><<重现输入</a>
</body>
</html>
==========================================
实例三、execAndWait执行等待拦截器
==========================================
如果某个action运行时间比较长,浏览器会因为等待服务器响应而长时间显示空白。这比较令人烦,如果能显示一个服务器正忙的页面就好了。execAndWait拦截器就是实现这种效果的。execAndWait拦截器接受请求后,能判断上一个请求是否处理完毕。如果已经处理完毕,则显示结果页面,否则显示等待页面。
WaitAction.java
package com.lmb.struts2.action;
import com.opensymphony.xwork2.ActionSupport;
public class WaitAction extends ActionSupport{
@Override
public String execute() throws Exception {
Thread.sleep(10000);//沉睡10秒钟,模拟大数据量处理
return SUCCESS;//返回成功页面
}
}
代码让action所在的线程沉睡10秒,模拟业务处理繁忙。在10秒钟之内,将会显示等待页面。execAndWait拦截器配置在action中。配置如下:
struts.xml
<action name="wait" class="com.lmb.struts2.action.WaitAction" >
<interceptor-ref name="completeStack" /><!-- completeStack拦截器 -->
<interceptor-ref name="execAndWait" /><!-- execAndWait属性 -->
<interceptor-ref name="defaultStack" />
<result>/waitSuccess.jsp</result>
<result name="wait">/wait.jsp</result>
</action>
action配置了completeStack拦截器栈与execAndWait拦截器,还配置了一个名为wait的JSP页面。如果action繁忙,就显示wait页面。
wait.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="/struts-tags" prefix="struts" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<body>
页面提交(下载)中。。。请等待。。。
<script type="text/javascript">
setTimeout("location = location;",1000);//1秒钟后刷新本页
</script>
</body>
</html>
wait页面每隔一秒钟刷新一次,直至显示成功页面。在浏览器中进行访问,前10秒钟会一直显示等待页面。
这是显示繁忙页面的简单方式,是struts2内置的解决方案。实际上较多的使用Ajax技术来实时显示繁忙页面。Ajax能做到页面无刷新,并能通过进度条实时显示进度。
==========================================
实例四、自定义的权限验证拦截器
==========================================
拦截器可以自由扩展。所有的拦截器都实现自Interceptor接口。实现自定义拦截器也要实现Interceptor接口。一般直接继承AbstractInterceptor抽象类即可。
本例使用自定义拦截器判断用户是否登录。逻辑很简单,就是检查当前用户session中的account属性是否为空。如果为空,则转到登录页面,否则继续执行。
自定义拦截器AuthenticationInterceptor.java:
package com.lmb.struts2.interceptor;
import java.util.Map;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class AuthenticationInterceptor extends AbstractInterceptor{
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//通过invocation获取本次调用的上下文
Map<String, Object> sessionValues=invocation.getInvocationContext().getSession();
String account=(String) sessionValues.get("account");//从session中取值
if (account == null) {//如果为空
return Action.LOGIN;//则返回登陆页面
}
return invocation.invoke();//否则,正常运行
}
}
以上代码会在action被调用之前运行。如果验证通过,代码invocation.invoke()会继续调用action。拦截器需要在package中定义。然后将该拦截器配置在AuthenticationAction(该action代码略)中。如果已经登录,该AuthenticationAction会显示一句话,表示验证通过。否则,会被拦截器拦截,返回登录页面(登陆页面是<global-results>中配置的全局<result/>
)。
struts.xml
<package name="main" extends="struts-default"><!-- 定义一个package-->
<global-results> <!-- 所有的全局result-->
<result name="login">/login.jsp</result> <!-- 名为login的result-->
</global-results>
<interceptors>
<interceptor name="authentication" class="com.lmb.struts2.interceptor.AuthenticationInterceptor"></interceptor>
</interceptors>
<action name="authentication" class="com.lmb.struts2.action.AuthenticationAction" >
<!-- action实现类 -->
<!-- 自定义authentication拦截器 -->
<interceptor-ref name="authentication" />
<interceptor-ref name="defaultStack" />
<result>/authenticationSuccess.jsp</result>
</action>
</package>
注意:
1、关于拦截器的使用,以下是要注意的问题:
a、在struts.xml中配置拦截器时要先配置在全局Result之前!!
b、一定要注意!!使用自定义拦截器的action一定也要配置默认拦截器的引用,因为默认拦截器包含了参数的读取、session的管理等功能。 <interceptor-ref name="defaultStack" />
c、一定要注意,拦截器只能拦截action,无法拦截所有的请求,如JSP页面的访问!!!!,如果想拦截对JSP页面的访问可以使用过滤器来完成。
2、使用拦截器之后如何将有关拦截器验证情况的信息带回到相应的JSP页面???
拦截器中可写出如下代码:
ActionSupport action=(ActionSupport)invocation.getAction();
action.addActionMessage("您还没有登录或者登陆已经超时,请重新登陆!(要展示的有关拦截器验证情况的信息)");
在前台的JSP页面中通过${ action.actionMessages }
取得该提示信息。