Groovy / Grails:如何实现约束?

时间:2013-07-01 14:14:20

标签: validation grails groovy closures constraints

在书中,Getting Started with Grails - 2e,第42页(电子页面,而不是纸质页面),列出了以下示例。

class Race {

    static constraints = {

        name(blank:false, maxSize:50)


        // NOTE: This doesn’t do
        // what you think it does
        startDate(min: new Date())

        // And this is what we're supposed
        // to be using:
        // startDate(validator: {return (it > new Date())})
    }

    String name
    Date startDate
    // ...
}

然后建议读者使用上面注释的startDate版本。引用的原因是:static constraints属性只会被评估一次(在服务器启动时),而我们的目的是在每个实例化和Race的后续验证中评估它。

我的问题是:为什么name约束的非闭包样式适用于每次验证尝试但不适用于startDate约束?而且,相反,如果startDate需要封闭风格,那么为什么name也不需要它?

如果我正确理解上面的Groovy语法,似乎static constraint块中列出的每个约束在语法上都是对函数的调用,该函数将各种验证属性作为Map。现在,由于静态块将在服务器启动时进行评估(一次),因此在服务器启动时也会发生(一次)两个函数调用,并且应该在非闭包形式中导致相同且一致的行为。 不是吗?

2 个答案:

答案 0 :(得分:10)

如果你选择:

    startDate(min: new Date())

然后new Date()将在服务器启动时进行评估,永远不会改变。因此,下周(假设服务器继续运行),它将验证上周的日期。

第二种形式:

    startDate(validator: {return (it > new Date())})

每次检查约束时都会对其进行评估,因此无论服务器运行多长时间,它都将始终对其进行验证。

另一方面,当涉及name时,会针对静态内容进行验证,即maxSize为50,将其作为键值对而不是使用validator是有意义的关闭,因为50每次对name进行验证时都不会评估值startDate

编辑:

评估来电name( maxSize:50 )来电时,实际上会为字段name创建MaxSizeConstraint object。然后,grails使用此property->constraints映射来检查对象验证时的属性。正如您在该课程中看到的那样,maxSize是一个私有财产。实际上,如果您希望maxSize随时间变化,那么您需要使用自定义验证器与Date

一样

答案 1 :(得分:1)

我做了一些实验,这里有一些结论。这都是实证研究;如果我错了,请纠正我。

首先,当对constraints的调用进行评估时,很容易看到。只需添加一些调试输出:

static constraints = {
        println "Entering constraints..."
        name(blank:false, maxSize:50)
        // etc.
        println "Exiting constraints..."
}

您将看到它确实在应用程序启动时进行了评估。由于某些我不理解的原因,通常会评估两次。另请注意,constraints被标记为静态,因此它与class Race的特定实例无关。

接下来,很容易发现namestartDate等确实是函数调用。只是尝试在不存在的属性上指定约束:

static constraints = {
  no_such_thing(nullable:true)
}

你将无法做到这一点!结果:

Error Error executing script Shell: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': 
       Invocation of init method failed; nested exception is org.codehaus.groovy.runtime.InvokerInvocationException: groovy.lang.MissingMethodException:
     No signature of method: Race.no_such_thing() is applicable for argument types: (java.util.LinkedHashMap) values: [[nullable:true]]

当然,您从未定义过所有这些方法,例如namestartDate等,您也无法从其他任何方面继承您的域类。但是由于Grails将它识别为域类,它使用Groovy的强大功能将方法注入到对象中,绕过了传统的面向对象编程的限制。

现在,它并没有真正地将方法注入到对象中。您可以轻松查看:

static constraints = {
    println Race.metaClass.methods*.name.sort().unique()
    // You can even construct an object if you really want to mess with the framework
    println new Race().metaClass.methods*.name.sort().unique()
}

您不会看到任何名为namestartDate等的方法,您也可以在println Race.name块内constraints { }。我认为发生的是Groovy拦截对不存在的方法Race.nameRace.startDate等的调用,并将此约束信息记录在其他地方以供将来使用。如果你愿意,试试实际实现的方法,例如Race.name;我认为我能够通过这样做来阻止这些限制,但我无法重现它。

关于什么时候评估的问题,我认为我们在这里有一些关于Groovy闭包的混淆。看看

startDate(validator: {return (it > new Date())})

这里我们有一个闭包{return (it > new Date())}。如果Groovy是像Python这样的纯解释语言,它只会存储这个文字代码并在任何一个调用中重新解释它。因此,您也可以获得最新的日期。我的猜测是Groovy模仿了这种行为,即使代码可能是编译的:它会将此代码包装到一个闭包对象中,并且每次请求验证时都会调用此对象。此闭包对象将存储在某处;据推测,在存储所有其他约束的同一地方。由于闭包的性质而产生混淆:如果您存储3,它将始终保持3;如果你存储一个闭包(一个函数),它可以在不同的场合评估不同的结果。

重申一下,代码

{return (it > new Date())}

不执行"或"评估"在应用程序启动;它只是存储以备将来使用。你可以轻松检查它:

static constraints = {
    startDate(validator: {println "Validating startDate..."})
}

然后运行grails shell

groovy> (new Race()).validate()