programing

kotlin에서는 콩 검증이 동작하지 않습니다(JSR 380).

nicescript 2023. 2. 11. 17:07
반응형

kotlin에서는 콩 검증이 동작하지 않습니다(JSR 380).

그래서 우선 이 질문에 대한 더 좋은 제목이 생각나지 않아서 변화를 받아들일 수 있습니다.

spring boot에 bean validation mechanism(JSR-380)을 사용하여 bean을 검증하려고 합니다.

이런 컨트롤러가 있어요

@Controller
@RequestMapping("/users")
class UserController {
    @PostMapping
    fun createUser(@Valid user: User, bindingResult: BindingResult): ModelAndView {
        return ModelAndView("someview", "user", 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항목이 null이 아닌 항목을 하나 이상 포함해야 합니다.그러나 위의 코드를 테스트할 때 다음과 같은 경우 바인딩 오류가 보고되지 않습니다.roles에는 늘 값이 포함되어 있습니다.다만, 세트가 비어 있는 경우는 에러가 보고됩니다.User 클래스가 Java로 작성되어 있을 때, 같은 코드로 Kotlin 코드가 컴파일 되는 것이 문제인 것 같습니다.다음과 같이 합니다.

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

같은 컨트롤러, 같은 테스트.

바이트 코드를 확인한 결과 kotlin 버전에 nested가 포함되어 있지 않습니다.@NotNull주석(아래 참조).

자바:

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

이제 질문은 왜일까?

여기 당신이 무언가를 시도하고 싶을 때를 대비한 샘플 프로젝트가 있습니다.

답변(Kotlin 1.3.70)

kotlin 코드를 jvm target 1.8 이상으로 컴파일하고,-Xemit-jvm-type-annotations를 참조해 주세요.

Spring Boot 프로젝트의 경우 다음 변경만 수행하면 됩니다(Spring Boot 2.3.3 및 Kotlin 1.4.0에서 테스트 완료).

  1. 폼 세트에는 다음 속성이 있습니다.
    <properties>
        <java.version>11</java.version>
        <kotlin.version>1.4.0</kotlin.version>
    </properties>
    
  2. 더하다<arg>-Xemit-jvm-type-annotations</arg>에게kotlin-maven-plugin:
    <build>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <groupId>org.jetbrains.kotlin</groupId>
            <configuration>
                <args>
                    <arg>-Xjsr305=strict</arg>
                    <arg>-Xemit-jvm-type-annotations</arg>
                </args>
                <compilerPlugins>
                    <plugin>spring</plugin>
                </compilerPlugins>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-allopen</artifactId>
                    <version>${kotlin.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </build>
    

샘플 프로젝트

제트브레인 릴리즈 노트


회피책 (Kotlin 1.3.70 이전)

Rafal G.는 이미 이 문제를 해결하기 위해 커스텀 검증기를 사용할 수 있다고 지적했습니다.여기 몇 가지 코드가 있습니다.

주석:

import javax.validation.Constraint
import javax.validation.Payload
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CONSTRUCTOR
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
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>> = []
)

Constraint Validator:

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

현재 검증이 실행되지만 결과적으로 발생하는 RestraintViolation은 약간 다릅니다.예를 들어,elementType그리고.propertyPath다음과 같이 다릅니다.

자바:

Java 버전

코틀린:

Kotlin 버전

출처는, https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround 에서 입수할 수 있습니다.

당신의 도움에 다시 한번 감사드립니다.

추가해 보다?다음과 같습니다.

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

그러면 Kotlin 컴파일러는 역할이 다음과 같을 수 있다는 것을 깨달아야 합니다.null인증을 받을 수 있을지도 모릅니다만, 저는 JSR380에 대해 잘 모르기 때문에 추측하고 있습니다.

저도 비슷한 문제가 있었어요.해결책은 다음과 같은 종속성을 추가하는 것이었습니다.

 implementation "org.springframework.boot:spring-boot-starter-validation"

의존관계가 REST , 「REST」의 「REST」의 「REST」의 「REST」의 「REST」의 「REST」의 「REST」의 「REST」의 「REST」의 예외는 발생하지 않았습니다.javax.validation콩의 제약이 충족되지 않았습니다. 던지다MethodArgumentNotValidException.

언급URL : https://stackoverflow.com/questions/52345291/bean-validation-not-working-with-kotlin-jsr-380

반응형