Kotlin 需要 NotNull 吗?

Is NotNull needed on Kotlin?

我有一个class:

class User(

    var name: String

)

和映射的 post 请求:

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

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

因为 nameUser 的 not-null 参数,它永远不能接受 nulls。

保证Kotlin编译器:

  • User 构造函数中插入一个 null 检查,如果某些 Java 代码试图传递 null.

  • @NotNull注释name以便与Java API

  • 保持一致
  • 不添加任何 @NotNull 注释,因为没有一个是每个人都同意的:(

下面是对应的docs

更新

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

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

假设您使用 jackson-module-kotlin, then you should get an exception with MissingKotlinParameterException 作为根本原因,然后您可以在 @ControllerAdvice 中处理它。在您的建议中,您可以处理正常的 bean 验证异常(例如 ConstraintViolationException@Size 引起)和缺少 non-null 构造函数参数(MissingKotlinParameterException)和 return 一个 API 指示错误字段的响应。

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

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

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Instantiation of...

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

@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)

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

@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)