拦截器是现代开发之中最为重要的特色,它是基于AOP的设计思想(AOP是基于代理设计思想)面向切面编程的设计思想实现的。
在Struts 2.x里面为了方便用户进行数据的验证,专门提供有validate()方法以及验证框架,但是这两个验证操作都有一个最致命的问题——永远都要在其赋值完成之后才能够验证,赋值之前无法验证,所以后台会一直出现错误。为了解决这样的问题,或者说为了解决所有辅助性的检测的功能,那么可以利用拦截器完成。
1 认识拦截器
在Struts 2.x里面所有的客户端发送来的请求都交给Filter进行处理,而后由Filter再去决定执行哪个Action。而拦截器是在执行某一个Action之前做的数据拦截操作。
实际上在之前也有拦截器出现,表单参数自动变为VO类型对象,这个就是一个拦截器的功能。
范例:定义一个Action,手工配置一个拦截器
package org.lks.action;
import com.opensymphony.xwork2.ActionSupport;
@SuppressWarnings("serial")
public class MessageAction extends ActionSupport {
public void insert(){
System.out.println("*********** insert");
}
}
范例:配置一个计算处理事件的拦截器
<package name="root" namespace="/" extends="struts-default">
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="timer"></interceptor-ref>
</action>
</package>
此时会计算出当前操作的执行时间,而后台的输出效果如下:
此时拦截器自动执行了,但是timer这个拦截器默认是在Action之后执行。
2 开发自定义拦截器
如果要想开发自定义的拦截器,那么在Struts 2.x里面是有要求的,这个拦截器的类必须继承自com.opensymphony.xwork2.interceptor.AbstractInterceptor
父类。这个类是一个抽象类。
public abstract class AbstractInterceptor
extends Object
implements Interceptor
在这个类之中存在有三个方法,但是有一个抽象方法:
(1)拦截:public abstract String intercept(ActionInvocation invocation) throws Exception
;
这个方法的参数里面接收了一个ActionInvocation的对象,这个类对象可以取得一切的发送及执行信息。
范例:实现一个最基础的拦截器
package org.lks.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
@SuppressWarnings("serial")
public class MyInterceptor extends AbstractInterceptor {
@Override
public void init() {
System.out.println("====================== 拦截器初始化 ===================");
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("====================== 拦截器执行 ===================");
return invocation.invoke(); //将请求向下传递
}
@Override
public void destroy() {
System.out.println("====================== 拦截器销毁 ===================");
}
}
拦截器定义完成固然是一个好事,但是拦截器必须在struts.xml文件中进行配置。
范例:配置struts.xml文件
<interceptors>
<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
</interceptors>
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="timer"/>
<interceptor-ref name="lks"/>
</action>
此时在MessageAction执行的时候挂了两个拦截器。此时执行效果如下:
此时的拦截器是在Action执行之前进行了处理,等于是说当前已经拦截下来了请求。
范例:现在在MessageAction中定义News类型进行数据接收
private News news = new News();
public News getNews() {
return news;
}
发现一旦编写了自定义的拦截器之后,非常遗憾的问题出现了,自动赋值的操作不会出现了。因为如果在Struts 2.x里面你没有使用拦截器,那么会自动使用赋值的拦截器,而一旦你使用了拦截器,那么就需要自己手工来配置拦截器。
范例:修改配置
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="timer"/>
<interceptor-ref name="lks"/>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
如果开始编写自定义拦截器,所有的操作必须采用手工的模式进行。
3 利用拦截器检测登录信息
如果要进行登录检测,那么肯定使用过滤器是最合适的,不过遗憾的是Struts 2.x(WebWork)为了与Struts 1.x做区别所以使用了过滤器进行了整个的分发处理。那么自己写的过滤器自然就无法进行session的登录检查。如果要想实现登录检查,那么只能够依靠拦截器完成,也就是说现在必须要对ActionInvocation做进一步的深入研究。
这是一个接口com.opensymphony.xwork2.ActionInvocation
。在这个接口中本次主要使用两个操作:
(1)请求继续向下传递给Action:public String invoke() throws Exception
;
(2)取得上下文对象内容:public ActionContext getInvocationContext()
;
使用getInvocationContext()
方法返回的是一个com.opensymphony.xwork2.ActionContext
类的对象,这个类包含有如下的方法:
(1)取得全部的参数:public Map<String,Object> getParameters()
;
|————Map集合中中的key为参数名称、value为参数内容;
(2)取得全部Session保存的属性数据:public Map<String,Object> getSession()
|————Map集合中的key为session属性名称、value为session属性的内容;
(3)取得全部Application保存的属性数据:``
|————|————Map集合中的key为application属性名称、value为application属性的内容;
范例:假设登录的session属性名称为mid
,所以本次验证如下:
package org.lks.interceptor;
import java.util.Map;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
@SuppressWarnings("serial")
public class LoginInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//取得所有的session属性
Map<String,Object> map = invocation.getInvocationContext().getSession();
if(map.get("mid") != null){ //登录过了,在session设置属性了
return invocation.invoke(); //正常访问
}else{ //如果现在没有session属性,那么表示无法操作
ServletActionContext.getRequest().setAttribute("msg", "未登录,请先登录!");
ServletActionContext.getRequest().setAttribute("url", "login.jsp");
return "forward.page"; //全局跳转提示页面
}
}
}
但是现在使用了一个forward.page
的页面,对于这个页面需要提醒注意,几乎所有的开发之中都需要使用一个统一的信息提示页,那么可以将其定义为全局跳转资源。
范例:修改struts.xml文件,配置拦截器使用
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<package name="root" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
<interceptor name="login" class="org.lks.interceptor.LoginInterceptor"></interceptor>
</interceptors>
<global-results>
<result name="forward.page">forward.jsp</result>
</global-results>
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="timer"/>
<interceptor-ref name="lks"/>
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
</package>
</struts>
此时已经彻底实现了拦截器的实际使用。
所有的拦截器都会按照既定的顺序依次向下执行,这一点的控制是非常方便的,因为很多时候一定是先进行登录验证而后在进行其它操作。
4 实现服务器端数据验证
对于数据验证的操作一定是分为两端进行,一端是客户端验证(虽然无用,但是必须提供),另外一端是基于服务端的应用,但是所有的Struts 2.x的操作有一点很麻烦,给出的验证方法或者是验证框架都必须在数据已经转换为VO类对象后才可以正常执行验证,而拦截器使用之后,发现可以在拦截其中针对数据进行验证处理。
如果要想进行验证,,那么必须满足于如下的几点:
(1)可以为每一个Action设置指定的验证规则,例如:现在编写一个NewsAction的验证规则,那么这个类里面要接收的参数名称:news.nid、news.ntitle、news.ncontent、news.npubdate,那么很明显没一种数据都有自己的类型。
范例:在NewsAction.java类里面定义规则
private String insertRule = "news.nid:int|news.ntitle:string|news.ncontent:string|news.npubdate:date";
public String insert(){
System.out.println(this.news);
return "news.show";
}
(1)可以验证的数据类型最多只有两大类:数组、普通类型,但是不管如何划分,常见类型:String、int、double、date。
(2)但是这个规则现在是保存在每一个Action里面的,而拦截器需要知道这个Action,这个时候就需要观察拦截器方法里面所具备的参数:ActionInvocation
,在这个接口里面定义有如下操作方法:
(1)取得要操作的Action类对象:public Object getAction()
。
Object actionObject = invocation.getAction();
此时取得了整个的Action对象,那么就意味着可以利用反射进行这个类属性或者是方法的调用。
(1)实际上取得了NewsAction类对象后,就必须要想办法取出一个适合于我们自己的操作类型。
范例:取得验证规则
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//取得要操作的Action,根据发送的提交路径不同,Action对象也不同
Object actionObject = invocation.getAction();
//为了可以确定要使用的验证操作成员,必须从里面取出分发的操作代码
String uri = ServletActionContext.getRequest().getRequestURI();
if(uri != null){ //为了保险起见,可以再增加一个null的验证
//取出关键的业务方法的名称,那么就相当于取得了“业务方法名称Rule”的验证规则
uri = uri.substring(uri.lastIndexOf('!') + 1, uri.lastIndexOf('.'));
//有了Action对象,又有了名称,就可以取得成员了
String fileName = uri + "Rule"; //验证规则的成员
//根据成员名称取得对象的具体内容,那么只能够依靠反射完成
Field fieldRule = actionObject.getClass().getDeclaredField("fieldName");
fieldRule.setAccessible(true);//取消掉封装处理
String rule = (String) fieldRule.get(actionObject);
System.out.println("[Rule]" + rule);
}
return invocation.invoke();
}
(2)所有的验证规则必须要和具体的输入参数进行捆绑才是最有用的。为了方便代码的维护,专门实现一个验证的具体操作类,这个类只是负责验证,那么这个类里面不需要保存任何的属性内容,那么很明显,使用全部的static方法就可以了。
(3)在Struts 2.x中为了省事,将所有的参数取得都不再去区分getParameter()
或者是getParameterValues()
,而是统一按照了getParameterValues()
方法取得,也就是说所有取得的内容都是字符串数组。
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
//需要知道有哪些参数
Iterator<Entry<String,Object>> iter = params.entrySet().iterator();
while(iter.hasNext()){
Entry<String,Object> entry = iter.next();
String[] str = (String[]) entry.getValue();
System.out.println(entry.getKey() + " : " + Arrays.toString(str));
}
return false;
}
所有的参数输出是没有任何意义的,因为必须要针对于我们给出的参数进行验证。
/**
* 进行数据验证的方法
* @param actionObject 表示要触发此操作的Action类
* @param rule 每个Action类里面定义的规则
* @param pamars 表示所有的输入参数
* @return 验证成功返回true,验证失败返回false
*/
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
//所有的验证操作都应该由rule发起,里面的组成“参数名称:类型”
String[] result = rule.split("\\|"); //取出每一组验证操作
for(int i = 0; i < result.length; i++){ //表示此处循环每一个验证
String temp[] = result[i].split(":"); //取出参数名称以及验证规则
String[] paramValue = (String[]) params.get(temp[0]);
for(int j = 0; j < paramValue.length; j++){ //循环每一个数组内容
switch(temp[1]){
case "string":{
System.out.println(temp[0] + "string validate");
break;
}
case "int":{
System.out.println(temp[0] + "int validate");
break;
}
case "double":{
System.out.println(temp[0] + "double validate");
break;
}
case "date":{
System.out.println(temp[0] + "date validate");
break;
}
default:{
break;
}
}
}
}
return false;
}
(4)随后在每一种规则中进行具体的验证操作。
package org.lks.util.interceptor;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class ValidateUtil {
/**
* 进行数据验证的方法
* @param actionObject 表示要触发此操作的Action类
* @param rule 每个Action类里面定义的规则
* @param pamars 表示所有的输入参数
* @return 验证成功返回true,验证失败返回false
*/
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
//所有的验证操作都应该由rule发起,里面的组成“参数名称:类型”
String[] result = rule.split("\\|"); //取出每一组验证操作
for(int i = 0; i < result.length; i++){ //表示此处循环每一个验证
String temp[] = result[i].split(":"); //取出参数名称以及验证规则
String[] paramValue = (String[]) params.get(temp[0]);
for(int j = 0; j < paramValue.length; j++){ //循环每一个数组内容
switch(temp[1]){
case "string":{
if(ValidateUtil.validateString(paramValue[j])){
System.out.println(temp[0] + "string validate pass");
}
break;
}
case "int":{
if(ValidateUtil.validateInt(paramValue[j])){
System.out.println(temp[0] + "int validate pass");
}
break;
}
case "double":{
if(ValidateUtil.validateDouble(paramValue[j])){
System.out.println(temp[0] + "double validate pass");
}
break;
}
case "date":{
if(ValidateUtil.validateDate(paramValue[j])){
System.out.println(temp[0] + "date validate pass");
}
break;
}
default:{
break;
}
}
}
}
return false;
}
/**
* 进行字符串的操作验证
* @param str 要验证的内容
* @return 如果字符串为空或者长度为0,那么返回false,否则返回true
*/
public static boolean validateString(String str){
if(str == null || "".equals(str)){
return false;
}
return true;
}
/**
* 进行数字的操作验证,验证之前首先要判断数据是否为空
* @param str 要验证的内容
* @return 如果是由数字所组成返回true,否则返回false
*/
public static boolean validateInt(String str){
if(validateString(str)){ //首先判断数据是否合法
return str.matches("\\d+");
}else{
return false;
}
}
/**
* 进行浮点数的验证操作,验证之前要先判断数据是否为空
* @param str 要验证的内容
* @return 如果字符串由小数所组成返回true,否则返回false
*/
public static boolean validateDouble(String str){
if(validateString(str)){ //首先判断数据是否合法
return str.matches("\\d+(\\.\\d+)?");
}else{
return false;
}
}
/**
* 验证字符串是否是日期类型或者是日期时间类型
* @param str 要验证的内容
* @return 如果字符串是日期或者日期时间返回true,否则返回false
*/
public static boolean validateDate(String str){
if(validateString(str)){ //首先判断数据是否合法
return str.matches("\\d{4}-\\d{2}-\\d{2}") || str.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
}else{
return false;
}
}
}
信息都在拦截器中存在了,可以一旦验证失败的话,那么怎么进行返回呢?
所有的错误信息实际上都会在前台以fieldErrors形式进行保存,而且保存的都是Map集合,这一切都要源自于addFiledError()这个方法的使用,但是同时还需要定义好错误信息内容。
范例:定义一个Messages.properties文件,在这个文件里面编写错误提示信息
string.validate.error.msg=数据不允许为空!
number.validate.error.msg=数据格式错误!
date.validate.error.msg=数据格式错误!(示例:yyyy-MM-dd)
/**
* 进行数据验证的方法
* @param actionObject 表示要触发此操作的Action类
* @param rule 每个Action类里面定义的规则
* @param pamars 表示所有的输入参数
* @return 验证成功返回true,验证失败返回false
*/
public static boolean validate(Object actionObject, String rule, Map<String,Object> params){
boolean flag = true;
try {
//取得增加错误信息的操作方法,通过此方法保存错误信息
Method addFieldErrorMethod = actionObject.getClass().getMethod("addFieldError", String.class, String.class);
//通过此方法取得错误的提示信息
Method getTextMethod = actionObject.getClass().getMethod("getText", String.class);
//所有的验证操作都应该由rule发起,里面的组成“参数名称:类型”
String[] result = rule.split("\\|"); //取出每一组验证操作
String text = ""; //保存每一组错误信息
for(int i = 0; i < result.length; i++){ //表示此处循环每一个验证
String temp[] = result[i].split(":"); //取出参数名称以及验证规则
String[] paramValue = (String[]) params.get(temp[0]);
for(int j = 0; j < paramValue.length; j++){ //循环每一个数组内容
switch(temp[1]){
case "string":{
if(!ValidateUtil.validateString(paramValue[j])){
text = (String) getTextMethod.invoke(actionObject, "string.validate.error.msg");
}
break;
}
case "int":{
if(!ValidateUtil.validateInt(paramValue[j])){
text = (String) getTextMethod.invoke(actionObject, "number.validate.error.msg");
}
break;
}
case "double":{
if(!ValidateUtil.validateDouble(paramValue[j])){
text = (String) getTextMethod.invoke(actionObject, "number.validate.error.msg");
}
break;
}
case "date":{
if(!ValidateUtil.validateDate(paramValue[j])){
text = (String) getTextMethod.invoke(actionObject, "date.validate.error.msg");
}
break;
}
default:{
break;
}
}
if(!("".equals(text))){
addFieldErrorMethod.invoke(actionObject, temp[0], text);
flag = false;
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return flag;
}
以最基础的CRUD为主,如果是增加出现了问题,那么应该返回到增加的错误页上,如果要是修改出现了问题,应该返回到修改的错误页,同样,依次类推…所有的错误页都应该在struts.xml文件里面进行配置。为每一个验证失败定义一个错误页,例如:insert业务验证规则是insertRule,那么它的错误的跳转页面就定义为:insertVF。
<result name="insertVF">news_insert.jsp</result>
如果这个类完成了,那么以后服务器端的验证,只需要将拦截器配置上。而后在需要的地方编写验证规则,在struts.xml文件里面编写验证失败的跳转,那么就可以实现服务器端的操作验证了。
5 定义拦截器栈
正如在之前所编写的代码一样,可以发现,在一个项目里面至少需要以下几种拦截器:验证拦截、登录拦截、defaultStack拦截,可是如果每一次都这样分别去写:
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="timer"/>
<interceptor-ref name="lks"/>
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
如果说要同时编写多个这样的拦截器引用,以上的代码就明显感觉到重复了,而且也不方便管理。所以为了方便的进行多个拦截器的操作管理,可以直接定义一个拦截器栈,在这个拦截器栈中可以引用多个拦截器。
范例:定义拦截器栈
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<package name="root" namespace="/" extends="struts-default">
<interceptors>
<interceptor-stack name="lksStack">
<interceptor-ref name="timer"/>
<interceptor-ref name="lks"/>
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
<interceptor name="lks" class="org.lks.interceptor.MyInterceptor"/>
<interceptor name="login" class="org.lks.interceptor.LoginInterceptor"></interceptor>
</interceptors>
<global-results>
<result name="forward.page">forward.jsp</result>
</global-results>
<action name="MessageAction" class="org.lks.action.MessageAction">
<interceptor-ref name="lksStack" />
</action>
</package>
</struts>
这种的操作形式是以后在开发之中使用最多的一种形式的结构。