Kotlin Spring Boot bean 验证不起作用

Posted

技术标签:

【中文标题】Kotlin Spring Boot bean 验证不起作用【英文标题】:Kotlin Spring Boot bean validation not working 【发布时间】:2022-01-09 21:44:21 【问题描述】:

我有很多项目正在慢慢地从 Java 迁移到 Kotlin,但是在从 Java POJO 更改为 Kotlin 数据类时遇到了问题。 Bean 验证在 REST 控制器中停止工作。我直接从https://start.spring.io 创建了一个非常简单的项目来演示失败。

@SpringBootApplication
class RestValidationApplication

fun main(args: Array<String>) 
    runApplication<RestValidationApplication>(*args)


@RestController
class Controller 

    @PostMapping
    fun test(@Valid @RequestBody request: Test) 
        println(request)
    


data class Test(@field:NotNull val id: String)

和毕业:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins 
    id("org.springframework.boot") version "2.6.1"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.0"
    kotlin("plugin.spring") version "1.6.0"


group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories 
    mavenCentral()


dependencies 
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")


tasks.withType<KotlinCompile> 
    kotlinOptions 
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    
 

发送请求不会触发 bean 验证,但会抛出 HttpMessageNotReadableException,因为 id 为 NULL。

curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d ""

我尝试了@Valid@Validated 并在属性上使用@get:NotNull,但没有任何效果。我看到许多其他人也有同样的问题,并且使用 Kotlin REST 控制器中的 Java 类可以工作。 更改为非数据类也可以进行验证。任何人都知道是否可以使用 Kotlin 数据类进行 bean 验证?

【问题讨论】:

我可能错了,但是控制器类可能也应该用@Validated注解... 它应该按原样工作。您是否尝试过使用 Spring Boot 2.5.X 或添加 hibernate-validator 依赖项? 我已经尝试过 Spring boot 2.4.132.5.72.6.1。还尝试将org.springframework.boot:spring-boot-starter-validation 替换为org.hibernate.validator:hibernate-validator:7.0.1.Final。当然还有 Kotlin 1.5.x1.6.0 你试过其他Java版本吗? @NotNull(假设这是 JSR-303 之一)实际上不是在为数据类生成的构造函数中创建 null 检查吗?这基本上会在构造时抛出错误,这根本不允许进行验证,因为它会在过程的早期失败。请在发生这种情况时在您获得的服务器上添加完整的堆栈跟踪,因为我认为它在对象构造时失败,这基本上会停止整个处理(这也解释了为什么它可以在常规 java 类上工作)。 【参考方案1】:

使该字段即使不是也可以为空,然后添加@field:NotNull

data class Test(@field:NotNull val id: String?) 

这将阻止 kotlin 验证在 ja​​vax 之前发生

【讨论】:

【参考方案2】:

该问题已在 Kotlin 版本 1.6.10 中得到修复。 https://github.com/JetBrains/kotlin/releases/tag/v1.6.10。升级后的异常现在是org.springframework.web.bind.MethodArgumentNotValidException

【讨论】:

【参考方案3】:

我认为您只是缺少控制器类顶部的 @Validated 注释。

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.FieldError
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.*
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
import java.time.LocalDateTime
import javax.validation.Valid
import javax.validation.constraints.NotEmpty


@SpringBootApplication
class BootValidationApplication

fun main(args: Array<String>) 
    runApplication<BootValidationApplication>(*args)


@RestController
@RequestMapping("/api/v1")
@Validated
class CustomerController 
    @PostMapping("/customer")
    fun createCustomer(@Valid @RequestBody customer: Customer): ResponseEntity<Customer> 
        return ResponseEntity.ok(customer)
    



@ControllerAdvice
class CustomerExceptionHandler : ResponseEntityExceptionHandler() 

    override fun handleMethodArgumentNotValid(
        ex: MethodArgumentNotValidException,
        headers: HttpHeaders,
        status: HttpStatus,
        request: WebRequest
    ): ResponseEntity<Any> 

        val fieldErrors: List<FieldError> = ex.fieldErrors
        val errorMapping = fieldErrors.associate  it.field to it.defaultMessage 

        val errorDetails = ErrorDetails(
            timestamp = LocalDateTime.now(),
            message = "Validation Failed",
            details = errorMapping
        )
        return ResponseEntity(errorDetails, HttpStatus.BAD_REQUEST)
    


data class Customer(
    @field:NotEmpty(message = "Mandatory field: Name is missing in Request Body") val name: String
)

data class ErrorDetails(val timestamp: LocalDateTime, val message: String, val details: Map<String, String?>)

输出应该是这样的

【讨论】:

问题是抛出的错误是:org.springframework.http.converter.HttpMessageNotReadableException。我希望它是 org.springframework.web.bind.MethodArgumentNotValidException 以便能够正确处理验证错误。 在这种情况下,您需要通过实现 ConstraintValidator 接口并抛出错误消息来捕获期望。 更新帖子供大家参考 它必须是override fun handleHttpMessageNotReadable。仍然会打印出可怕的错误消息。不是您可以在公共 API 中公开的东西。我希望错误信息简短而准确。我们经常会在验证注释中添加一条消息,例如:NotEmpty(message = "Please provide an ID")。当使用 Java 时将其构建到 Spring 中时,还解析字段的错误消息似乎是一个糟糕的解决方案。 我不认为这是一项艰巨的任务,您只需正确映射字段错误即可。我已经更新了 handleHttpMessageNotReadable 供您参考。

以上是关于Kotlin Spring Boot bean 验证不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在 spring-boot 中使用 kotlin 挂起函数创建名称为“requestMappingHandlerMapping”的 bean 时出错

如何在 Spring Boot 和 Spring WebFlux 中使用“功能 bean 定义 Kotlin DSL”?

Spring Boot Kotlin 项目在 Eclipse 中不起作用

由于缺少 ServletWebServerFactory bean,Spring Boot jar 无法启动 Web 服务器

Kotlin Spring bean 验证可空性

Spring Boot - 自定义 JsonDeserializer 被忽略