En un proyecto SpringBoot/Kotlin Coroutines, tengo una clase de controlador como esta.
@RestContollser @Validated class PostController(private val posts: PostRepository) { suspend fun search(@RequestParam q:String, @RequestParam @Min(0) offset:Int, @RequestParam @Min(1) limit:Int): ResponseEntity<Any> {} } La validación en @ResquestBody funciona como Spring WebFlux general, pero al probar
al validar los parámetros de la solicitud , falló y arroja una excepción como:
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1 at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4165) Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: No es una ConstraintViolationException .
Creo que esto es un error en el marco cuando está usando rutinas (actualización, lo es, vi el comentario de Happy Songs). En resumen:
"@Validated de hecho aún no cumple con Coroutines, debemos solucionarlo mediante el uso de métodos compatibles con Coroutines para descubrir los parámetros del método".
El problema es que Spring mejora la firma del método en su controlador para tener un parámetro adicional, como este, agregando una continuación:
public java.lang.Object com.example.react.PostController.search(java.lang.String,int,int,kotlin.coroutines.Continuation)así que cuando el validador de hibernación llama a getParameter nombres para obtener la lista de parámetros en su método, cree que hay 4 en total en la solicitud y luego obtiene una excepción de índice fuera de los límites tratando de obtener el 4to (índice 3).
Si pones un punto de interrupción en el retorno de esto:
@Override public E get(int index) { return a[index]; } y coloque una condición de punto de interrupción de index ==3 && a.length <4 puede ver lo que está sucediendo.
Lo reportaría como un error en el rastreador de problemas de Spring.
Es mejor que adopte un enfoque alternativo, como se describe aquí, utilizando un RequestBody como DTO y utilizando la anotación @Valid
Gracias por los comentarios de las canciones felices, encontré la mejor solución ahora para superar esta barrera de los problemas de Spring Github # 23499 .
Como se explica en los comentarios de este problema y la respuesta de PaulNuk, se agregará una Continuation a los argumentos del método en tiempo de ejecución, lo que fallará en el cálculo del índice de los nombres de los parámetros del método en Hibernate Validator.
La solución es cambiar el ParameterNameDiscoverer.getParameterNames(Method) y agregar una cadena vacía como nombre de parámetro adicional cuando se trata de una función de suspensión .
class KotlinCoroutinesLocalValidatorFactoryBean : LocalValidatorFactoryBean() { override fun getClockProvider(): ClockProvider = DefaultClockProvider.INSTANCE override fun postProcessConfiguration(configuration: javax.validation.Configuration<*>) { super.postProcessConfiguration(configuration) val discoverer = PrioritizedParameterNameDiscoverer() discoverer.addDiscoverer(SuspendAwareKotlinParameterNameDiscoverer()) discoverer.addDiscoverer(StandardReflectionParameterNameDiscoverer()) discoverer.addDiscoverer(LocalVariableTableParameterNameDiscoverer()) val defaultProvider = configuration.defaultParameterNameProvider configuration.parameterNameProvider(object : ParameterNameProvider { override fun getParameterNames(constructor: Constructor<*>): List<String> { val paramNames: Array<String>? = discoverer.getParameterNames(constructor) return paramNames?.toList() ?: defaultProvider.getParameterNames(constructor) } override fun getParameterNames(method: Method): List<String> { val paramNames: Array<String>? = discoverer.getParameterNames(method) return paramNames?.toList() ?: defaultProvider.getParameterNames(method) } }) } } class SuspendAwareKotlinParameterNameDiscoverer : ParameterNameDiscoverer { private val defaultProvider = KotlinReflectionParameterNameDiscoverer() override fun getParameterNames(constructor: Constructor<*>): Array<String>? = defaultProvider.getParameterNames(constructor) override fun getParameterNames(method: Method): Array<String>? { val defaultNames = defaultProvider.getParameterNames(method) ?: return null val function = method.kotlinFunction return if (function != null && function.isSuspend) { defaultNames + "" } else defaultNames } }Luego declare un nuevo bean de fábrica de validación.
@Primary @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) fun defaultValidator(): LocalValidatorFactoryBean { val factoryBean = KotlinCoroutinesLocalValidatorFactoryBean() factoryBean.messageInterpolator = MessageInterpolatorFactory().getObject() return factoryBean }Obtenga los códigos de muestra completos de mi Github.