7

在《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. 现在,由于静态块将在服务器启动时被评估(一次),两个函数调用也将在服务器启动时发生(一次),并且应该以它们的非封闭形式导致相同且一致的行为。不是这样吗?

4

2 回答 2

10

如果你去:

    startDate(min: new Date())

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

第二种形式:

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

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

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

编辑:

当调用name( maxSize:50 )call 被评估时,它实际上为 field创建了一个MaxSizeConstraint对象name。然后,当验证对象时,grails 会使用此映射property->constraints来检查属性。正如您在该类中看到的那样,maxSize是私有财产。事实上,如果你想maxSize随着时间的推移而改变,那么你需要使用自定义验证器Date

于 2013-07-01T14:20:55.830 回答
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.nameconstraints { }块内。我认为发生的情况是,Groovy 拦截了对不存在的方法Race.nameRace.startDate等的调用,并将此约束信息记录在其他地方以供将来使用。如果您愿意,请尝试实际实现名为 eg 的方法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()
于 2013-10-07T23:40:10.853 回答