信息基本校验
对于每个web框架输入输入校验都是一个重要的部分,对用户输入的数据进行有效的过滤,是保持系统安全的一方面措施.Struts2也不例外,同样也提供了更简易的输入校验机制,Struts2提供的输入校验有两种方式,一种是硬编码的方式,一种是采用Struts2的输入校验框架进行校验,即采用XML配置的方式进行校验。
下面我们看一上采集硬编码的方式如果校验:
举例说明:需要对一个用户注册的数据进行校验
首先要在myeclipse 中创建一Web工程,在src目录中创建struts.xml文件,到struts2的资源包中例子中拷
1.1 在WebRoot下面创建一个register.jsp 页面,代码
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>register page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<form action="register.action" method="post">
<font color="red" size="2"><s:fielderror /></font>
<table border="1" cellpadding="0" cellspacing="0" width="500">
<tr>
<td colspan="2"> 用户注册</td>
</tr>
<tr>
<td width="100">username</td>
<td><input type="text" name="username" value="${requestScope.username }"/>6-10 cahr</td>
</tr>
<tr>
<td>password</td>
<td><input type="password" name="password" value="${requestScope.password }"/></td>
</tr>
<tr>
<td>re-password</td>
<td><input type="password" name="repassword" value="${requestScope.repassword }"/></td>
</tr>
<tr>
<td>age</td>
<td><input type="text" name="age" value="${requestScope.age }"/>0-150</td>
</tr>
<tr>
<td>birthday</td>
<td><input type="text" name="birthday" value="${requestScope.birthday }"/>must be date</td>
</tr>
<tr>
<td>graduation</td>
<td><input type="text" name="graduation" value="${requestScope.graduation }" />must after birthday</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="register"/></td>
</tr>
</table>
</form>
</body>
</html>
上面一个表单使用HTML描述表单内容。显示如:
校验需要,username: 不能为空 必须是6-10个字符
password repassword不能为空 必须是6-10个字符,而且两次输入密码要一致
age 必须是0-150数字
birthday graduation 都必须是日期,且出生日期要大于毕业日期
1.2下面创建一个RegisterAction 来处理用户注册数据代码如下:
package com.snt.struts2.action;
import java.util.Calendar;
import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;
public class RegisterAction extends ActionSupport {
private String username;
private String password;
private String repassword;
private int age;
private Date birthday;
private Date graduation;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Date getGraduation() {
return graduation;
}
public void setGraduation(Date graduation) {
this.graduation = graduation;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRepassword() {
return repassword;
}
public void setRepassword(String repassword) {
this.repassword = repassword;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String execute() throws Exception {
return SUCCESS;
}
public String abc()throws Exception{
System.out.println("abc method invoke!");
return SUCCESS;
}
public String xyz()throws Exception{
System.out.println("abc method invoke!");
return SUCCESS;
}
public void validateAbc(){
System.out.println("valide abc()");
}
public void validateXyz(){
System.out.println("valide xyz()");
}
/***
* 当遇到类型转化时,struts2自动生成一条错误信息,放到
* fielderrors 中
*/
@Override
public void validate() {
//当用户直接访问action时,action的属性都是null
//防止NullPointException
System.out.println("validate()...........");
if(null==username || username.length()<6 || username.length()>10){
this.addFieldError("username", "username is invalid");
}
if(null==password || password.length()<6 || password.length()>10){ this.addFieldError("password", "password is invalid!");
}else if(null==repassword || repassword.length()<6 || repassword.length()>10){
this.addFieldError("password", "password is invalid!");
}else if(!password.equals(repassword)){
this.addFieldError("password", "tow password is not be same!");
}
if(age<0 || age>150){
this.addFieldError("age", "age is invalid!");
}
if(null==birthday){
this.addFieldError("birthday", "birthday invalid!");
}
if(null==graduation){
this.addFieldError("graduation", "graduation invalid!");
}
if(null!=birthday && null!=graduation){
Calendar c1=Calendar.getInstance();
c1.setTime(birthday);
Calendar c2=Calendar.getInstance();
c2.setTime(graduation);
if(!c1.before(c2)){
this.addFieldError("birthday","birthday shoud before graduation!");
}
}
}
}
红色的是检验的代码,我创建的Action继承了ActionSupport类,ActionSupport 双继承了Validateable 和ValidationAware 接口,validate()是Validateable接口中的方法,我们继承Validateable 接口,实现validate()就可以在调用Action的execute()方法之前,执行validate()方法进行数据检验。根据的我们的检验逻辑如果某个属性出错了,会产生一个相应的错误信息,以属性名为Key值,以错误信息为值,形成一个键值对通过addFieldError()方法放在一个保存错误信息的Map 中,下面介绍一个addFieldError()方法的原理:
下面是ActionSupport 中的一些方法:
private final ValidationAwareSupport validationAware=new ValidationAwareSupport();
//struts2是采用上面的ValidationAwareSupport这个类完成的类完成的。
//设置字段错误信息
public void setFieldErrors(Map<String, List<String>> errorMap) {
validationAware.setFieldErrors(errorMap);
}
//获取所有字段错误信息
public Map<String, List<String>> getFieldErrors() {
return validationAware.getFieldErrors();
}
//我们从上面的方法大致可以看出来,struts2是如下保存错误信息:
每个字段的错误是这样保存的 每个字段上可能产生多条错误信息,所有保存时是:字段名为Key,
将所有这个字段的错误形成一个List作为value 值,放在Map的一条记录中。
//添加一条字段错误信息
public void addFieldError(String fieldName, String errorMessage) {
validationAware.addFieldError(fieldName, errorMessage);
}
下面是ValidationAwareSupport 部分源代码:
public class ValidationAwareSupport implements ValidationAware, Serializable {
private Collection<String> actionErrors; //保存Action级别的错误信息 Collection
private Map<String, List<String>> fieldErrors; //保存Field级别的错误信息 Map
//添加一条字段错误的详细代码
public synchronized void addFieldError(String fieldName, String errorMessage) {
//获取所有字段错误的Map
final Map<String, List<String>> errors = internalGetFieldErrors();
//取出对应字段的错误信息列表
List<String> thisFieldErrors = errors.get(fieldName);
//如果之前没有错误,新创建一个List保存错误信息
if (thisFieldErrors == null) {
thisFieldErrors = new ArrayList<String>();
errors.put(fieldName, thisFieldErrors);
}
//将错误信息添加到列表中
thisFieldErrors.add(errorMessage);
}
//初始化存储错误的信息的Map
private Map<String, List<String>> internalGetFieldErrors() {
if (fieldErrors == null) {
fieldErrors = new LinkedHashMap<String, List<String>>();
}
return fieldErrors;
}
}
1.3在struts.xml 文件本配置RegisterAction 如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.custom.i18n.resources" value="message" />
<package name="struts2" extends="struts-default">
<action name="register"
class="com.snt.struts2.action.RegisterAction">
<result name="success">/success.jsp</result>
<result name="input">/register2.jsp</result>
</action>
</package>
</struts>
运行应用测试:
OK,验证信息成功,但是我们发现错误信息中出现一些我们没有见过的信息,
<constant name="struts.custom.i18n.resources" value="message" />
Name代表常量名,固定的;value代表指定的资源配置文件
这样配置会覆盖default.properties 中struts2的配置常量
xwork.default.invalid.fieldvalue={0} error
等号左边的是固定不变的,右边是{0}一个占位符,将来会被错误的字段名替换;
但是还存在一些问题,信息出错时,错误信息显示太死板了,我们想给一些字段加上个性化的信息。Struts2也提供对应的方法:
[1]在需要输入校验的Action同目标下,创建一个与Action同名的资源文件,比如:RegisterAction.properties这个文件将成功局部的资源配置文件,它里面的配置可以替换全局的配置;输入以下内容
invalid.fieldvalue.age=/u5e74/u9f84/u8f93/u5165/u4fe1/u606f/u4e0d/u6b63/u786e
invalid.fieldvalue.birthday=/u65e5/u671f/u8f93/u5165/u9519/u8bef
invalid.fieldvalue.graduation=/u6bd5/u4e1a/u65f6/u95f4/u65e5/u671f/u8f93/u5165/u4e0d/u6b63/u786e
这样的信息,注意等号左边的invalid.fieldvalue 是固定的,之后跟的是字段名;右边是要显示的错误信息;采用的是Unicode码表示的!因为是中文,所有需要使用JDK中的native2ascii 命令将汉字翻译成unicode 码,即可使用。Native2ascii 命令在JDK安装目录/bin 下面。
这样运行即可以显示友好的中文信息:
回显表单数据,排除信息重复显示
OK,上面的基本的输入校验已经完成,当然还存在一些问题,比如:错误信息提示还是有重复的,当数据错误时不能回显,这都是一些不友好的做法!我们下面一步步完善!
为了添加回显的功能,我用采用Struts2标签库来很容易地实现!当然使用HTML,通过JSP的EL表达式也可以实现回显,上面写过了,介这种做法并不好,显示过程中可能会出现问题,特别是日期显示那块!
下面我们采用struts2的标签库来重构页面:
创建一个register2.jsp 页面,导入struts2的标签库,代码如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>register page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<!-- 回显 -->
<body>
<s:actionerror cssStyle="color:red;"/>
<hr>
<s:fielderror cssStyle="color:red;"/>
<s:form action="register2" theme="simple">
<table border="1" cellpadding="0" cellspacing="0">
<tr>
<td>username</td>
<td><s:textfield name="username" label="usernmae"/></td>
</tr>
<tr>
<td>password</td>
<td><s:password name="password" label="password"/></td>
</tr>
<tr>
<td>repassword</td>
<td><s:password name="repassword" label="repassword" /></td>
</tr>
<tr>
<td>age</td>
<td><s:textfield name="age" label="age"/></td>
</tr>
<tr>
<td>birthday</td>
<td><s:textfield name="birthday" label="birthday"/></td>
</tr>
<tr>
<td>graduation</td>
<td><s:textfield name="graduation" label="graduation"/></td>
</tr>
<tr>
<td> </td>
<td><s:submit /></td>
</tr>
</table>
</s:form>
</body>
</html>
上面我们采用了表格布局,当然如果不采用表格的话,struts2在翻译struts2标签是,会为每个标签添加一个单元格将控件括起来。这是struts表单默认的显示样式。
显示如下:
运行测试仪可以正常回显数据:
错误级别
上面我们提到过,错误信息的级别,其实Struts2中的错误有两种级别:
1. Action级别的错误
2. Field 级别的错误
而Struts2只有判断到两种错误级别的信息都没有的情况,才认为是输入校验通过。
查看VlidationAwareSupport 类的原代码:高亮显示的一句代码,可以确定struts2是这样做的。
public synchronized boolean hasActionErrors() {
return (actionErrors != null) && !actionErrors.isEmpty();
}
public synchronized boolean hasActionMessages() {
return (actionMessages != null) && !actionMessages.isEmpty();
}
public synchronized boolean hasErrors() {
return (hasActionErrors() || hasFieldErrors());
}
public synchronized boolean hasFieldErrors() {
return (fieldErrors != null) && !fieldErrors.isEmpty();
}
下面我们重写一下validate()方法,将所有添加addFieldError() 的信息,转化成addActionError();
如下:
public void validateExecute() {
//当用户直接访问action时,action的属性都是null
//防止NullPointException
System.out.println("validate()...........");
if(null==username || username.length()<6 || username.length()>10){
this.addActionError("username invalid!");
}
if(null==password || password.length()<6 || password.length()>10){
this.addActionError("password is invalid!");
}else if(null==repassword || repassword.length()<6 || repassword.length()>10){
this.addActionError("password is invalid!");
}else if(!password.equals(repassword)){
this.addActionError( "tow password is not be same!");
}
if(age<=0 || age>150){
System.out.println("ERROR");
this.addActionError( "age is invalid!");
}
if(null!=birthday && null!=graduation){
Calendar c1=Calendar.getInstance();
c1.setTime(birthday);
Calendar c2=Calendar.getInstance();
c2.setTime(graduation);
if(!c1.before(c2)){
this.addActionError("birthday shoud before graduation!");
}
}
再将运行界面时,我们发现数据校验明明是错误的却不会显示错误信息;这是因为Struts2标签默认只会显示Field级别的错误信息。要想显示Action级别的信息,需要在表单一加上一句;
<s:actionerror cssStyle="color:red;"/>
OK,这样信息显示出来,但还是有问题的,我们发现Struts2默认的表单布局,错误信息部会显示在控件的上方,而且种级别的信息显示有重复的,这需要我们做两件事情,来避免!
1.需要重新考虑validate() 校验方法的代码,比如:前类型转化失败的情况下,一些校验就不用做了;即便这样有时还会是有重复的,另一种办法就是改变表单的主题,Struts2 为每个表单显示都定义了一个主题,默认是HTML主题,其它的还有Ajax和Simple ,我们可以把它改成Simple 即可以自己定义布局的主题,是这样,一旦改成了theme=”simple” 它的错误信息就不会显示了。这也是我们使用这个主题的原因。
这些操作已经在上面jsp页面中做了!
在真正的开发中,需要根据情况采用修改主题,或重构验证方法,来正确显示错误信息。
多方法情况下输入校验
OK,上面讲的都是针对Action中execute()方法之前的验证,我们知道struts2中也提供了一个Action中多个方法,处理多种请求的机制。可能通过,struts.xml 配置action中,加一个method属性来实现!如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.custom.i18n.resources" value="message" />
<package name="struts2" extends="struts-default">
<action name="register1" class="com.snt.struts2.action.RegisterAction" method="register1">
<result name="success">/success.jsp</result>
<result name="input">/register.jsp</result>
</action>
<action name="register2"
class="com.snt.struts2.action.RegisterAction">
<result name="success">/success.jsp</result>
<result name="input">/register2.jsp</result>
</action>
</package>
</struts>
这样配置是可以的,使用register1 的请求会调用Action中的register()方法;
使用regiser2的请求会调用Acrtion中的execute()方法。
但这时输入校验如何处理呢?Struts2是这样处理的:
对于execute()方法,它会执行validate()方法进行输入校验,对于register()它会调用validateRegiser()方法进行输入校验,但是对于register()的情况比较特别,它调用validateRegister()方法后,它还会调用validate()方法。也是就说validate()方法总会被调用的!对于这种情况呢,当然是对我们输入校验会产生干扰的!~
如何处理这样的问题呢? 提供以下解决方案:
1. 空实现valiate()方法,但是这样有问题,如何校验execute()方法呢?可以选择不用execute()方法
2. 不提供validate()方法,也就是空实现,但要提供一个validateExecute()方法来校验execute()方法!