【问题标题】:bean validation not working with kotlin (JSR 380)bean 验证不适用于 kotlin (JSR 380)
【发布时间】:2019-02-20 01:16:32
【问题描述】:

所以首先我想不出一个更好的标题来解决这个问题,所以我愿意改变。

我正在尝试使用带有 spring boot 的 bean 验证机制 (JSR-380) 来验证 bean。

所以我得到了一个这样的控制器:

@Controller
@RequestMapping("/users")
class UserController {
    @PostMapping
    fun createUser(@Valid user: User, bindingResult: BindingResult): ModelAndView {
        return ModelAndView("someview", "user", user)
    }
}

这是用 kotlin 编写的 User 类:

data class User(
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role> = HashSet()
)

这就是测试:

@Test
internal fun shouldNotCreateNewTestWithInvalidParams() {
    mockMvc.perform(post("/users")
        .param("roles", "invalid role"))
        .andExpect(model().attributeHasFieldErrors("user",  "roles[]"))
}

无效角色被映射为 null。

如您所见,我希望roles 至少包含一项,其中没有一项为空。 但是,在测试上述代码时,如果roles 包含空值,则不会报告绑定错误。如果集合为空,它确实会报告错误。 我在想这可能是 kotlin 代码如何编译的问题,因为当 User 类是用 java 编写时,相同的代码工作得很好。像这样:

@Data // just lombok...
public class User {
    @NotEmpty
    private Set<@NotNull Role> roles = new HashSet<>();
}

相同的控制器,相同的测试。

检查字节码后,我注意到 kotlin 版本不包括嵌套的 @NotNull 注释(见下文)。

Java:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Ljavax/validation/constraints/NotNull;() : FIELD, 0;
@Ljavax/validation/constraints/NotEmpty;() : FIELD, null

科特林:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Lorg/jetbrains/annotations/NotNull;() // added because roles is not nullable in kotlin. this does not affect validation

现在的问题是为什么?

这里是sample project,以防你想尝试一些东西。

【问题讨论】:

  • 在 \@NotEmpty 之前只在课堂用户中添加 \@NotNull 并尝试
  • 仍然失败,java.lang.AssertionError: No errors for field 'roles[]' of attribute 'userDto' 并且在这种情况下实际上是有意义的,因为将@NotNull 添加到角色只会确保roles 永远不会为空。但是我想验证roles 中的项目。
  • 是的,我也有同样的问题。我想我暂时必须创建自定义验证器。 twitter.com/GlowinskiRafal/status/1043183121796083718
  • @TommySchmidt 我使用您提供的所有详细信息创建了一个 Kotlin 问题:youtrack.jetbrains.com/issue/KT-27049 - 希望您不介意 :)
  • @RafalG。我一点也不中。感谢发布!

标签: java spring-boot kotlin bean-validation jsr380


【解决方案1】:

答案(Kotlin 1.3.70)

确保使用 jvm 目标 1.8 或更高版本编译 kotlin 代码,并通过在编译时提供 -Xemit-jvm-type-annotations 来启用此功能。

对于 Spring Boot 项目,您只需进行以下更改(使用 Spring Boot 2.3.3 和 Kotlin 1.4.0 测试):

  1. 在您的 pom 中设置以下属性:
    <properties>
        <java.version>11</java.version>
        <kotlin.version>1.4.0</kotlin.version>
    </properties>
    
  2. &lt;arg&gt;-Xemit-jvm-type-annotations&lt;/arg&gt; 添加到kotlin-maven-plugin
    <build>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <groupId>org.jetbrains.kotlin</groupId>
            <configuration>
                <args>
                    <arg>-Xjsr305=strict</arg>
                    <arg>-Xemit-jvm-type-annotations</arg>
                </args>
                <compilerPlugins>
                    <plugin>spring</plugin>
                </compilerPlugins>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-allopen</artifactId>
                    <version>${kotlin.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </build>
    

Sample Project

Jetbrains Release Notes


解决方法(Kotlin 1.3.70 之前)

Rafal G. 已经指出我们可以使用自定义验证器作为解决方法。所以这里有一些代码:

注释:

import javax.validation.Constraint
import javax.validation.Payload
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CONSTRUCTOR
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
import kotlin.reflect.KClass

@MustBeDocumented
@Constraint(validatedBy = [NoNullElementsValidator::class])
@Target(allowedTargets = [FUNCTION, FIELD, ANNOTATION_CLASS, CONSTRUCTOR, VALUE_PARAMETER, TYPE_PARAMETER])
@Retention(AnnotationRetention.RUNTIME)
annotation class NoNullElements(
    val message: String = "must not contain null elements",
    val groups: Array<KClass<out Any>> = [],
    val payload: Array<KClass<out Payload>> = []
)

约束验证器:

import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext

class NoNullElementsValidator : ConstraintValidator<NoNullElements, Collection<Any>> {
    override fun isValid(value: Collection<Any>?, context: ConstraintValidatorContext): Boolean {
        // null values are valid
        if (value == null) {
            return true
        }
        return value.stream().noneMatch { it == null }
    }
}

最后是更新的 User 类:

data class User(
    @field:NotEmpty
    @field:NoNullElements
    var roles: MutableSet<Role> = HashSet()
)

虽然验证现在有效,但生成的 ConstrainViolation 略有不同。例如,elementTypepropertyPath 不同,如下所示。

Java:

科特林:

来源可在此处获得:https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround

再次感谢您的帮助Rafal G.

【讨论】:

  • @mazend 你有这方面的例子吗?在我的测试中,即使使用 kotlin 1.5.10 和 spring boot 2.5.0,仍然需要 &lt;arg&gt;-Xemit-jvm-type-annotations&lt;/arg&gt;。见:github.com/DarkAtra/jsr380-kotlin-issue/tree/…
  • @mazend 我刚刚更新了代码(参见上面的链接),这是我收到的日志消息:bindingResult.hasErrors() = false(确保使用mvn clean verify 运行代码)
  • 这很奇怪。当我运行UserControllerTest 时,我得到了与您相同的结果。但是,当我从 POSTMAN 发送空 POST 请求时,两种情况都显示bindingResult.hasErrors() = true。没有&lt;arg&gt;-Xemit-jvm-type-annotations&lt;/arg&gt;,似乎AutoConfigureMockMvc 无法工作。你怎么看?其实我对 Spring Test 并不熟悉。
  • 看来你是对的。所以我们需要包含&lt;arg&gt;-Xemit-jvm-type-annotations&lt;/arg&gt; 来验证所有约束?
  • 是的,如果没有 &lt;arg&gt;-Xemit-jvm-type-annotations&lt;/arg&gt;,kotlin 编译器会丢弃所有 TYPE_PARAMETER 注释(例如泛型上的注释) - 您还可以看到相应字节码的差异(参见问题)
【解决方案2】:

尝试像这样添加?

data class User(
    @field:Valid
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role?> = HashSet()
)

那么 kotlin 编译器应该意识到角色可能是null,它可能会尊重验证,我对 JSR380 知之甚少,所以我只是猜测。

【讨论】:

  • 您好,谢谢您的回答!可悲的是,添加一个? to Role 没有任何区别。从 youtrack.jetbrains.com/issue/KT-13228 开始,这似乎是 kotlin 中的一个错误/缺失功能。我会更新我的问题并指出解决方法,直到它得到修复。
  • 这是最佳答案!谢谢!!!
猜你喜欢
  • 2018-10-02
  • 2016-06-21
  • 1970-01-01
  • 1970-01-01
  • 2016-08-04
  • 2015-01-10
  • 1970-01-01
  • 2019-05-22
  • 2013-11-07
相关资源
最近更新 更多