Проверка bean-компонентов не работает с kotlin (JSR 380)

поэтому, во-первых, я не мог придумать лучшего названия для этого вопроса, поэтому я открыт для изменений.

Я пытаюсь проверить bean-компонент, используя механизм проверки bean-компонента (JSR-380) с загрузкой Spring.

Итак, у меня есть такой контроллер:

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

это класс User, написанный на kotlin:

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[]"))
}

Недействительные роли сопоставляются с нулевым значением.

Как видите, я хочу, чтобы roles содержал хотя бы один элемент, и ни один из элементов не был нулевым. Однако при тестировании приведенного выше кода не сообщается об ошибках привязки, если roles содержит нулевые значения. Однако он сообщает об ошибке, если набор пуст. Я думал, что это может быть проблема с тем, как код kotlin компилируется, поскольку тот же код отлично работает, когда класс User написан на java. Нравится:

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

Тот же контроллер, тот же тест.

После проверки байт-кода я заметил, что версия kotlin не включает вложенную аннотацию @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

Теперь вопрос, почему?

Вот образец проекта на случай, если вы захотите что-нибудь попробовать.

перед \ @NotEmpty просто объявите \ @NotNull в классе пользователя и попробуйте

Ashvin solanki 18.09.2018 19:50

по-прежнему не работает с java.lang.AssertionError: No errors for field 'roles[]' of attribute 'userDto', и в этом случае это действительно имеет смысл, поскольку добавление @NotNull к ролям только гарантирует, что roles никогда не будет нулевым. Однако я хочу проверить элементы внутри roles.

Tommy Schmidt 18.09.2018 19:58

Да, у меня такая же проблема. Думаю, мне пока придется создавать собственные валидаторы. twitter.com/GlowinskiRafal/status/1043183121796083718

Rafal G. 21.09.2018 19:08

@TommySchmidt Я создал проблему с Kotlin, используя всю предоставленную вами информацию: youtrack.jetbrains.com/issue/KT-27049 - Надеюсь, вы не против :)

Rafal G. 22.09.2018 10:55

@RafalG. я совсем не против. спасибо за публикацию!

Tommy Schmidt 22.09.2018 13:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
23
5
9 840
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте добавить ? следующим образом:

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

Затем компилятор kotlin должен понимать, что ролями могут быть null, и он может учитывать проверку, я мало знаю о JSR380, поэтому я просто предполагаю.

Привет, спасибо за ответ! К сожалению, добавление? на роль не имеет значения. Кажется, это ошибка / отсутствующая функция в kotlin прямо сейчас, начиная с youtrack.jetbrains.com/issue/KT-13228. Я обновлю свой вопрос и укажу обходной путь, пока он не будет исправлен.

Tommy Schmidt 25.09.2018 19:32
Ответ принят как подходящий

Ответ (Котлин 1.3.70)

Обязательно скомпилируйте код kotlin с jvm target 1.8 или выше и включите эту функцию, предоставив -Xemit-jvm-type-annotations при компиляции.

Для проектов Spring Boot вам нужно сделать только следующие изменения (проверено с помощью Spring Boot 2.3.3 и Kotlin 1.4.0):

  1. В вашем pom установите следующее свойство:
    <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>
    

Образец проекта

Jetbrains Информация о выпуске


Обходной путь (до Kotlin 1.3.70)

Рафаль Г. уже указывал, что мы можем использовать собственный валидатор в качестве обходного пути. Итак, вот код:

Аннотация:

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>> = []
)

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 }
    }
}

И, наконец, обновленный класс User:

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

Хотя проверка работает сейчас, результирующий ConstrainViolation немного отличается. Например, elementType и propertyPath отличаются, как вы можете видеть ниже.

Джава:

The Java Version

Котлин:

The Kotlin Version

Источник доступен здесь: https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround

Еще раз спасибо за вашу помощь Рафаль Г.

Похоже, они добавили поддержку этой функции в kotlin 1.3.70: blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released

Tommy Schmidt 08.07.2020 18:50

Я подтвердил, что проверка работает без <arg>-Xemit-jvm-type-annotations</arg> в весенней загрузке 2.4.4 с версией java 8 и kotlin 1.4.31

mazend 30.05.2021 05:41

@mazend у вас есть для этого пример? во время моих тестов <arg>-Xemit-jvm-type-annotations</arg> по-прежнему требуется даже с kotlin 1.5.10 и Spring boot 2.5.0. См .: github.com/DarkAtra/jsr380-kotlin-issue/tree/…

Tommy Schmidt 30.05.2021 17:33

Я скачал ваш код. гм .. Почему бы вам не добавить простой лог и не проверить результат? '@PostMapping fun createUserKotlin (@Valid user: de.darkatra.jsr380kotlinissue.kotlin.User, bindingResult: BindingResult): ModelAndView {println ("bindingResult.hasErrors () = $ {bindingResult.hasErrors ()}") return someview "," пользователь ", пользователь)} '

mazend 02.06.2021 13:26

@mazend, я только что обновил код (см. ссылку выше), и это сообщение журнала, которое я получил: bindingResult.hasErrors() = false (не забудьте запустить код с mvn clean verify)

Tommy Schmidt 02.06.2021 13:43

Это странно. Я получил тот же результат с вами, когда запустил UserControllerTest. Однако, когда я отправляю пустой запрос POST из POSTMAN, в обоих случаях отображается bindingResult.hasErrors() = true. Кажется, AutoConfigureMockMvc не работает без <arg>-Xemit-jvm-type-annotations</arg>. Как ты об этом думаешь? На самом деле я не знаком с Spring Test.

mazend 03.06.2021 09:23

вы, вероятно, видите bindingResult.hasErrors() = true, потому что отправка пустого почтового запроса нарушает ограничение @field:NotEmpty на поле ролей. мой тестовый пример, с другой стороны, нарушает ограничение @NotNull внутри MutableSet, потому что в тесте not a role преобразуется в null через github.com/DarkAtra/jsr380-kotlin-issue/blob/…

Tommy Schmidt 03.06.2021 22:22

Кажется, ты прав. так что нам нужно включить <arg>-Xemit-jvm-type-annotations</arg> для проверки всех ограничений?

mazend 04.06.2021 08:37

да, без <arg>-Xemit-jvm-type-annotations</arg> компилятор kotlin отбрасывает все аннотации TYPE_PARAMETER (например, аннотации к дженерикам) - вы также можете увидеть разницу в соответствующем байт-коде (см. вопрос)

Tommy Schmidt 04.06.2021 20:39

Другие вопросы по теме