【问题标题】:Is NotNull needed on Kotlin?Kotlin 需要 NotNull 吗?
【发布时间】:2017-02-02 03:29:17
【问题描述】:

我有一堂课:

class User(

    var name: String

)

还有一个映射的发布请求:

@PostMapping("/user")
fun test(@Valid @RequestBody user: User) {
    //...
}

如果客户端发送带有name: null 的用户的JSON 怎么办?它会被 MVC Validator 拒绝还是会抛出异常?我应该用@NotNull 注释name 吗?不幸的是,我无法检查,因为只能编写测试(无法创建User(null))。

【问题讨论】:

  • 您是在询问一般的 kotlin,还是特别询问如何在 Spring MVC 中处理这种情况(即“因为 @NotNull 在使用 Kotlin 时是多余的,我如何验证其中非空的请求请求 JSON 中未提供属性?")

标签: spring-mvc kotlin bean-validation


【解决方案1】:

您可以避免使用@NotNull,因为它永远不会因不可为空的属性而被触发(bean 验证仅在 Jackson 反序列化 JSON 后才开始 - 这将失败)。

假设您使用的是jackson-module-kotlin,那么您应该得到一个以MissingKotlinParameterException 为根本原因的异常,然后您可以在@ControllerAdvice 中处理它。在您的建议中,您可以处理正常的 bean 验证异常(例如 ConstraintViolationExceptioncaused by @Size)和缺少非空构造函数参数(MissingKotlinParameterException)并返回一个 API 响应,指示错误的字段。

唯一需要注意的是 jackson-kotlin-module fails on the first missing property 与 Java 中的 bean 验证不同,后者将返回所有违规行为。

【讨论】:

    【解决方案2】:

    由于nameUser 的非空参数,它永远不能接受nulls。

    为了保证 Kotlin 编译器:

    • User 构造函数中插入 null 检查,如果某些 Java 代码尝试传递 null,则会引发异常。

    • @NotNull 注释name 以便与Java API 保持一致

    • 没有添加任何 @NotNull 注释,因为没有一个所有人都同意:(

    这里是对应的docs

    更新

    我已经重新检查过了,Kotlin 编译器 v1.0.6 确实org.jetbrains.annotations 插入了@Nullable@NotNull。我已经相应地更新了答案。

    【讨论】:

      【解决方案3】:

      正如我测试的那样,@NotNull 根本不会影响 MVC 验证。您只会在控制台中收到WARN 消息,这是正常的:

      无法读取 HTTP 消息:org.springframework.http.converter.HttpMessageNotReadableException:无法读取文档:实例化...

      【讨论】:

        【解决方案4】:

        我现在的解决方法(将文件放在任何地方):

        @Order(Ordered.HIGHEST_PRECEDENCE)
        @ControllerAdvice
        class ExceptionHandler {
        
            @ResponseStatus(BAD_REQUEST)
            @ResponseBody
            @ExceptionHandler(MissingKotlinParameterException::class)
            fun missingKotlinParameterException(ex: MissingKotlinParameterException): Error? {
                return createMissingKotlinParameterViolation(ex)
            }
        
            private fun createMissingKotlinParameterViolation(ex: MissingKotlinParameterException): Error{
                val error = Error(BAD_REQUEST.value(), "validation error")
                val errorFieldRegex = Regex("\\.([^.]*)\\[\\\"(.*)\"\\]\$")
                val errorMatch = errorFieldRegex.find(ex.path[0].description)!!
                val (objectName, field) = errorMatch.destructured
                error.addFieldError(objectName.decapitalize(), field, "must not be null")
                return error
            }
        
            data class Error(val status: Int, val message: String, val fieldErrors: MutableList<CustomFieldError> = mutableListOf()) {
                fun addFieldError(objectName: String, field: String, message: String) {
                    val error = CustomFieldError(objectName, field, message)
                    fieldErrors.add(error)
                }
            }
        
            data class CustomFieldError(val objectName: String, val field: String, val message: String)
        

        【讨论】:

          【解决方案5】:

          我使用以下异常处理程序:

          @Order(Ordered.HIGHEST_PRECEDENCE)
          @ControllerAdvice
          class ExceptionHandlerResolver {
          
            @ResponseStatus(HttpStatus.BAD_REQUEST)
            @ResponseBody
            @ExceptionHandler(MissingKotlinParameterException::class)
            fun missingKotlinParameterException(ex: MissingKotlinParameterException): MyCustomError? {
              return MyCustomError(
                timestamp = LocalDateTime.now(),
                status = HttpStatus.BAD_REQUEST,
                exception = ex,
                validationMessages = listOf(
                  ValidationMessage(
                    field = ex.path.fold("") { result, segment ->
                      if (segment.fieldName != null && result.isEmpty()) segment.fieldName
                      else if (segment.fieldName != null) "$result.${segment.fieldName}"
                      else "$result[${segment.index}]"
                    },
                    message = "value is required"
                  )
                )
              )
            }
          }
          

          它支持任何Json路径的异常,例如:arrayField[1].arrayItemField.childField

          MyCustomError 和 ValidationMessage 类:

          data class MyCustomError(
            val timestamp: LocalDateTime,
            @JsonIgnore val status: HttpStatus,
            @JsonIgnore val exception: Exception? = null,
            val validationMessages: List<ValidationMessage>? = null
          ) {
            @JsonProperty("status") val statusCode = status.value()
            @JsonProperty("error") val statusReasonPhrase = status.reasonPhrase
            @JsonProperty("exception") val exceptionClass = exception?.javaClass?.name
            @JsonProperty("message") val exceptionMessage = exception?.message
          }
          
          data class ValidationMessage(val field: String, val message: String)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-12-02
            • 2021-06-08
            • 2021-06-21
            • 1970-01-01
            • 2022-12-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多