繁体   English   中英

为 Jackson 自定义解串器抛出带有 HTTP 状态码的自定义异常

[英]Throw custom Exception with HTTP status code for Jackson Custom deserializer

我有这个 InstantDesrializer

@Slf4j
public class InstantDeserializer extends StdDeserializer<Instant> {

    public InstantDeserializer() {
        this(null);
    }

    public InstantDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Instant deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        log.info(node.asText());
        TemporalAccessor parse = null;
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT).withZone(ZoneOffset.UTC);
        try {
            parse = dateTimeFormatter.parse(node.asText());
        } catch (Exception e) {
            e.printStackTrace();
            throw new IOException();
        }
        log.info(Instant.from(parse).toString());
        return Instant.from(parse);
    }
}

然后在@ControllerAdvice中对应IOException

@ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException e) {
    return ResponseEntity.status(422).build();
}

这在我的 DTO 中:

    @NotNull
    @JsonDeserialize(using = InstantDeserializer.class)
//    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private Instant timestamp;

即使取消注释@DateTimeFormat ,它也不起作用

理想情况下,它应该返回 422 状态。 但是,它返回 400。

也许我只是错过了一些我无法弄清楚的小东西。

此处建议使用此方法: Throw custom exception while deserializing the Date field using jackson in java

永远不会调用Controller方法,因为JSON正文解析引发了异常。

  • 由于未调用Controller方法,因此未应用@ContollerAdvice。

  • 您的handleIOException方法不会被调用,并且您的422状态不会被应用。

我怀疑这是更详细的情况...

  1. HTTP请求包含json正文。

  2. 与@RequestMapping和请求的其他注释匹配的控制器方法将DTO类的实例作为参数。

  3. Spring会调用控制方法之前尝试反序列化传入的json主体。 为了传递DTO对象,它必须这样做。

  4. 反序列化使用您的自定义反序列化器,它引发IOException。

  5. 此IOException发生调用控制器方法之前 实际上,永远不会为该请求调用控制器方法。

  6. Spring使用默认行为处理异常,返回HTTP400。Spring具有非常广泛的RFC 7231 HTTP 400概念。

  7. 由于永远不会调用控制器方法,因此不会应用@ControllerAdvice,并且@ExceptionHandler不会看到异常。 状态未设置为422。

为什么我相信这一点?

我经常从Spring看到这种行为,并且我认为这是预期的行为。 但是我没有找到文档或阅读源代码来确定。

你能为这个做什么?

您可能不喜欢的一种简单方法是,声明您的控制器方法以接受几乎永不失败的输入,例如String。

  • 您负责验证和反序列化输入,并确定要返回的状态和消息。

  • 您致电Jackson进行反序列化。 使用您的@ExceptionHandler方法。

  • 奖励:您可以返回杰克逊经常有用的解析错误消息的文本。 这些可以帮助客户弄清楚为什么他们的json被拒绝。

如果Spring提供一种更时尚的方法,一个要子类化的类,一个特殊的注释,我不会感到惊讶。 我没有追求。

你应该怎么做?

我宁愿不提起400 vs. 422诉讼。 根据您的优先级,最好接受Spring的约定。

RFC 7231处于状态400

400(错误请求)状态代码表示服务器由于某些原因(例如格式错误的请求语法,无效的请求消息框架或欺骗性的请求路由)而被视为客户端错误,因此服务器无法处理该请求。

如果HTTP状态代码警察选择了您,则可以指向它并说“ 我认为此输入是客户端错误。 ”然后辩称422是不合适的,除非您正在提供WebDAV只是为了使它们保持平衡。

您不需要handleIOException方法,只需添加@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
到您的CustomException。

import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public class MyException extends JsonProcessingException {
    public MyException(String message) {
        super(message);
    }
}

因此,当您对正文提出无效要求时

{"timestamp":"2018-04-2311:32:22","id":"132"}

响应将是:

{
    "timestamp": 1552990867074,
    "status": 422,
    "error": "Unprocessable Entity",
    "message": "JSON parse error: Instant field deserialization failed; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Instant field deserialization failed (through reference chain: TestDto[\"timestamp\"])"
}

邮递员回应

使用有效的请求可以正常工作:

{"timestamp":"2018-04-23T11:32:22.213Z","id":"132"}

响应:

{
    "id": "132",
    "timestamp": {
        "nano": 213000000,
        "epochSecond": 1514700142
    }
}

Jackson在反序列化错误的情况下抛出的异常是HttpMessageNotReadableException。

在您的自定义反序列化器中,您可以抛出扩展 JsonProcessingException 的自己的反序列化异常。

在您的 ControllerAdvice 中,您可以处理 HttpMessageNotReadableException 并获取其原因,这是您的自定义异常。 这样,你就可以抛出你想要的http代码了。

    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseBody
    public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) {
        Throwable cause = ex.getCause();
        if (cause.getCause() instanceof YourOwnException) {
            //Return your response entity with your custom HTTP code
        } 

        //Default exception handling
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM