Struts2 输入校验 实例讲解

时间:2021-05-08 05:39:04

Struts2 输入校验 实例讲解信息基本校验

对于每个web框架输入输入校验都是一个重要的部分,对用户输入的数据进行有效的过滤,是保持系统安全的一方面措施.Struts2也不例外,同样也提供了更简易的输入校验机制,Struts2提供的输入校验有两种方式,一种是硬编码的方式,一种是采用Struts2的输入校验框架进行校验,即采用XML配置的方式进行校验。

下面我们看一上采集硬编码的方式如果校验:

举例说明:需要对一个用户注册的数据进行校验

首先要在myeclipse 中创建一Web工程,在src目录中创建struts.xml文件,到struts2的资源包中例子中拷

11 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描述表单内容。显示如:

Struts2 输入校验 实例讲解

 

校验需要,username: 不能为空  必须是6-10个字符

          password repassword不能为空  必须是6-10个字符,而且两次输入密码要一致

                   age 必须是0-150数字

                   birthday graduation  都必须是日期,且出生日期要大于毕业日期

12下面创建一个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()就可以在调用Actionexecute()方法之前,执行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;

    }

}

 

13struts.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>

  

运行应用测试:

Struts2 输入校验 实例讲解

 

OK,验证信息成功,但是我们发现错误信息中出现一些我们没有见过的信息,

比如:Invalid field value for field "birthday". 这样的信息,这是由Struts2自身的校验给出来的信息,默认的情况下,如果遇到类型转化失败的问题,struts2会自动添加一条错误信息,使用addFieldError()添加一条错误信息。但是这样的信息并不是我们想要的,因为这些信并不友好,我们又不好控制,所有我们要想办法取消这些信息,但是这些是信息是struts2自身的一些拦截器实现的功能,又不容易取掉(应该可以去掉,可以找到这个拦截器,重写它去掉这部分功能,自己再组织拦截器栈),所以考虑将其替换掉;因此Struts2提供一此替换这些信息的方法:1 可以通过替换国际化资源配置文件,来达到目的,具体做法:

[1]strrts.xml 配置中指定国际化资源文件常量

<package >标签下加一条

   <constant name="struts.custom.i18n.resources" value="message" />

Name代表常量名,固定的;value代表指定的资源配置文件

这样配置会覆盖default.properties struts2的配置常量

[2]classes 目录下,创建工程时可以在src目录下创建一个message.properties 文件,文件名要和上面的vlaue值对应,这个文件是全局的配置,输入内容如下:

xwork.default.invalid.fieldvalue={0} error

等号左边的是固定不变的,右边是{0}一个占位符,将来会被错误的字段名替换;

         也是当一个字段发生错误了,比如:age转化时错了,就会显示 age error 的信息 

运行测试,这样显示就友好一点了!如下显示:

Struts2 输入校验 实例讲解

 

但是还存在一些问题,信息出错时,错误信息显示太死板了,我们想给一些字段加上个性化的信息。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,通过JSPEL表达式也可以实现回显,上面写过了,介这种做法并不好,显示过程中可能会出现问题,特别是日期显示那块!

下面我们采用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>&nbsp;</td>

            <td><s:submit /></td>

        </tr>

    </table>

    </s:form>

  </body>

</html>

上面我们采用了表格布局,当然如果不采用表格的话,struts2在翻译struts2标签是,会为每个标签添加一个单元格将控件括起来。这是struts表单默认的显示样式。

显示如下:

Struts2 输入校验 实例讲解

 

运行测试仪可以正常回显数据:

Struts2 输入校验 实例讲解

 

错误级别

上面我们提到过,错误信息的级别,其实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主题,其它的还有AjaxSimple ,我们可以把它改成Simple 即可以自己定义布局的主题,是这样,一旦改成了theme=”simple” 它的错误信息就不会显示了。这也是我们使用这个主题的原因。

这些操作已经在上面jsp页面中做了!

在真正的开发中,需要根据情况采用修改主题,或重构验证方法,来正确显示错误信息。

多方法情况下输入校验

OK,上面讲的都是针对Actionexecute()方法之前的验证,我们知道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()方法!