web-dev-qa-db-ja.com

Bean検証がkotlinで機能しない(JSR 380)

まず第一に、私はこの質問のためのより良いタイトルを考えることができなかったので、私は変更を受け入れるつもりです。

スプリングブートでBean検証メカニズム(JSR-380)を使用してBeanを検証しようとしています。

だから私はこのようなコントローラーを得ました:

@Controller
@RequestMapping("/users")
class UserController {
    @PostMapping
    fun createUser(@Valid user: User, bindingResult: BindingResult): ModelAndView {
        return ModelAndView("someview", "user", user)
    }
}

これはkotlinで書かれたUserクラスです:

data class User(
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role> = HashSet()
)

そしてこれはテストです:

@Test
internal fun shouldNotCreateNewTestWithInvalidParams() {
    mockMvc.perform(post("/users")
        .param("roles", "invalid role"))
        .andExpect(model().attributeHasFieldErrors("user",  "roles[]"))
}

無効なロールはnullにマップされます。

ご覧のとおり、rolesには少なくとも1つのアイテムを含め、どのアイテムもnullにしないようにします。ただし、上記のコードをテストすると、rolesにnull値が含まれている場合、バインディングエラーは報告されません。ただし、セットが空の場合はエラーを報告します。これは、UserクラスがJavaで記述されている場合、同じコードが正常に機能するため、kotlinコードのコンパイル方法に問題があるのではないかと考えていました。このような:

@Data // just lombok...
public class User {
    @NotEmpty
    private Set<@NotNull Role> roles = new HashSet<>();
}

同じコントローラー、同じテスト。

バイトコードを確認したところ、kotlinバージョンにネストされた@NotNullアノテーションが含まれていないことがわかりました(以下を参照)。

Java:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Ljavax/validation/constraints/NotNull;() : FIELD, 0;
@Ljavax/validation/constraints/NotEmpty;() : FIELD, null

コトリン:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Lorg/jetbrains/annotations/NotNull;() // added because roles is not nullable in kotlin. this does not affect validation

今問題はなぜですか?

これは、何かを試したい場合のために サンプルプロジェクト です

19
Tommy Schmidt

回答

現在、kotlinの問題のようです。詳細は KT-27049 を参照してください。


Workaround

Rafal G。 カスタムバリデーターを回避策として使用できることをすでに指摘しました。だからここにいくつかのコードがあります:

注釈:

import javax.validation.Constraint
import javax.validation.Payload
import kotlin.annotation.AnnotationTarget.*
import kotlin.reflect.KClass

@MustBeDocumented
@Constraint(validatedBy = [NoNullElementsValidator::class])
@Target(allowedTargets = [FUNCTION, FIELD, ANNOTATION_CLASS, CONSTRUCTOR, VALUE_PARAMETER, TYPE_PARAMETER])
@Retention(AnnotationRetention.RUNTIME)
annotation class NoNullElements(
    val message: String = "must not contain null elements",
    val groups: Array<KClass<out Any>> = [],
    val payload: Array<KClass<out Payload>> = []
)

ConstraintValidator:

import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext

class NoNullElementsValidator : ConstraintValidator<NoNullElements, Collection<Any>> {
    override fun isValid(value: Collection<Any>?, context: ConstraintValidatorContext): Boolean {
        // null values are valid
        if (value == null) {
            return true
        }
        return value.stream().noneMatch { it == null }
    }
}

そして最後に更新されたユーザークラス:

data class User(
    @field:NotEmpty
    @field:NoNullElements
    var roles: MutableSet<Role> = HashSet()
)

検証は現在機能していますが、結果のConstrainViolationは少し異なります。たとえば、以下のようにelementTypepropertyPathは異なります。

Java:

The Java Version

Kotlin:

The Kotlin Version

ソースはここにあります: https://gitlab.com/darkatra/jsr380-kotlin-issue/tree/workaround

ご協力ありがとうございます Rafal G。

4
Tommy Schmidt

次のように?を追加してみてください:

data class User(
    @field:Valid
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role?> = HashSet()
)

次に、kotlinコンパイラはロールがnullである可能性があることを認識し、検証を尊重する可能性があります。JSR380についてはほとんど知らないので、私は推測しています。

2
StevieB