从struts2拦截器到自定义拦截器
拦截器可谓struts2的核心了,最基本的bean的注入就是通过默认的拦截器实现的,一般在struts2.xml的配置中,package内直接或间接继承了struts-default.xml,这样struts2默认的拦截器就会作用.下面详细的说明一下:
Interceptor拦截器类似于过滤器,是可以在action执行前后执行的代码。是我们做web开发时经常用的技术。比如:权限控制、日志等。我们也可以将多个Interceptor连在一起组成Interceptor栈。
Struts2拦截器,每个拦截器类只有一个对象实例,即采用单例模式,所以引用这个拦截器的Action都共享这一拦截器类的实例,因此,在拦截器中如果使用类变量,要注意同步问题。
实现原理 : Struts2拦截器的实现原理相对简单,当请求struts2的action时,Struts 2会查找配置文件,并根据其配置实例化相对应拦截器对象,然后串成一个列表,最后一个一个地调用列表中的拦截器。
拦截器和过滤器的区别:
1. 拦截器和过滤器的概念非常类似。
2. 过滤器隶属于web容器,可以过滤一切请求(包括action、servlet、jsp、html等等)。
3. 而拦截器隶属于struts2框架,只能拦截action(无法拦截对jsp的请求)。
4. 过滤器内部采用函数回调来实现。拦截器采用动态代理来实现!
拦截器在struts2中的应用:
对于Struts2框架而言,正是大量的内置拦截器完成了大部分操作。比如:
– 像params拦截器将http请求中参数解析出来赋值给Action中对应的属性。
– Servlet-config拦截器负责把请求中HttpServletRequest实例和HttpServletResponse实例传递给Action
– …
struts-default.xml中有一个默认的引用,在默认情况下(也就是<action>中未引用拦截器时)会自动引用一些拦截器。
常用拦截器:token
作用:防止表单重复提交拦截器
使用方法:配置struts2.xml+重复提交处理+jsp页面表单内插入<s:token></s:token>标签;其中struts.xml的配置是核心
(1)先来看一下最基本的struts.xml的配置:
1 <package name="test" namespace="/" extends="struts-default"> 2 <action name="testValidate" class="com.bjsxt.struts.test.TestValidateAction"> 3 <interceptor-ref name="token"></interceptor-ref> 4 <result name="success">/ok.jsp</result> 5 <result name="input">/testFormLabel.jsp</result> 6 <result name="invalid.token">/tokenInvalid.jsp</result> 7 </action> 8 </package>
说明:
1.代码第三行代表引入一个struts2自带的拦截器token,此操作会使默认的拦截器失效,即无法使用其他功能,自定义拦截器再详细说明;
2.代码第六行为基本固定的格式,result的name属性值是固定的,后面的/tokenInvalid.jsp可以根据需求自己定义
3.此token拦截器会对该action下的所有请求作用,最关键的一点!即:所有该action的请求都会经过token的拦截处理,自定义拦截器时再说明;
(2)form表单中需加入<s:token></s:token>标签:
1 <s:form action="testValidate" > 2 <s:textfield name="uname" ></s:textfield> 3 <s:token></s:token> 4 <s:submit></s:submit> 5 </s:form>
说明:不一定是s标签的表单,普通表单中插入<s:token></s:token>也可以实现拦截的效果.
(3)处理措施,我的是跳到一个错误页面:testFormLabel.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="gbk"%> 2 <h1>表单不能重复提交!!</h1>
--------------------------------------------------------------------------------------------------------------------------------------------------------
自定义拦截器:
作为“框架(framework)”,可扩展性是不可或缺的,因为世上没有放之四海而皆准的东西。虽然,Struts 2为我们提供如此丰富的拦截器实现,但是这并不意味我们失去创建自定义拦截器的能力,恰恰相反,在Struts 2自定义拦截器是相当容易的一件事。
大家在开始着手创建自定义拦截器前,切记以下原则:
拦截器必须是无状态的,不要使用在API提供的ActionInvocation之外的任何东西。要求拦截器是无状态的原因是Struts 2不能保证为每一个请求或者action创建一个实例,所 以如果拦截器带有状态,会引发并发问题。
条件:
1. 直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor或者继承类com.opensymphony.xwork2.interceptor.AbstractInterceptor
2.通过<interceptor>元素来定义拦截器
3.通过<interceptor-ref>元素来使用拦截器
4.自定拦截器的java类并重写public String intercept(ActionInvocation ai) throws Exception
注意:如果为Action指定了一个拦截器,则系统默认的拦截器栈将会失去作用。为了继续使用默认拦截器,所以上面配置文件中手动引入了默认拦截器
步骤:
1.新建一个class,用于实现自定义拦截器功能,这儿就实现最为普遍的登陆验证,即防止未经登陆的情况下,访问action;先说一下原理:
首先,用户登陆后,经过action验证用户名和密码是否正确,如果正确则在session作用域内存放user对象,否则跳到登陆页面并提示用户名或密码不正确,而且session作用域中user的值是没有设置的.然后,由于在该action下使用了自定义的拦截器(struts.xml配置),访问该action的所有请求都会被拦截,验证session中user属性的值,如果值不存在,则跳到登陆页面并提示无权操作,否则执行invoke方法,即请求会被action正常响应,并返回相应的结果.
代码体现如下:
自定义拦截器类:
1 package com.bjsxt.struts2.exercise.interceptor; 2 3 import java.util.Map; 4 5 import com.bjsxt.struts2.exercise.vo.Users; 6 import com.opensymphony.xwork2.Action; 7 import com.opensymphony.xwork2.ActionContext; 8 import com.opensymphony.xwork2.ActionInvocation; 9 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; 10 11 public class LoginInterceptor extends MethodFilterInterceptor{ 12 @Override 13 protected String doIntercept(ActionInvocation invocation) throws Exception { 14 //获取session对象(经过struts2包装过) 15 Map session = ActionContext.getContext().getSession(); 16 //获取session作用域内是否有值 17 Users user = (Users) session.get("user"); 18 if(user!=null){//合法访问 19 return invocation.invoke(); 20 }else{//user为空说明未经过登陆,保存错误提示信息,跳到登陆页面 21 ActionContext.getContext().put("noright", "请先登陆再进行操作!"); 22 return Action.LOGIN; 23 } 24 } 25 }
user类:
1 package com.bjsxt.struts2.exercise.vo; 2 3 public class Users { 4 private Integer id; 5 private String name; 6 private String password; 7 public Integer getId() { 8 return id; 9 } 10 public void setId(Integer id) { 11 this.id = id; 12 } 13 public String getName() { 14 return name; 15 } 16 public void setName(String name) { 17 this.name = name; 18 } 19 public String getPassword() { 20 return password; 21 } 22 public void setPassword(String password) { 23 this.password = password; 24 } 25 }
2.配置struts.xml,使自定义拦截器作用:
1 <package name="move" namespace="/move" extends="struts-default"> 2 <interceptors> 3 <interceptor name="loginInteceptor" class="com.bjsxt.struts2.exercise.interceptor.LoginInterceptor"></interceptor> 4 </interceptors> 5 <global-results> 6 <result name="login" >/jsp/movebooking/login.jsp</result> 7 </global-results> 8 <action name="moveBookingAction" class="com.bjsxt.struts2.exercise.action.MoveBookingAction" > 9 <interceptor-ref name="loginInteceptor"> 10 <param name="excludeMethods">addMoveInfo</param> 11 </interceptor-ref> 12 <interceptor-ref name="token"> 13 <param name="includeMethods">addMoveInfo</param> 14 </interceptor-ref> 15 <interceptor-ref name="defaultStack"></interceptor-ref> 16 <result name="queryList" >/jsp/movebooking/movelist.jsp</result> 17 <result name="queryInfo" >/jsp/movebooking/moveinfo.jsp</result> 18 <result name="update" >/jsp/movebooking/updateinfo.jsp</result> 19 <result name="afterupdate" type="redirectAction" >moveBookingAction!queryMoveList</result> 20 <result name="success">/jsp/movebooking/success.jsp</result> 21 <result name="invalid.token">/jsp/movebooking/tokenInvalid.jsp</result> 22 </action> 23 24 <action name="userAction" class="com.bjsxt.struts2.exercise.action.UserAction"> 25 <result name="success" type="redirectAction" >moveBookingAction!queryMoveList</result> 26 <result name="failed" type="redirect">/jsp/movebooking/login.jsp 27 </result> 28 </action> 29 </package>
说明:
(1)使用自定义拦截器会使默认拦截器失效,所以需要手动引入:代码第15行;当然上述代码引入拦截器的方式也可以采用拦截器栈的方式,就不贴了;
(2)注意拦截器的顺序,一般情况下,默认拦截器都是放在最后面的,权限验证与token相比较,我觉得先验证是否登陆更加必要;
(3)interceptor-ref内可以定义param元素,即实现对特定方法的拦截;但是注意,使用方法拦截器必须直接或间接实现AbstractInerceptor接口或者继承
MethodFilterInterceptor类,并重写doIntercept方法.param标签内有两个属性:
excludeMethods:排除的方法
includeMethods:包含的方法
在本配置文件中,由于token拦截器的作用范围为moveBookingAction下所有请求,而我在测试的时候就发现,该action下的所有请求都会被拦截到重复提交的错误页面,原理尚不明白,有高手敬请指导(只有需要被作用的请求对应的jsp页面的form表单内加入了token标签),所以被逼无奈对token拦截的方法只限定在addMoveInfo方法中!
3.使用的action类和几个jsp页面:
UserAction:
1 package com.bjsxt.struts2.exercise.action; 2 3 import com.bjsxt.struts2.exercise.service.UserService; 4 import com.bjsxt.struts2.exercise.vo.Users; 5 import com.opensymphony.xwork2.Action; 6 import com.opensymphony.xwork2.ActionContext; 7 import com.opensymphony.xwork2.ActionSupport; 8 9 public class UserAction extends ActionSupport{ 10 private UserService userService =new UserService(); 11 private Users user; 12 public String queryUser() throws Exception{ 13 user = userService.queryUser(user); 14 // 将user存到session中,实现拦截器拦截未经登陆直接请求的url的功能 15 ActionContext.getContext().getSession().put("user", user); 16 if(user!=null){ 17 return Action.SUCCESS; 18 }else{ 19 ActionContext.getContext().getSession().put("wrong", "用户名或密码错误!"); 20 return "failed"; 21 } 22 } 23 24 public Users getUser() { 25 return user; 26 } 27 28 public void setUser(Users user) { 29 this.user = user; 30 } 31 32 }
MoveBookingAction:
1 package com.bjsxt.struts2.exercise.action; 2 3 import com.bjsxt.struts2.exercise.service.MoveBookingService; 4 import com.bjsxt.struts2.exercise.util.MyPageUtil; 5 import com.bjsxt.struts2.exercise.vo.MoveBookingVo; 6 import com.opensymphony.xwork2.ActionSupport; 7 8 public class MoveBookingAction extends ActionSupport{ 9 private MoveBookingService moveBookingService = new MoveBookingService(); 10 private MyPageUtil pu; 11 private Integer pageNum; 12 private MoveBookingVo moveBookingVo; 13 private String[] phone; 14 /** 15 * 搬家列表 16 * @return 17 * @throws Exception 18 */ 19 public String queryMoveList() throws Exception{ 20 pu = moveBookingService.queryMoveList(pageNum); 21 return "queryList"; 22 } 23 24 /** 25 * 搬家信息详情 26 * @return 27 * @throws Exception 28 */ 29 public String queryMoveInfo() throws Exception{ 30 moveBookingVo = moveBookingService.queryMoveInfo(moveBookingVo); 31 return "queryInfo"; 32 } 33 34 /** 35 * 搬家详情,跳到update页面 36 * @return 37 * @throws Exception 38 */ 39 public String queryForUpdate() throws Exception{ 40 moveBookingVo = moveBookingService.queryMoveInfo(moveBookingVo); 41 return "update"; 42 } 43 44 /** 45 * 修改搬家信息 46 * @return 47 * @throws Exception 48 */ 49 public String updateMoveInfo() throws Exception{ 50 moveBookingService.updateMoveInfo(moveBookingVo); 51 return "afterupdate"; 52 } 53 /** 54 * 搬家预约 55 * @return 56 * @throws Exception 57 */ 58 public String addMoveInfo() throws Exception{ 59 moveBookingVo.setStatus("0"); 60 moveBookingVo.setPhone(phone[0]+phone[1]); 61 moveBookingService.addMoveInfo(moveBookingVo); 62 return "success"; 63 } 64 65 public MyPageUtil getPu() { 66 return pu; 67 } 68 public void setPu(MyPageUtil pu) { 69 this.pu = pu; 70 } 71 public Integer getPageNum() { 72 return pageNum; 73 } 74 public void setPageNum(Integer pageNum) { 75 this.pageNum = pageNum; 76 } 77 public MoveBookingVo getMoveBookingVo() { 78 return moveBookingVo; 79 } 80 public void setMoveBookingVo(MoveBookingVo moveBookingVo) { 81 this.moveBookingVo = moveBookingVo; 82 } 83 84 public String[] getPhone() { 85 return phone; 86 } 87 88 public void setPhone(String[] phone) { 89 this.phone = phone; 90 } 91 }
login.jsp:
1 <body> 2 <font size="5"><b>管理员登陆</b></font><br> 3 <form id="f1" action="move/userAction!queryUser"> 4 <input type="hidden" id="flag" name="flag" value="${flag }"/> 5 用户名: 6 <input id="uname" name="user.name" onclick="clearMsg()" /> 7 密码: 8 <input id="pwd" type="password" name="user.password" onclick="clearMsg()" /> 9 <input type="button" style="background-color: lightgray;width: 100px;" value="登陆" onclick="submit_form()"/> 10 </form> 11 <div id="div" style="color: red">${wrong }${noright }</div> 12 </body>
tokenInvalid.jsp:
1 <body> 2 <h1>请勿重新提交</h1> 3 </body>