поэтому, во-первых, я не мог придумать лучшего названия для этого вопроса, поэтому я открыт для изменений.
Я пытаюсь проверить 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
Теперь вопрос, почему?
Вот образец проекта на случай, если вы захотите что-нибудь попробовать.
по-прежнему не работает с java.lang.AssertionError: No errors for field 'roles[]' of attribute 'userDto', и в этом случае это действительно имеет смысл, поскольку добавление @NotNull к ролям только гарантирует, что roles никогда не будет нулевым. Однако я хочу проверить элементы внутри roles.
Да, у меня такая же проблема. Думаю, мне пока придется создавать собственные валидаторы. twitter.com/GlowinskiRafal/status/1043183121796083718
@TommySchmidt Я создал проблему с Kotlin, используя всю предоставленную вами информацию: youtrack.jetbrains.com/issue/KT-27049 - Надеюсь, вы не против :)
@RafalG. я совсем не против. спасибо за публикацию!




Попробуйте добавить ? следующим образом:
data class User(
@field:Valid
@field:NotEmpty
var roles: MutableSet<@NotNull Role?> = HashSet()
)
Затем компилятор kotlin должен понимать, что ролями могут быть null, и он может учитывать проверку, я мало знаю о JSR380, поэтому я просто предполагаю.
Привет, спасибо за ответ! К сожалению, добавление? на роль не имеет значения. Кажется, это ошибка / отсутствующая функция в kotlin прямо сейчас, начиная с youtrack.jetbrains.com/issue/KT-13228. Я обновлю свой вопрос и укажу обходной путь, пока он не будет исправлен.
Обязательно скомпилируйте код kotlin с jvm target 1.8 или выше и включите эту функцию, предоставив -Xemit-jvm-type-annotations при компиляции.
Для проектов Spring Boot вам нужно сделать только следующие изменения (проверено с помощью Spring Boot 2.3.3 и Kotlin 1.4.0):
<properties>
<java.version>11</java.version>
<kotlin.version>1.4.0</kotlin.version>
</properties>
<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 Информация о выпуске
Рафаль Г. уже указывал, что мы можем использовать собственный валидатор в качестве обходного пути. Итак, вот код:
Аннотация:
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 отличаются, как вы можете видеть ниже.
Джава:
Котлин:
Источник доступен здесь: https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround
Еще раз спасибо за вашу помощь Рафаль Г.
Похоже, они добавили поддержку этой функции в kotlin 1.3.70: blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released
Я подтвердил, что проверка работает без <arg>-Xemit-jvm-type-annotations</arg> в весенней загрузке 2.4.4 с версией java 8 и kotlin 1.4.31
@mazend у вас есть для этого пример? во время моих тестов <arg>-Xemit-jvm-type-annotations</arg> по-прежнему требуется даже с kotlin 1.5.10 и Spring boot 2.5.0. См .: github.com/DarkAtra/jsr380-kotlin-issue/tree/…
Я скачал ваш код. гм .. Почему бы вам не добавить простой лог и не проверить результат? '@PostMapping fun createUserKotlin (@Valid user: de.darkatra.jsr380kotlinissue.kotlin.User, bindingResult: BindingResult): ModelAndView {println ("bindingResult.hasErrors () = $ {bindingResult.hasErrors ()}") return someview "," пользователь ", пользователь)} '
@mazend, я только что обновил код (см. ссылку выше), и это сообщение журнала, которое я получил: bindingResult.hasErrors() = false (не забудьте запустить код с mvn clean verify)
Это странно. Я получил тот же результат с вами, когда запустил UserControllerTest. Однако, когда я отправляю пустой запрос POST из POSTMAN, в обоих случаях отображается bindingResult.hasErrors() = true. Кажется, AutoConfigureMockMvc не работает без <arg>-Xemit-jvm-type-annotations</arg>. Как ты об этом думаешь? На самом деле я не знаком с Spring Test.
вы, вероятно, видите bindingResult.hasErrors() = true, потому что отправка пустого почтового запроса нарушает ограничение @field:NotEmpty на поле ролей. мой тестовый пример, с другой стороны, нарушает ограничение @NotNull внутри MutableSet, потому что в тесте not a role преобразуется в null через github.com/DarkAtra/jsr380-kotlin-issue/blob/…
Кажется, ты прав. так что нам нужно включить <arg>-Xemit-jvm-type-annotations</arg> для проверки всех ограничений?
да, без <arg>-Xemit-jvm-type-annotations</arg> компилятор kotlin отбрасывает все аннотации TYPE_PARAMETER (например, аннотации к дженерикам) - вы также можете увидеть разницу в соответствующем байт-коде (см. вопрос)
перед \ @NotEmpty просто объявите \ @NotNull в классе пользователя и попробуйте