Kotlin 的 ControllerAdvice

Posted

技术标签:

【中文标题】Kotlin 的 ControllerAdvice【英文标题】:ControllerAdvice for Kotlin 【发布时间】:2020-11-15 04:25:40 【问题描述】:

我想为验证异常创建ControllerAdvice,我正在使用 Webflux、Kotlin 和 jackson-module-kotlin。

我尝试过使用以下传统代码:

  @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidationExceptions(
            ex: MethodArgumentNotValidException): Map<String, String?>? 
        val errors: MutableMap<String, String?> = HashMap()
        ex.bindingResult.allErrors.forEach(Consumer  error: ObjectError ->
            val fieldName = (error as FieldError).field
            val errorMessage = error.getDefaultMessage()
            errors[fieldName] = errorMessage
        )
        return errors
    

但它不能正常工作,默认响应是:


    "timestamp": "2020-07-25T10:19:00.023+00:00",
    "path": "/boarding/subscribeUserWithSoloWorkspace",
    "status": 400,
    "error": "Bad Request",
    "message": "Failed to read HTTP message",
    "requestId": "62f1e90a-1",
    "trace": "org.springframework.core.codec.DecodingException: JSON decoding error: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 4, column: 5] (through reference chain: co.ashiyane.flare.domains.supdomains.UserAndWorkspace[\"user\"]->co.ashiyane.flare.domains.User[\"mobileNumber\"])\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:215)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:173)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeToMono$1(AbstractJackson2Decoder.java:159)\n\tat reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118)\n\tat reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:96)\n\tat reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287)\n\tat reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:330)\n\tat reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782)\n\tat reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:152)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)\n\tat reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:427)\n\tat reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:210)\n\tat reactor.netty.channel.FluxReceive.request(FluxReceive.java:121)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:155)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:130)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:155)\n\tat reactor.core.publisher.MonoCollect$CollectSubscriber.onSubscribe(MonoCollect.java:112)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:86)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:163)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:86)\n\tat reactor.netty.channel.FluxReceive.startReceiver(FluxReceive.java:300)\n\tat reactor.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:138)\n\tat io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)\n\tat io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)\n\tat io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:384)\n\tat io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)\n\tat io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)\n\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\tat java.base/java.lang.Thread.run(Thread.java:832)\nCaused by: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 4, column: 5] (through reference chain: co.ashiyane.flare.domains.supdomains.UserAndWorkspace[\"user\"]->co.ashiyane.flare.domains.User[\"mobileNumber\"])\n\tat com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:112)\n\tat com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:202)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:490)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)\n\tat com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)\n\tat com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2057)\n\tat com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1431)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:168)\n\t... 29 more\n"

消息中还有DecodingExceptionMissingKotlinParameterException等其他异常,但我也处理不了!

【问题讨论】:

【参考方案1】:

我已经通过关注ControllerAdvice 解决了这个问题:

    @ExceptionHandler(value = [ServerWebInputException::class])
    @ResponseBody
    fun onException(exception: ServerWebInputException): Mono<ResponseEntity<ClientAcknowledgement>> 
        val parameterName = (exception.rootCause as MissingKotlinParameterException).parameter.name // id
        val parameterType = (exception.rootCause as MissingKotlinParameterException).parameter.type // ObjectId
        val fieldName = (exception.rootCause as MissingKotlinParameterException).path[0].fieldName // in User part
        return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ClientAcknowledgement("there is a missing parameter in your request, check your request body." +
                        " detail : missing $parameterName ($parameterType) type in $fieldName")))
    

【讨论】:

以上是关于Kotlin 的 ControllerAdvice的主要内容,如果未能解决你的问题,请参考以下文章

@ControllerAdvice 用法

@ControllerAdvice三种使用场景

@ControllerAdvice(处理全局异常)

@ControllerAdvice(处理全局异常)

@ControllerAdvice(处理全局异常)

@ControllerAdvice 拦截异常并统一处理