在前面的几篇中,我们已经看到了Grails的验证框架带来的便利,现在让我们深入对其进行了解。
和Grails的大多数特性一样,Grails的验证框架同样也是建立在Spring之上的,不同的是它是以Spring的验证和绑定API为基础。通过前面的内容,我们已经知道Domain Class、Command Object和URL Mappings都可以使用它,具体的形式就是“约束”。以Domain Class为例,我们回顾一下它的写法:
class User {
    ……   
    static constraints = {
   ……
    }
}
同时,我们也知道,有些约束会影响到数据库模式的生成,它们是:
  • nullable:验证是否可为null
  • inList:限制值在一个范围或集合内
  • maxSize(minSize):限制属性值大小,对应列大小
  • max(min):设定最大(小)值,属性必须是java.lang.Comparable
  • range:限定值范围,属性必须是java.lang.Comparable
  • scale:限定小数位
  • size:限定属性值大小范围
  • unique:限定唯一
其他的约束则有:
  • blank:验证字符串是否为empty
  • creditCard:验证字符串是否为信用卡
  • email:验证字符串是否为邮件地址
  • url:验证字符串是否是URL
  • matches:匹配正则表达式
  • notEqual:不等于
如果这些预定义的约束无法满足要求,我们还可以使用validator约束进行自定义验证。其返回值决定了验证结果:
  • boolean值:false:失败;true:成功
    password1( validator: {
           val, obj ->
              obj.properties['password2'] == val})
  • 字符串:返回错误码,在显示错误消息时用,对应“class.property.错误码”;返回null则表示成功
    login(validator: {
            if (!it.startsWith('boba')) 
                return ['invalid.bountyhunter']})
  • 字符串列表:返回[错误码,参数1,参数2…],对应“错误消息+参数值”;返回null表示成功
    otherProperty( validator: { 
            return ['custom.error', arg1, arg2] } )
验证的阶段有2:
  1. 阶段1:数据绑定,验证Request参数是否可转换成对象属性。
  2. 阶段2:validate或save,save会调用validate。基于约束进行验证。
如果验证出错,获得错误的方法有:
  • 法1:
    if(user.hasErrors()) {
         if(user.hasFieldError("login")) {
          println user.getFieldError("login").rejectedValue
         }
        }
  • 法2:
    user.errors.allErrors.each { println it }
在客户端显示错误消息的方法有:
  • 简单列举:<g:renderErrors bean="${user}" />
  • 更多控制:<hasErrors>和<eachError>
  • 单独显示并检索输入值:fieldValue
    <div class='value ${hasErrors(bean:user,field:'login','errors')}'>
           <input type="text" name="login" 
                       value="${fieldValue(bean:user,field:'login')}"/>
        </div>
这些内容其实在Grails根据Domain Class产生的页面代码中都有。
Grails对国际化提供了支持,这一部分内容会在以后详谈,表现在验证部分,就是错误消息的国际化。对于国际化有所了解的人都知道,所谓的国际化实际就是不直接输出消息内容,而是以消息键取而代之,然后准备好支持目标语言的各种消息内容,在运行时根据Locale决定消息键的消息内容。
验证中错误消息的键约定:Class.Property.Constraint,对于很多错误消息,Grails已经提供了缺省的内容,如果不满意,直接按约定覆盖即可。页面使用<g:message>来显示消息内容:
<g:hasErrors bean="${user}">
  <ul>
   <g:eachError var="err" bean="${user}">
       <li><g:message error="${err}" /></li> 
   </g:eachError>
  </ul>
</g:hasErrors>
Grails的验证能力还可以扩展到其他对象上去,具体步骤有2:
  1. 在类中定义constraints静态属性
  2. 告知Grails框架
具体的操作,则有2种方法。
方法1:
  1. 将类用@Validateable注解
  2. 在类中定义constraints静态属性
    import org.codehaus.groovy.grails.validation.Validateable
        @Validateable
        class User {
            ...
            static constraints = {
                login(size:5..15, blank:false, unique:true)
                password(size:5..15, blank:false)
                email(email:true, blank:false)
                age(min:18, nullable:false)
            }
        }
  3. 在Config.groovy中定义要查找@Validateable的包名:grails.validateable.packages
    grails.validateable.packages = ['com.mycompany.dto', 'com.mycompany.util']
方法2:
  1. 在类中定义constraints静态属性
    class User {
            ...
            static constraints = {
                login(size:5..15, blank:false, unique:true)
                password(size:5..15, blank:false)
                email(email:true, blank:false)
                age(min:18, nullable:false)
            }
        }
  2. 在Config.groovy中注册类名: grails.validateable.classes
    grails.validateable.classes = [com.mycompany.myapp.User
                                     , com.mycompany.dto.Account]
但是在使用时,你必须注意( 这是文档中没有的!):
  • Grails会动态给这些类加入validate、hasErrors、getErrors、setErrors、clearErrors等方法。
  • 在调用validate之后,如果validate为false,在重新进行validate之前,必需调用clearErrors。
  • 在Domain Class上无需如此。
不信的话,大家可以试一试以下的代码(在Grails Console中运行即可):
def u= new foxgem.test1.User1(name:'')
assert u.validate() == false
u.name='foxgem'
assert u.validate() == false
u.clearErrors()
assert u.validate() == true
因为比较简单,具体内容就不列出了,只简要说明一下:
  • User1不要放到domain目录下,这样Grails会把它当作domain class
  • name上的约束是:“blank:false”
最后,在独家报料一个文档上没有的内容,对Domain Class进行部分验证。这种需求产生的原因:有可能某个Domain Class是由多个Controller产生的,而每个Controller只关心部分属性,因此产生了部分验证的需要。解决办法:在validate中传入一个属性字符串列表,这时validate就只验证该列表中的属性。例子如下:
def p = new Person()
p.validate(['firstName', 'lastName', 'age'])
ps:这个技巧来自Grails用户组邮件列表,就在本文刚刚写完之时,该内容已经加入到了文档中;):http://hudson.grails.org/job/grails_core_1.2.x/ws/grails-doc/output/ref/Domain%20Classes/validate.html