深入理解Struts2----数据校验

     在表现层的数据处理方面主要分为两种类型,一种是类型转换,这点我们上篇已经简单介绍过,另外一种则是我们本篇文章将要介绍的:数据校验。对于我们的web应用,我们经常需要和用户进行交互收集用户信息,那么无论是用户误操作还是恶意攻击,这些错误的信息一旦被传入到后台,小则导致程序异常关闭,大则导致整个系统瘫痪。数据校验就是对用户的输入做一层过滤,保护我们的系统免受侵入。下面我们开始介绍本篇的内容,主要包括以下几小节:

  • 一个简单的例子(用于全局把握整个校验过程)
  • 两种校验配置风格
  • 为不同Action处理逻辑配置不同的校验配置
  • 详解struts2框架内置的几种校验器
  • 自定义校验器

一、一个简单的例子      在详细介绍数据校验的每一步骤之前,我们先通过一个简单的例子从全局范围把握下整个数据校验流程都需要哪些文件,各个步骤执行的顺序。强调的是从全局粗略的感受下,不用在意具体的代码。

//登录表单页面,信息提交到loginAction
<html>
  <head>
    <title></title>
  </head>
  <body>
    <s:form method="POST" action="/login">
      <s:textfield name="name" label="姓名"/>
      <s:textfield name="age" label="年龄"/>
      <s:submit value="提交"/>
    </s:form>
    
  </body>
</html>
//定义一个action
public class LoginAction extends ActionSupport {

    private String name;
    private int age;
    public void setName(String n){
        this.name= n;
    }
    public String getName(){
        return this.name;
    }
    public void setAge(int a){
        this.age = a;
    }
    public int getAge(){
        return this.age;
    }

    public String execute(){
        return SUCCESS;
    }
}
//创建校验文件并键入以下内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>
    <field name="name">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>姓名不能为空</message>
        </field-validator>
    </field>
    <field name="age">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>年龄不能为空</message>
        </field-validator>
    </field>
</validators>

我们首先从login这个表单页面提交表单信息到loginAction中的属性,然后框架会查找是否有校验规则文件,如果有则执行它。在校验的过程中,如果校验失败会跳转到处理结果为 input 视图页面,这里和上篇介绍的类型转换是一样的,我们也一般是需要为其指定一个input视图页面的。在我们上述的校验文件中,我们规定两个属性的值不能为空,如果为空则该数据不符合要求,框架会封装错误信息并跳转到input视图页面。下面我们看看上述代码的运行截图:

至此我们简单了解了数据校验整个过程大致需要的文件及其作用,当然该例中还有很多细节之处没有展现出来,因为目的只是从整体上直观感受下,详细的内容将在下面的小节中展现。

二、两种校验配置风格      下面我们从校验文件开始看起。首先一点,校验文件的命名是有要求的并且一般一个校验文件只服务一个Action,所以该文件的命名规则如下:

<ActionName>-validation.xml

所以上述我们为LoginAction创建的校验文件名为:LoginAction-validation.xml。该文件有个根元素 validators ,我们的属性校验元素是其子元素。下面我们介绍第一种配置校验文件的方式,上述的例子就是这种方式,该种方式使用field 作为一级子元素,该元素将对应于Action实例中实际的属性,它有一个name属性,该属性就是用于指定此field元素配置的是Action的哪个实例属性,有几个实例属性就应该有几个field元素。

我们由field元素可以定位到Action实例中具体的某个属性,使用field-validator元素为给属性指定校验器(Struts默认提供的检验器,具体有关内置的校验器后文详细介绍),param 元素用于指定校验的参数,message元素用于指定不符合校验规则时输出的信息。我们可以根据不同的处理需要为Action实例属性指定不同的校验器,当然我们也是可以自定义校验器来校验属性的数值的。

上面介绍的是用field元素来配置的数据校验规则。下面我们介绍第二种配置风格,使用validator取代field作为一级子元素,用fieldName属性指定对应的Action实例属性,对于上面的配置,我们也可以改写为:

<validators>
    <validator type="requiredstring">
        <param name="fieldName">name</param>
        <param name="trim">true</param>
        <message>姓名不能为空</message>
    </validator>
    <validator type="requiredstring">
        <param name="fieldName">age</param>
        <param name="trim">true</param>
        <message>年龄不能为空</message>
    </validator>
</validators>

我们看到该方式的配置每个属性用一个validator元素表示,通过param元素的fieldName属性对应实际的Action实例属性,其余内容和第一种方式类似。他们之间的区别相信大家对比之后也能明白,只是书写方式不同,都能达到效果,大家可以根据自己喜好选择使用哪种方式,由于个人喜好,下文都会采用第一种方式进行介绍。

三、为不同Action处理逻辑配置不同的校验配置      我们的某个具体的Action类在很多情况下是可以用于多个不同的处理逻辑的,例如某个action既可以处理用户注册请求也可以处理用户登录请求,但是对于这两种截然不同的请求,我们的数据校验却不尽相同,下面看个例子:

public class LoginAction extends ActionSupport {

    private String name;
    private String pass1;
    private String pass2;
    public void setName(String n){
        this.name= n;
    }
    public String getName(){
        return this.name;
    }
    public void setPass1(String p1){
        this.pass1 = p1;
    }
    public String getPass1(){
        return this.pass1;
    }
    public void setPass2(String p2){
        this.pass2 = p2;
    }
    public String getPass2(){
        return this.pass2;
    }

    public String execute(){
        return SUCCESS;
    }
}
//Struts.xml中指定该类用于响应两个请求
<struts>
    <package name="my" extends="struts-default" namespace="/">
        <action name="login" class="MyPackage.LoginAction">
            <result name="success">/index.jsp</result>
            <result name="input">/input.jsp</result>
        </action>
        <action name="regist" class="MyPackage.LoginAction">
            <result name="success">/index.jsp</result>
            <result name="input">/input.jsp</result>
        </action>
    </package>
</struts>

此时我们需要创建两个数据校验文件,一个用于校验请求login,一个用于校验请求regist。当然为了框架能够快速搜索到,这里两个文件的命名也是有要求的,规则如下:

<ActionName>-<ActionAliasName>-validation.xml

对应于上述的两个文件名为:LoginAction-login-validation.xml和LoginAction-regist-validation.xml。下面给出此例中上述两个文件的内容:

//LoginAction-login-validation.xml局部内容
<validators>
    <field name="name">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>姓名不能为空</message>
        </field-validator>
    </field>
    <field name="pass1">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>密码不能为空</message>
        </field-validator>
    </field>
</validators>
//LoginAction-regist-validation.xml局部内容
//该校验文件为pass1属性添加了一个fieldexpression校验器,用于校验是否和pass2相等
<validators>
    <field name="name">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>姓名不能为空</message>
        </field-validator>
    </field>
    <field name="pass1">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>密码不能为空</message>
        </field-validator>
        <field-validator type="fieldexpression">
            <param name="expression"><![CDATA[(pass1==pass2)]]></param>
            <message>两次密码不同</message>
        </field-validator>
    </field>
    <field name="pass2">
        <field-validator type="requiredstring">
            <param name="trim">true</param>
            <message>密码不能为空</message>
        </field-validator>
    </field>
</validators>

上述的两个文件依然是需要存放在和Action相同目录下,框架会根据不同的请求而去检索相应的校验文件,下面我们看表单页面:

//注册页面
  <body>
    <s:form method="POST" action="/regist">
      <s:textfield name="name" label="姓名"/>
      <s:textfield name="pass1" label="密码"/>
      <s:textfield name="pass2" label="密码"/>
      <s:submit value="提交"/>
    </s:form>
  </body>

而对于login登录逻辑来说,

  <body>
    <s:form method="POST" action="/login">
      <s:textfield name="name" label="姓名"/>
      <s:textfield name="pass1" label="密码"/>
      <s:submit value="提交"/>
    </s:form>
  </body>

只需要用户名和密码不为空即可,并没有别的校验逻辑,可能上述的例子并不是十分恰当,但是却很清晰的展示了这种校验分离的处理过程。对于校验文件中的内容,我们将在下文详细介绍,此处不必纠结。最后需要强调一点的是,当我们为每个不同的处理逻辑配置相对应的校验文件时,原来的那个LoginAction-validation.xml文件则会被作为默认的校验文件,当LoginAction-login-validation.xml或者LoginAction-regist-validation.xml执行之后,会自动继续执行该校验文件,也就会导致校验了两遍,所以一般会在该文件中添加该Action的通用校验代码。

四、详解struts2框架内置校验器      有关框架的内置校验器,我们在上文中一直都使用,但是没有详细的介绍,本小节就专门介绍这些内置校验器的使用,下一小节将介绍如何自定义一个校验器。首先解压xwork-core-2.3.32.jar,从com\opensymphony\xwork2\validator\validators中找到default.xml并打开:

这是框架内建立的所有校验器,我们会介绍其中的部分, 剩余的一些校验器由于某些局限性不经常使用,所以此处就不在介绍。

下面看第一种校验器,必填校验器。该校验器要求指定字段的值非空(null)。该校验器的使用比较简单,此处不再演示。

第二种校验器,必填字符串校验器。该校验器要求字段的值非空并且长度要大于0。即字段不能是""。该校验器要求比第一种必填校验器严格一点。它还具有一个参数:trim。该参数用于剔除字段中前后的空白,默认值为true。这一点也是比较容易理解的,此处不再赘述。

第三种校验器,整数校验器。对于Action中字段类型为int,long,short的情况,我们可以使用该校验器来要求该字段的值必须存在于指定的范围内。它有两个参数,min,max,一个是指定该字段的值可能出现的最小值,一个则是指定该字段的值可能出现的最大值。下面看个例子:

//LoginAction中添加age属性的代码并未贴出
//此处指定了该age属性必须在0-100之间
<validators>
    <field name="age">
        <field-validator type="int">
            <param name="min">0</param>
            <param name="max">100</param>
            <message>输入的年龄必须在指定0-100之间</message>
        </field-validator>
    </field>
</validators>

第四种校验器,日期校验器。该校验器用于规定日期类型的属性值可能出现的范围。有两个参数,min和max。两者分别指定日期最小值和最大值。下面看一个例子:

//LoginAction中添加属性date
<validators>
    <field name="date">
        <field-validator type="date">
            <param name="min">1990-01-01</param>
            <param name="max">2017-5-19</param>
            <message>日期范围为:1990-01-01到2017-5-19</message>
        </field-validator>
    </field>
</validators>

第五种校验器,字段表达式校验器,fieldexpression。它要求该字段满足一个基于ognl的表达式。该校验器具有一个参数,expression,该参数指定了一个表达式。下面我们看一个具体的例子:

//表达式要写在 <![CDATA[....]]> 内
<validators>
    <field name="pass1">
        <field-validator type="fieldexpression">
            <param name="expression"><![CDATA[(pass1 == pass2)]]></param>
            <message>无法匹配表达式</message>
        </field-validator>
    </field>
</validators>

需要注意一点的是我们可以使用ognl表达式从ValueStack中取数据进行比较,但是ognl表达式本身必须被写在<[CDATA[....]]>中。至于为什么,作者还没参透。

第六种校验器,字符串长度校验器。该校验器比较简单,和之前介绍的几种很是类似,主要有几个参数,maxLength,minLength,trim。相信大家也都知道他们什么意思,此处不再赘述。

第七种校验器,正则表达式校验器。该校验器有两个参数,regex代表正则表达式内容,caseSensitive指定校验时是否考虑大小写。具体使用情况比较容易,此处不再赘述。下面我们看自定义校验器。

五、自定义校验器      相比于使用Struts2内置校验器,自定义一个校验器反而简单些。我们只需要重写ActionSupport类中的一个方法即可:

  public void validate() {}

下面我们看一个例子:

//在LoginAction中添加如下一个方法
    public void validate(){
        if(name.isEmpty()){
            addFieldError("name","用户名不能为空");
        }
        if (!pass1.equals(pass2)){
            addFieldError("pass1","两次输入的密码必须相同");
        }
    }

该方法用于判断字段name是否为空,如果为空打包错误信息添加到FieldError中,判断两次输入的密码是否一致,如果不一致打包错误信息添加到FieldError中。在方法结束时,框架会去查看FieldError是否为空,如果不为空说明校验出错,跳转视图input页面。下面我们看上述代码的运行截图:

从运行的结果看,自定义校验器和使用框架内置校验器都能完成数据校验的工作,但是个人认为自定义校验方式反而显得过程简单。对于之前介绍的一个Action类响应多个请求时候对数据校验的不同形态,在我们自定义校验器中也是可以实现的,只是定义的方法名有所区别,例如:

响应login处理逻辑的自定义校验方法命名为:validateLogin()
响应regist处理逻辑的自定义校验方法命名为:validateRegist()

至此,我们简单介绍完了有关Struts2框架的数据校验部分的内容,总结的粗糙,望大家多多评论指点!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏贾老师の博客

Boost 中的智能指针

752
来自专栏Web项目聚集地

什么是跨域?解决方案有哪些?

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同...

752
来自专栏九彩拼盘的叨叨叨

JavaScript 严格模式介绍

如我们所知,JavaScript 是一门灵活的语言。其灵活性同样也带来了很多坑,当然也有一些是设计缺陷。比如

742
来自专栏哈雷彗星撞地球

Runtime系列(一)-- 基础知识

众所周知,Objective-C 是一种运行时语言。运行时怎么来体现的呢?比如一个对象的类型确定,或者对象的方法实现的绑定都是推迟到软件的运行时才能确定的。而运...

702
来自专栏Jackson0714

【.Net底层剖析】3.用IL来理解属性

3217
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础19(01)总结IO流,异常try…catch,throws,File类

1:异常(理解) (1)程序出现的不正常的情况。 (2)异常的体系 Throwable |--Error 严重问题,我们不处理。 |--Excepti...

3547
来自专栏Phoenix的Android之旅

匿名内部类何为匿名?

比如为什么称之为匿名? 为什么也算是一个类,而且是内部类? 它和内部类有什么区别?

773
来自专栏武军超python专栏

2018-7-18pythoh中函数的参数,返回值,变量,和递归

********************************************************************************...

644
来自专栏逆向技术

C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原

      C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原 我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch...

19810
来自专栏進无尽的文章

RunTime 之使用前须知

有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:

712

扫码关注云+社区