[英]bean validation not working with kotlin (JSR 380)
so first of all i could not think of a better title for this question so i'm open for changes.所以首先我想不出这个问题更好的标题,所以我愿意改变。
I am trying to validate a bean using the bean validation mechanism (JSR-380) with spring boot.我正在尝试使用带有 spring 引导的 bean 验证机制 (JSR-380) 来验证 bean。
So i got a controller like this:所以我得到了这样的 controller:
@Controller
@RequestMapping("/users")
class UserController {
@PostMapping
fun createUser(@Valid user: User, bindingResult: BindingResult): ModelAndView {
return ModelAndView("someview", "user", user)
}
}
with this being the User class written in kotlin:这是用 kotlin 编写的用户 class:
data class User(
@field:NotEmpty
var roles: MutableSet<@NotNull Role> = HashSet()
)
and this being the test:这是测试:
@Test
internal fun shouldNotCreateNewTestWithInvalidParams() {
mockMvc.perform(post("/users")
.param("roles", "invalid role"))
.andExpect(model().attributeHasFieldErrors("user", "roles[]"))
}
Invalid Roles are mapped to null.无效角色映射到 null。
As you can see i want roles
to contain at least one item with none of the items being null.如您所见,我希望roles
至少包含一项,其中没有一项是 null。 However when testing the above code no binding errors are reported if roles
contains null values.但是,在测试上述代码时,如果roles
包含 null 值,则不会报告绑定错误。 It does report an error if the set is empty though.如果集合为空,它确实会报告错误。 I was thinking that this might be an issue with how kotlin code compiles as the same code works just fine when the User class is written in java.我在想这可能是 kotlin 代码如何编译的问题,因为当用户 class 是用 java 编写时,相同的代码工作得很好。 Like this:像这样:
@Data // just lombok...
public class User {
@NotEmpty
private Set<@NotNull Role> roles = new HashSet<>();
}
Same Controller, same test.同样的 Controller,同样的测试。
After checking the bytecode i noticed that the kotlin version is not including the nested @NotNull
annotation (see below).检查字节码后,我注意到 kotlin 版本不包括嵌套的@NotNull
注释(见下文)。
Java: Java:
private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Ljavax/validation/constraints/NotNull;() : FIELD, 0;
@Ljavax/validation/constraints/NotEmpty;() : FIELD, null
Kotlin: Kotlin:
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
Now the question is why?现在的问题是为什么?
Here's a sample project in case you want to try some stuff.如果您想尝试一些东西,这是一个示例项目。
Make sure to compile the kotlin code with jvm target 1.8 or greater and enable this feature by providing the -Xemit-jvm-type-annotations
when compiling.确保使用 jvm target 1.8 或更高版本编译 kotlin 代码,并通过在编译时提供-Xemit-jvm-type-annotations
启用此功能。
For Spring Boot projects you only have to do the following changes (tested with Spring Boot 2.3.3 and Kotlin 1.4.0):对于 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>
to the kotlin-maven-plugin
:将<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 Release Notes Jetbrains 发行说明
Rafal G. already pointed out that we could use a custom validator as a workaround. Rafal G.已经指出我们可以使用自定义验证器作为解决方法。 So here's some code:所以这里有一些代码:
The Annotation:注释:
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>> = []
)
The 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 }
}
}
And finally the updated User class:最后是更新的 User 类:
data class User(
@field:NotEmpty
@field:NoNullElements
var roles: MutableSet<Role> = HashSet()
)
Altough validation works now, the resulting ConstrainViolation is slightly different.尽管现在验证有效,但产生的 ConstrainViolation 略有不同。 For example the elementType
and propertyPath
differs as you can see below.例如elementType
和propertyPath
不同,如下所示。
Java:爪哇:
Kotlin:科特林:
Source is available here:https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround来源可在此处获得:https ://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround
Try adding ?
尝试添加?
like this:像这样:
data class User(
@field:Valid
@field:NotEmpty
var roles: MutableSet<@NotNull Role?> = HashSet()
)
Then the kotlin compiler should realise roles could be null
, and it might honor the validation, I know little about JSR380 so i'm just guessing though.然后 kotlin 编译器应该意识到角色可能是null
,它可能会尊重验证,我对 JSR380 知之甚少,所以我只是猜测。
I had a similar problem.我有一个类似的问题。 The solution was to add this dependency:解决方案是添加此依赖项:
implementation "org.springframework.boot:spring-boot-starter-validation"
Without this dependency, the REST controller was working but no exception was thrown when the javax.validation
bean constraints were not honored.如果没有此依赖关系,则 REST controller 可以正常工作,但在不遵守javax.validation
bean 约束时不会引发异常。 Now it throws MethodArgumentNotValidException
.现在它抛出MethodArgumentNotValidException
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.