Kotlin Spring bean 验证可空性

Posted

技术标签:

【中文标题】Kotlin Spring bean 验证可空性【英文标题】:Kotlin Spring bean validation nullability 【发布时间】:2019-02-25 22:06:13 【问题描述】:

在我用 Kotlin 构建的 Spring 应用程序中,我想在一个看起来像这样的数据类上使用 bean 验证。

data class CustomerDto(
    @field: NotBlank
    val firstName: String,

    @field: NotBlank
    val lastName: String)

在向客户端点发送一个空名字的帖子时,我想获得约束验证,但由于字段不允许空值,我没有得到验证,而是得到以下错误。

"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Instantiation of [simple type, class pkg.CustomerDto] value failed for JSON property firstName due to missing (therefore NULL) value for creator parameter firstName which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class pkg.CustomerDto] value failed for JSON property firstName due to missing (therefore NULL) value for creator parameter firstName which is a non-nullable type\n at [Source: (PushbackInputStream); line: 19, column: 1] (through reference chain: pkg.CustomerDto[\"firstName\"])",
"path": "/shop/5/customer"

是否有任何其他选项可以将 dto 字段标记为非可选并且仍然违反约束?当我将它们标记为可选时,我必须使用 !!将代码中的不可空字段映射到我的实体时。

谢谢。

【问题讨论】:

好问题,这里的沉默可能支持了我的印象。 Imo 没有办法,因为成功构建对象是字段验证的先决条件。不过,我很想阅读 Kotlin 冠军的观点。我已经想到了一个与 Spring 相关的解决方案,但我猜你想用 Kotlin 方式解决它? 不,我没有一个好的解决方案,除了让所有东西都是可选的或使用 !!当映射到我的实体以依赖我的模型中的空安全性时。我会对您想到的 Spring 解决方案感兴趣。 在做了一点搜索之后,我想,这可能比我最初想象的要难一些,但是,可能的。问题是,如果这些方法证明你需要付出的努力是合理的。首先是使用 Spring AOP,第二个 HandlerInterceptors 让您在 Jackson 将请求负载反序列化到您的 CustomerDTO 之前访问和修改请求。这绝对不是一个简单的解决方案,看看这个问题:***.com/questions/50932518/… 【参考方案1】:

我相信你走错路了。

Kotlin 的空安全运算符的确切目的是强制您在代码中明确表达可空性行为,以大幅减少 NPE,或者至少确保您自己故意造成它们 :)。在您(或任何类似 MVC 的访问模式)的情况下,您将面临以下情况

作为 DTO 有效负载的一部分,字段可能为空 如果字段为空,框架验证应取消 DTO 的资格。 如果框架验证成功,则隐式假定 DTO 字段不为空

虽然从逻辑流的角度来看是有道理的,但它实际上是可能导致 NPE 的违规行为,因为模型/合同中没有任何内容可以保证这些字段不会为空

不过,在 java 中,您只是使用 getter 做出了最后的假设(无论如何,您一直在使用 getter,它是 java,对吧?)。

好吧 - 如果你需要的话,在 kotlin 中也没有什么不同:

data class CustomerDto(@field:NotNull 
                       @JsonProperty("firstName") private val _firstName: String? = null,
                       @field:NotNull
                       @JsonProperty("lastName") private val _lastName: String? = null) 
        val firstName get() = _firstName!!
        val lastName get() = _lastName!!
    

(此示例假设您使用 jackson 进行 JSON 反序列化)

虽然仍然使用 !! 运算符手动强制不可为空(这是您想要避免的),但您现在正在从代码库的其余部分中抽象出该方面,从而获得类似 java-getter 的行为

【讨论】:

您描述了问题,但我想知道是否有办法在 Kotlin 非空字段上添加 @NotNull 注释。我仍然认为,如果 Spring 先执行验证,然后像 @Matt 描述的那样映射到目标类,但有框架支持,就可以实现。 我确实理解你的问题,但我也相信我建议的数据类布局以“kotlin”的方式解决了这个问题,因为它让你可以同时享受 kotlin 的 null 安全性和 spring 验证(你可以仍然注释 DTO 字段),同时将前者从应用程序的其余部分中抽象出来,并将其作为内部数据类设计考虑。我看不出这还不够。 field:JsonProperty 必须用于识别该注释是针对该字段的。 getter 必须有 get:JsonIgnore 注解,这样jackson 就不会使用 getter 进行识别。如果在 hidden 值为 null 时识别并调用 getter,则会由于 NPE 导致 JsonProcessingException。接下来,即使这样,如果出现验证错误,在响应中,spring 将显示字段名称,而不是 json 属性。在这个问题中有描述:***.com/questions/41717866/…【参考方案2】:

我认为更好的解决方案是在属性中使用 Kotlin 的默认值:

data class CustomerDto(
@field: NotBlank
val firstName: String="",

@field: NotBlank
val lastName: String="")

名字和姓氏属性总是有一个值(来自 json 或默认值 =“”)。该解决方案并不完美,但它的工作方式与预期的一样。

【讨论】:

请注意,只有在 json 中缺少属性时,默认值才有帮助,例如 "firstName": "Name" 。但是如果 json 明确地将属性定义为 null,它仍然会抛出异常,就像 "firstName": "Name", "lastName": null 【参考方案3】:

我在这里聚会迟到了,但我希望它可以帮助别人。

由于 Spring 在实际尝试绑定字段之前首先尝试创建带有 @ModelAttribute 注释的 bean 实例(请参阅 ModelAttributeMethodProcessor),因此我们没有太多选择。虽然我们可以为字段使用默认值,但这不是复杂对象的选项(例如MultipartFile)。

查看源代码,我发现 Spring 可以使用 DataBinder 设置这些值。因此,我们可以声明稍后要初始化的字段,而不需要为空。

class MyClassDTO 

    @field:NotBlank(message = "Name cannot be blank")
    lateinit var name: String

    @field:NotBlank(message = "Topic cannot be blank")
    lateinit var topic: String

    @field:NotNull(message = "File cannot be null")
    lateinit var file: MultipartFile

【讨论】:

以上是关于Kotlin Spring bean 验证可空性的主要内容,如果未能解决你的问题,请参考以下文章

KotlinKotlin 与 Java 互操作 ① ( 变量可空性 | Kotlin 类型映射 | Kotlin 访问私有属性 | Java 调用 Kotlin 函数 )

Kotlin系列之类型可空性

Kotlin中与Java互操作与可空性类型映射属性访问@JvmOverloads@JvmField@JvmStatic@Throws和函数类型操作详解

Kotlin空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )

[译] Dart中可空性语法的定案: a?[b] 或 a?.[b]

可空引用类型 - 通过接受的参数返回类型可空性