08 06Struts 2.x拦截器

拦截器是现代开发之中最为重要的特色,它是基于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>    

这种的操作形式是以后在开发之中使用最多的一种形式的结构。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章