【问题标题】:Kotlin Spring Boot bean validation not workingKotlin Spring Boot bean 验证不起作用
【发布时间】: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 类上工作)。

标签: java spring spring-boot kotlin


【解决方案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 供您参考。
      猜你喜欢
      • 2020-11-22
      • 1970-01-01
      • 2020-11-15
      • 2021-08-11
      • 1970-01-01
      • 1970-01-01
      • 2019-01-14
      • 1970-01-01
      • 2020-11-05
      相关资源
      最近更新 更多