Grails 2.2.4:瞬态属性:为啥自定义验证器被调用两次?

Posted

技术标签:

【中文标题】Grails 2.2.4:瞬态属性:为啥自定义验证器被调用两次?【英文标题】:Grails 2.2.4: Transient Property: Why is the Custom Validator being called twice?Grails 2.2.4:瞬态属性:为什么自定义验证器被调用两次? 【发布时间】:2016-05-13 08:35:53 【问题描述】:

给定一个具有瞬态属性的简单域,例如:

package org.example.domain

class Ninja 

    String name
    String sensei

    static transients = ['name']

    static constraints = 
        name nullable:false, bindable:true, validator:  val, obj, errors ->
            obj.log.error "[VALIDATING] 'name': $val, $obj FIRED!", new Exception()
        
        sensei nullable:false, bindable:true, validator: val, obj, errors ->
            obj.log.error "[VALIDATING] 'sensei': $val, $obj FIRED!", new Exception()
        
    

还有一个针对域约束的简单单元测试,如下:

package org.example.domain

import grails.test.mixin.Mock
import spock.lang.Specification
import grails.test.mixin.TestFor

@TestFor(Ninja)
class NinjaSpec extends Specification 

    def "Should not fire the name's custom validator twice"() 
        given:
            def instance = new Ninja(name: 'Naruto',
                                   sensei: 'Kakashi')
        when:
            instance.validate(['sensei'])
        then:
            instance.errors['name'] == null
    

当我运行这个规范时,它只是为了验证非瞬态属性 sensei,发生了两件意想不到的事情:

    正在调用两个自定义验证器; 不仅名称的自定义验证器被调用,而且事实上,它被调用两次

检查一下:

➜  grails test-app unit:spock -echoOut NinjaSpec

| Running 1 unit test... 1 of 1

--Output from Should not fire the name's custom validator twice--

2016-02-04 19:08:25.208 [main] grails.app.domain.org.example.domain.Ninja
 ERROR [VALIDATING] 'name': Naruto, org.example.domain.Ninja : (unsaved) FIRED!

java.lang.Exception
    at org.example.domain.Ninja$__clinit__closure1_closure2.doCall(Ninja.groovy:15)
    at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
    at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
    at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
    at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
    at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
    at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
    at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
    at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
    at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
    at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
    at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
    at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
    at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
    at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
    at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
    at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
    at TestApp$_run_closure1.doCall(TestApp:82)

2016-02-04 19:08:25.216 [main] grails.app.domain.org.example.domain.Ninja
 ERROR [VALIDATING] 'sensei': Kakashi, org.example.domain.Ninja : (unsaved) FIRED!

java.lang.Exception
    at org.example.domain.Ninja$__clinit__closure1_closure3.doCall(Ninja.groovy:18)
    at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
    at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
    at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
    at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
    at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
    at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
    at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
    at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
    at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
    at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
    at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
    at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
    at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
    at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
    at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
    at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
    at TestApp$_run_closure1.doCall(TestApp:82)

2016-02-04 19:08:25.223 [main] grails.app.domain.org.example.domain.Ninja
 ERROR [VALIDATING] 'name': Naruto, org.example.domain.Ninja : (unsaved) FIRED!

java.lang.Exception
    at org.example.domain.Ninja$__clinit__closure1_closure2.doCall(Ninja.groovy:15)
    at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
    at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
    at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
    at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
    at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
    at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
    at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
    at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
    at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
    at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
    at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
    at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
    at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
    at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
    at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
    at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
    at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
    at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
    at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)

| Completed 1 spock test, 0 failed in 2872ms

因此,我只想知道的是:

考虑到测试只验证属性sensei

为什么要调用两个自定义验证器? 另外,为什么 transient 属性 name 的自定义验证器会被调用两次?

Sample Project @ github

==== 编辑:

一件有趣的事:

奇怪的是,每当我在名称的自定义验证器中放置一个rejectValue 时,例如:

name nullable:false, bindable:true, validator:  val, obj, errors ->
    obj.log.error "[VALIDATING] 'name': $val, $obj FIRED!", new Exception()
    errors.rejectValue 'name', 'should.not.be.fired!', [].toArray(), null

自定义验证器只被调用一次。

但是,不应该调用名称的自定义验证器吗?

【问题讨论】:

【参考方案1】:

这个问题与Spock无关,主要与Grails有关。

据我所知,Grails 无法保证何时以及多少次调用 bean 的约束。也许它们被调用一次,也许是 100 次。

Grails 测试并不完全是单元测试(具有正确的术语定义)。它们在 Grails 引擎中运行,所以很多事情都发生在幕后。

另外需要注意的是,您已经使用 @TestFor 注释标记了您的测试,但您没有使用 Grails 创建的测试 Ninja 实例,而是创建了自己的(可能在 Grails 上下文之外创建)

我手头没有安装 grails 2,但我认为正确的单元测试应该使用由 @TestFor 创建的字段,或者为你的 Ninja 类使用 @Mock() 注释。

【讨论】:

以上是关于Grails 2.2.4:瞬态属性:为啥自定义验证器被调用两次?的主要内容,如果未能解决你的问题,请参考以下文章

Grails 域中的瞬态属性

Grails 瞬态属性未在对象创建时获取

Grails - 非空属性引用空值或瞬态值

Grails:在地图构造函数中设置瞬态字段

Grails - 非空属性仅对并发用户引用空值或瞬态值错误

如何使用 JOSSO 和 Spring Security 从 Grails 应用程序中的 LDAP 获取自定义属性?