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.13
、2.5.7
和 2.6.1
。还尝试将org.springframework.boot:spring-boot-starter-validation
替换为org.hibernate.validator:hibernate-validator:7.0.1.Final
。当然还有 Kotlin 1.5.x
和 1.6.0
。
你试过其他Java版本吗?
@NotNull
(假设这是 JSR-303 之一)实际上不是在为数据类生成的构造函数中创建 null
检查吗?这基本上会在构造时抛出错误,这根本不允许进行验证,因为它会在过程的早期失败。请在发生这种情况时在您获得的服务器上添加完整的堆栈跟踪,因为我认为它在对象构造时失败,这基本上会停止整个处理(这也解释了为什么它可以在常规 java 类上工作)。
【参考方案1】:
使该字段即使不是也可以为空,然后添加@field:NotNull
data class Test(@field:NotNull val id: String?)
这将阻止 kotlin 验证在 javax 之前发生
【讨论】:
【参考方案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 服务器