在书中,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
。现在,由于静态块将在服务器启动时进行评估(一次),因此在服务器启动时也会发生(一次)两个函数调用,并且应该在非闭包形式中导致相同且一致的行为。 不是吗?
答案 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
的特定实例无关。
接下来,很容易发现name
,startDate
等确实是函数调用。只是尝试在不存在的属性上指定约束:
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]]
当然,您从未定义过所有这些方法,例如name
,startDate
等,您也无法从其他任何方面继承您的域类。但是由于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()
}
您不会看到任何名为name
,startDate
等的方法,您也可以在println Race.name
块内constraints { }
。我认为发生的是Groovy拦截对不存在的方法Race.name
,Race.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()