Struts表单校验插件:Validator框架

jopen 9年前

一、Validator框架的优势

       Validator框架是一个Struts插件,最初由David Winterfeldt设计并实现。Validator框架从Struts 0.5时代就可以使用,但那时Validator框架只是被捐助的一个jar包。Apache组织为了使开发人员更方便地使用Validator框架,决定从Struts1.1开始,将Validator框架作为Struts的一部分同Struts一起发布。

Validator框架可以在XML文件中配置验证规则和验证对象。因此,使用Validator框架可以无需在ActionForm的子类中覆盖validate方法就可以很方便地验证客户端的提交数据。由于Validator框架内置了很多预定义的验证机制,如验证某个属性是否存在,验证EMail是否合法等。所以在一般情况下,只需要配置XML文件就可以满足我们的验证需求。

在使用Validator框架时,就会发现这种方式要比直接使用validate方法进行验证会给我们带来如下的好处:

1.  更容易维护。 由于验证信息可以被放置在同一个配置文件中,因此,我们可以更容易地来维护这些验证信息。

2.  标准化。由于很多简单的验证都是相同的。如用户名和密码都要求由字母、数字以及下划下组成。如果将这些验证都写在validate方法中,对这些验证进行标准化非常困难。而在Validator框架中的这些验证机制都是预先定义的,因此,标准化相同的验证对于Validator框架来说将是一件非常轻松的事。

3.  避免重造轮子。虽然一些验证很简单,但如果想正确实现它们也是非常困难的。一个典型的例子是验证EMail地址的格式。如果这个验证要想完美无缺,就必须按着RFC-2822规范的要求来验证EMail地址。而如果我们使用Validator框架,就无需再重造轮子来验证EMail地址了。

4.  减少重复代码的数量。由于Validator框 架提供了很多预定义的验证,因此,我们可以避免自己写很多重复的代码进行验证。当然,我们也可以将大量使用的验证封装在类的方法中,这些虽然可以避免大量的重复劳动,但这就意味着我们团队的新成员要使用这些被封装的验证方法之前必须先学习它们。而最糟糕的情况是很多开发人员可能会忘记使用这些由其他成员实 现的验证库,而自己重新编写具有同样功能的验证库。当然,这一切如果使用Validator框架就都可以得到解决。

    5.  客户端和服务端验证自动切换。我们只需要简单地在JSP页面中放一个单独的<html::javascript/>元素就可以将服务端的验证转换为客户端验证(基于JavaScript的验证)
   
虽然Validator框架的预定义验证已经可以满足大多数的验证需求了,但在某些特殊情况下,这些预定义验证就无法满足我们的需求了,为此,Validator框架也为开发人员提供了扩展验证机制的功能。这也使得Validator框架可以完成更复杂的验证工作。

二、配置和使用Validator框架

 1.  安装Validator框架

    由于ValidatorStruts的一个插件,因此,就需要在struts-config.xml文件中按着Struts插件的方式来安装Validator框架。打开struts-config.xml文件,在<struts-config>元素中加入一个<plug-in>子元素,如下面的代码所示:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validator.xml" />
</plug-in>     

其中<set-property>元素设置了插件中使用的pathnames属性的值。在pathnames属性值中包含了两个xml文件。

1validator-rules.xml:在这个文件中声明了Validator框架的预定义验证。这个文件可以在Struts的发行包的lib目录中可以找到这个文件。在使用MyEclipseWeb工程添加Struts功能后,会自动将这个文件加到WEB-INF目录中。

2validator.xml:这个文件定义了要验证的对象。实际上,在这个文件中,包含了一个或多个ActionForm的子类及其要验证的属性和验证规则。因此,这个文件就相当于validate方法。在Validator框架中,可以有多个定义验证对象的xml文件(可以将不同的ActionForm的子类分散到不同的xml文件中),中间用逗号(,)隔开,如下面的代码所示:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validator1.xml,
                                             /WEB-INF/validator2.xml, /WEB-INF/validator3.xml"
 />
</plug-in>     

2.  使用Validator框架的一个例子

   
在本节将举一个例子来演示如何使用Validator框架来验证数据。我们需要按着如下的六步来完成这个例子:

【第1步】建立FirstValidatorForm类(ValidatorForm的子类)

<samples工程目录>\src\actionform目录中建立一个FirstValidatorForm.java文件,代码如下:

  package actionform;
  
import org.apache.struts.validator.ValidatorForm;
  
public class FirstValidatorForm extends ValidatorForm  // 必须从ValidatorForm继承
  {
      
private String name;
      
private String age;
      
private String email;
      
public String getName()
      {
          
return name;
      }
      
public void setName(String name)
      {
          
this.name = name;
      }
      
public String getEmail()
      {
          
return email;
      }
      
public void setEmail(String email)
      {
          
this.email = email;
      }
      
public String getAge()
      {
          
return age;
      }
      
public void setAge(String age)
      {
          
this.age = age;
      }
  }

    要注意的是,要想使用Validator框架验证数据,Form类就必须从ValidatorForm继承,而不能从ActionForm继承。这是因为ValidatorForm类是从ActionForm继承的,在ValidatorForm类中已经覆盖了validate方法来自动进行验证工作,因此,我们在ValidatorForm的子类中就不用写validate方法了。

【第2步】建立ValidatorAction类(Action的子类)
   
<samples工程目录>\src\action目录中建立一个ValidatorAction.java文件,代码如下:

  package action;
  
import javax.servlet.http.*;
  
import org.apache.struts.action.*;
  
public class ValidatorAction extends Action
  {
      
public ActionForward execute(ActionMapping mapping, ActionForm form,
              HttpServletRequest request, HttpServletResponse response)
      {
          response.setCharacterEncoding("GBK");
          
try
          {
              response.getWriter().println("
验证成功!");
          }
          
catch (Exception e)
          {
          }
          
return null;
      }
  }

        ValidatorAction类是一个空的Struts动作类(除了输出一行“验证成功!”字符串)。这个动作是为了正常运行含有Struts元素的JSP程序所编写的。在以后的代码中会经常使用到这个Struts动作类。

【第3步】配置struts-config.xml文件

    配置FirstValidatorFormValidatorAction的代码如下所示

<form-bean name="firstValidatorForm" type=" actionform.FirstValidatorForm" />
<action name="firstValidatorForm" path="/firstValidator" scope="request" type=" action.ValidatorAction"  input="/firstValidator.jsp"/>       

其中firstValidator.jsp是用户录入信息的界面,也是显示错误信息的界面。

【第4步】建立firstValidator.jsp
   
Web根目录建立一个firstValidator.jsp文件,代码如下:

  <%@ page pageEncoding="GBK"%>
  
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
  
<html>
    
<head>
      
<title>第一个Validator程序</title>
    
</head>
    
<body>
      
<html:form action="firstValidator" >
         
  名:
<html:text property="name" />&nbsp;&nbsp;<font color="red"><html:errors property="name"/></font><p>
         
  龄:
<html:text property="age"/>&nbsp;&nbsp;<font color="red"><html:errors property="age"/></font><p>
       EMail

<html:text property="email"/>&nbsp;&nbsp;<font color="red"><html:errors property="email"/></font><p>
              
<html:submit value="提交"/>
      
</html:form>  
    
</body>
  
</html>

    firstValidator.jsp中可以看出,不管是否使用Validator框架进和验证,对于JSP代码来说是完全一样的。仍然是使用<html:errors>元素来显示错误信息。但要注意,在使用Validator框架时,<html:errors>标签的property属性的值就是所对应ValidatorForm的子类的属性名。

【第5步】配置validator.xml文件
   
在本例中只使用了一个XML文件(validator.xml)来配置要验证的对象。validator.xml的代码如下:

  <?xml version="1.0" encoding="GBK" ?>
  
<!DOCTYPE form-validation PUBLIC
   "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
            "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"
>
  
<form-validation>
      
<formset>
          
<form name="firstValidatorForm">
              
<field property="name" depends="required,maxlength,minlength,mask">
                  
<msg name="required" key="error.name.blank" />
                  
<msg name="minlength" key="error.name.minLength" />
                  
<msg name="maxlength" key="error.name.maxLength" />
                  
<msg name="mask" key="error.name.alphanum" />
  
                  
<arg name="minlength" key="${var:minlength}" position="0" resource="false" />
                  
<arg name="maxlength" key="${var:maxlength}" position="0" resource="false" />
                  
<var>
                      
<var-name>minlength</var-name>
                      
<var-value>5</var-value>
                  
</var>
                  
<var>
                      
<var-name>maxlength</var-name>
                      
<var-value>10</var-value>
                  
</var>
                  
<var>
                      
<var-name>mask</var-name>
                      
<var-value>^[a-zA-Z0-9]*$</var-value>
                  
</var>
              
</field>
              
<field property="age" depends="required,integer,intRange">
                  
<msg name="required" key="error.age.blank" />
                  
<msg name="integer" key="error.age.integer" />
                  
<msg name="intRange" key="error.age.intRange" />
  
                  
<arg name="intRange" key="${var:min}" position="0" resource="false" />
                  
<arg name="intRange" key="${var:max}" position="1" resource="false" />
                  
<var>
                      
<var-name>min</var-name>
                      
<var-value>18</var-value>
                  
</var>
                  
<var>
                      
<var-name>max</var-name>
                      
<var-value>60</var-value>
                  
</var>  
              
</field>
              
<field property="email" depends="required,email">
                  
<msg name="required" key="error.email.blank" />
                  
<msg name="email" key="error.email.invalid" />
              
</field>
          
</form>
      
</formset>
  
</form-validation>

        validator.xml文件中的所有配置都放到<form-validation>元素中。在<form-validation>元素中有一个<formset>子元素,这个元素可以定义多个<Form>元素,这个元素用来定义要验证的ValidatorForm类的子类。其中name属性值就是<form-bean>元素中的name属性值。

<field>元素用来定义某个属性的约束条件,如第一个<field>元素定义了name属性必须存在(required)、必须要满足最小长度(minlength)和最大长度(maxlength)以及还要通过mask所指的正则表达式的验证。

<msg>元素用来定义出错信息在属性文件中的Key(所有的出错信息都在属性文件中)。<arg>元素用来向出错信息中的参数传递参数值。<var>元素用来定义变量名和变量值。

【第6步】ErrorDescription.properties文件中添加错误信息

   
打开ErrorDescription.properties文件,在文件的后面添加如下的内容:

  error.name.blank = 姓名不能为空
  error.name.minLength = 
姓名的长度不能小于{0}
  error.name.maxLength = 
姓名的长度不能大于{0}
  error.name.alphanum = 
姓名必须由字母和数字组成
  error.age.blank = 
年龄不能为空
  error.age.integer = 
年龄必须为数字
  error.age.intRange = 
年龄必须在{0}{1}之间

  启动Tomcat,在IE中输入如下的URL来测试程序:

   
http://localhost:8080/samples/%20firstValidator.jsp

   
在输入一些错误信息后,点击“提交”按钮,将出现类似下图的效果。

将会按着depends属性中的约束来验证items属性的每一个值。我们还可以将propertyindexedListProperty配合使用,如下面代码所示:

  <field indexedListProperty="items"  property="employee.age"  depends=>  </field>

    Validator框架将根据上述的配置代码来验证items属性中的每一项的employee.age属性。

二、使用常量和变量

1. 常量

   
Struts1.x系列教程(10):Validator验证框架入门》一文中的name属性使用了mask进行验证。这个mask实际上代表了一个正则表达式。但如果在validator.xml文件中有很多个地方需要用到这个正则表达式,那就它就会在很多地方重复。为此,我们可以使用常量来避免这种事情发生。   

常量分为全局常量和局部常量。

1)全局常量

全局常量可以被用在validator.xml中定义的所有form中。我们可以使用<global>元素来配置全局常量。为了配置全局常量,将如下的内容放到第一个<formset>元素的前面。

  <global>
      
<constant>
          
<constant-name>mask</constant-name>
          
<constant-value> ^[a-zA-Z0-9]*$ </constant-value>
      
</constant>
  
</global>
  
<formset>

2)局部常量
   
局部常量需要放到<formset>元素的开始位置(根据DTD的定义,局部常量只能放到这里,而全局常量可以放在第一个<formset>元素的前面,也可以放到<formset>的后面)。如下面的代码所示:

  <formset>
      
<constant>
          
<constant-name>mask</constant-name>
          
<constant-value> ^[a-zA-Z0-9]*$ </constant-value>
      
</constant>
      
<form name="firstValidatorForm"> 
       
  
</formset>

 我们可以编写如下的代码来使用mask常量:

<var>
    <var-name>mask</var-name>
    <var-value> ${mask}</var-value>
</var>


2. 变量
    Validator
框架通过<var>元素来定义变量。变量将作为<field>的子元素被使用。主要向depends属性值 (如mask)以及<msg>的错误信息传递数据。如下面的代码通过变量required姓名传递给了错误信息的第一个参数{0}

<field property="name"  depends="required ">
    <msg name="required" key="error.name.blank" />
    <arg name="required" key="${var:required}" position="0" resource="false" />
    <var>
        <var-name>required</var-name>
       
<var-value>姓名</var-value>
   
</var>
</field>

三、客户端验证

 使用Validator框架的好处之一就是可以很容易地将服务端验证变为客户端验证(JavaScript验证)。为了完成这一转换,我们只需要修改两个地方。现在就拿Struts1.x系列教程(10):Validator验证框架入门》中的firstValidator.jsp为例来说明要修改什么。
   
首先,在firstValidator.jsp中的任何地方(当然,任何地方指的是客户端代码区,并不包括<%...%><%--... --%>所包含的内容)添加如下的<html:javascript>标签:

  <html:javascript formName="firstValidatorForm"/>

然后在<html:form>标签中加入如下的属性:

  onsubmit="return validateFirstValidatorForm(this);"

    现在再运行firstValidator.jsp,如果输入错误,IE就会直接弹出错误信息对话框。我们查看客户端源代码就会发现,在源代码中多了很多JavaScript代码。实际上,当加入<html:javascript>标签后,在运行JSP页面时,Servlet引擎就会将Validator框架中JavaScript版本的预定义验证代码连同HTML代码都发送到客户端。这些JavaScript代码中有一个入口函数,这个函数的名称前缀为validate,后面是首字母大写的Form名。在<html:form>标签中的onsubmit事件中调用这个入口函数就可以通过JavaScript代码来验证客户端录入的数据了。

下面列出了Validator框架的预定义验证(Struts的版本号为1.2.9)。

Validator

变量

引发条件

required


字段只有空格

validwhen

test

test条件失败(详见下一节)

minlength

minlength

字段的字符数小于minlength

maxlength

maxlength

字段的字符数大于maxlength

mask

mask

字段值不匹配mask所指的个正则表达式

byteshortintegerlongfloatdouble


字段值无法转换为这些数据类型

date

datePattern

datePatternStrict

字值值不能按着指定的格式转换为日期类型

intRangefloatRangedoubleRange

minmax

字段值不在指定的范围内

creditCard


字段值不是一个信誉卡号

email


字段值不是一个合法的email

url

allowallschemes

allow2slashes

nofragments

schemes

字段不是一个URL

关于Validator框架标准验证的详细信息,读者可以访问如下的URL  

http://struts.apache.org/1.2.9/userGuide/dev_validator.html

    下面来举一个validwhen验证的例子。
    validwhen
可以验证当前属性和其他属性的关系,如在验证密码时,要保证两次输入的密码一致,验证代码如下:

<field property="password1" depends="validwhen">
    <msg name="validwhen" key="error.password1.confirmation" />
   
<var>
       
<var-name>test</var-name>
       
<var-value>
            (password1!= null) and (*this* == password)
       
</var-value>
   
</var>
</field>


   
使用validwhen也可以验证嵌套和索引属性,如下面的代码所示:

<field property="field1" depends="validwhen">
    <msg name="validwhen" key="error.field.message" />
   
<var>
       
<var-name>test</var-name>
       
<var-value>
            (*this* == field2.name) and (*this* != field3[1])
       
</var-value>
   
</var>
</field>

    其中*this*表示当前属性的值。

====================================================================

一、动态Form简介

    虽然ActionForm功能强大,但有些时候使用起来有些麻烦。如每建立一个用户录入界面(JSP页面),就得建立一个ActionForm子类来和这个页面对应。当然,我们可以采用嵌套属性从一定程度上弥补这个问题。但是在建立新的用户录入界面时,仍不可避免地要建立新的ActionForm子类。

    Struts1.2.6及以后的Struts版本中提供了一种动态Form的技术。使得不用再建立新的ActionForm就可以封装用户提交的数据。实际上,这种技术将定义ActionForm子类的工作变成了编写XML文件的工作。

    每定义一个动态Form,就要在struts-config.xml中加一个<form-bean>元素,并使用<form-property>子元素来定义动态Form的属性。

    在本章的最后还会介绍一个LazyValidatorForm类,通过这个类甚至可以不定义动态Form的属性就可以使用动态Form。这将大大简化开发人员的工作量。    

二、声明动态Form 

    声明一个动态Form非常简单,只需要在struts- config.xml<form-beans>元素中加入一个<form-bean>子元素,并使用<form- property>元素来定义动态Form的属性。我们可以定义的属性类型有简单属性(如String)、索引属性(如数组)、映射属性(如 HashMap)以及嵌套属性(属性类型是另一个类)。
   
对于动态Form来说,Form的类型必须是org.apache.struts.action.DynaActionForm或其子类。声明一个动态Form的代码如下:

<form-bean name="dynamicForm"  type="org.apache.struts.action.DynaActionForm">
    <!-- 声明一个简单属性 -->
    <form-property name="simpleProp" type="java.lang.String" initial="bill"/>
    <!-- 声明一个索引属性 -->
    <form-property name="indexedProp" type="java.lang.String[]" />
    <!-- 声明一个映射属性 -->
    <form-property name="mappedProp" type="java.util.HashMap" />
    <!-- 声明一个嵌套属性 -->
    <form-property name="nestedProp" type="com.bean.MyBean" />
</form-bean>

三、动态Form的属性类型

    下面列出了动态Form支持的简单属性的所有类型:

  • java.math.BigDecimal
  • java.math.BigInteger
  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Character
  • java.lang.Class
  • java.lang.Double
  • java.lang.Float
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Short
  • java.lang.String
  • java.sql.Date
  • java.sql.Time
  • java.sql.Timestamp 

实际上,上面的数据类型就是Java中提供了简单数据类型。它们用在动态Form中和在Java中代表的数据类型是完全一样的。我们还可以使用<form-property>元素的initial属性为动态Form的简单属性指定一个默认值。如在例程6-11name属性的默认值为“bill”。

对于索引属性的类型来说,可以是数组,也可以是java.util.List接口的类(如ArrayList)。我们还可以为动态Form的属性指定实现java.util.Map接口的类作为数据类型(也就是映射属性)。但遗憾的是,如果使用动态Form,就无法使用泛型进行自动类型转换了。

四、访问动态Form

    我们可以使用DynaActionForm类的getter方法来读取动态Form中的属性值。DynaActionForm类的getter方法有三个重载形式,分别用来读取简单属性、索引属性和映射属性。这三个getter方法的定义如下:

  public Object get(String name) ;   // 读取简单属性
  public Object get(String name, int index) ;  // 读取索引属性
  public Object get(String name, String key);  // 读取映射属性

   
   
下面的代码演示了如何通过getter方法获得动态Form的属性值:

DynaActionForm dForm = (DynaActionForm)form;
String name = (String)dForm.get("name");
String[] hobbies = (String[])dForm.get(“hobbies”);
String value1 = (String)dForm.get(“myMap”, “key1”);

    除了上述的get方法外,DynaActionForm还提供了getStringgetString方法,分别用来读取StringString[]类型的属性值,这两个方法的定义如下:

public String getString(String name) ;  
public String[] getStrings(String name) ;

    下面的代码演示了如何通过getStringgetStrings方法获得动态Form的属性值:

String name = dForm.getString(“name”);  // 相当于String name = (String)dForm.get("name");
String[] hobbies = dForm.getStrings(“hobbies”);  // 相当于String[] hobbies = (String[])dForm.get(“hobbies”);

    在使用getStringgetStrings方法时应注意,这两个方法只能读取StringString[]类型的属性,读取其他类型的属性将会抛出异常。  

五、一个动态Form的例子

    我们在这一部分来实现一个完整的动态Form的例子,在这个例子中的动态Form有四个属性,三个是简单数据类型,一个是数组类型。完成这个例子需要如下四步:

【第1步】配置动态Form
   
打开struts-config.xml,在<form-beans>元素中加入如下的内容:

<form-bean name="dynamicForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="name" type="java.lang.String" />
    <form-property name="age" type="java.lang.Short" />
    <form-property name="salary" type="java.lang.Float" />
    <form-property name="hobby" type="java.lang.String[]" />
</form-bean>

【第2步】编写Struts Action
   
这个Struts Action类负责从动态Form中获得属性值,并输出到客户端浏览器。在<samples工程目录>"src"action目录中建立一个DynamicAction.java文件,代码如下:

  package chapter6.action;
  
  
import javax.servlet.http.*;
  
import org.apache.struts.action.*;
  
import java.io.*;
  
  
public class DynamicAction extends Action
  {
      
public ActionForward execute(ActionMapping mapping, ActionForm form,
              HttpServletRequest request, HttpServletResponse response)
      {
          
try
          {
              DynaActionForm dForm = (DynaActionForm) form;
              String name = (String) dForm.get("name");
              Short age = (Short) dForm.get("age");
              Float salary = (Float) dForm.get("salary");
              
// 获得数组类型字段值的数组长度
              int hobbyCount = ((String[]) dForm.get("hobby")).length;
              PrintWriter out = response.getWriter();
              out.println("name: " + name + "<p/>");
              out.println("age: " + age + "<p/>");
              out.println("salary: " + salary + "<p/>");
              
for (int i = 0; i < hobbyCount; i++)
                  out.println("hobby" + (i + 1) + ": " + dForm.get("hobby", i) + "<p/>");
          }
          
catch (Exception e)
          {
          }
          
return null;
      }
  }

【第3步】配置Struts Action     

    打开struts-config.xml文件,在<action-mappings>元素中加入如下的内容:

  <action name="dynamicForm" path="/dynamic" scope="request" type="action.DynamicAction" />


【第4步】编写用户录入数据的JSP页面

    Web根目录中建立一个dynamic.jsp文件,代码如下:

  <%@ page pageEncoding="GBK"%>
  <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
  
<html>
    
<head>
      
<title>测试动态Form</title>
      
<html:base/>
    
</head>
    
<body> 
      
<html:form action="dynamic"  >
         
  名:<html:text property="name" />&nbsp;&nbsp;<p>
         
  龄:<html:text property="age" />&nbsp;&nbsp;<p>
         
  资:<html:text property="salary" />&nbsp;&nbsp;<p>
         
爱好1<html:text property="hobby"  value=""/>&nbsp;&nbsp;<p>
         
爱好2<html:text property="hobby" value=""/>&nbsp;&nbsp;<p>
         
爱好3<html:text property="hobby" value=""/>&nbsp;&nbsp;<p>
                
<html:submit value="提交" />
      
</html:form>  
    
</body> 
  
</html>


   
启动Tomcat后,在IE中输入如下的URL来测试程序:

http://localhost:8080/samples/dynamic.jsp

六、验证动态Form

有两种方法可以验证动态Form

1. DynaActionForm的子类中覆盖validate方法。

2. 如果要使用Validator框架来验证动态Form,需要用DynaActionForm的子类org.apache.struts.validator. DynaValidatorForm或其子类来作为动态Form的类型。

在使用DynaValidatorForm的了类时,要想使用Validator框架的验证机制,需要在DynaValidatorForm子类的validate方法的开始位置使用 super.validate()语句来调用DynaValidatorForm中的validate方法</span></span></span>