Java 为Jackson自定义反序列化程序引发带有HTTP状态代码的自定义异常

Java 为Jackson自定义反序列化程序引发带有HTTP状态代码的自定义异常,java,spring,spring-boot,exception,jackson,Java,Spring,Spring Boot,Exception,Jackson,我有这个InstantDeserializer @Slf4j public class InstantDeserializer extends StdDeserializer<Instant> { public InstantDeserializer() { this(null); } public InstantDeserializer(Class<?> vc) { super(vc); }

我有这个InstantDeserializer

@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);
    }
}
在我的DTO中:

    @NotNull
    @JsonDeserialize(using = InstantDeserializer.class)
//    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private Instant timestamp;
即使是未注释的
@DateTimeFormat
,它也不起作用

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

也许我只是错过了一些我无法理解的小事

这里提出了这种方法:

由于JSON正文解析引发异常,因此从未调用控制器方法。

  • 由于未调用控制器方法,因此未应用@ContollerAdvice

  • 不会调用handleIOException方法,并且不会应用422状态

我怀疑这是更详细的情况

  • HTTP请求包含一个json主体

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

  • Spring尝试在调用控制方法之前反序列化传入的json主体。它必须这样做,才能将DTO对象传递到

  • 反序列化使用您的自定义反序列化程序引发IOException

  • 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);
        }
    }
    
  • 此IOException在调用控制器方法之前发生。事实上,您的控制器方法从未为此请求调用

  • Spring使用其默认行为处理异常,返回HTTP 400。Spring对HTTP 400有一个非常广泛的RFC 7231概念

  • 由于您的控制器方法从未被调用,@ControllerAdvice从未被应用,@ExceptionHandler没有看到异常。状态未设置为422

  • 我为什么相信这一点?

    我经常在Spring中看到这种行为,我认为这是预期的行为。但我还没有找到文档或阅读资料来源

    你能做些什么?

    您可能不喜欢的一种简单方法是声明您的控制器方法,以获取几乎从不失败的输入,例如字符串

    • 您接管了验证和反序列化输入的责任,并决定返回什么状态和消息

    • 你打电话给杰克逊去反序列化。将使用@ExceptionHandler方法

    • 好处:您可以返回Jackson常用的解析错误消息的文本。这些可以帮助客户找出他们的json被拒绝的原因

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

    你该怎么办?

    400对422是一个我不想提起诉讼的案子。根据你的优先级,最好接受Spring的约定

    RFC 7231处于状态400

    400(错误请求)状态代码表示服务器无法或 由于被认为是错误的内容,将不处理请求 客户端错误(例如,格式错误的请求语法、无效的请求 消息帧或欺骗请求路由)


    如果HTTP状态码警察找你麻烦,你可以指出这一点并说“我认为这是一个客户端错误。”然后辩称422是不合适的,除非你服务WebDAV只是为了让它们保持平衡。

    你不需要
    handleIOException
    方法,只需添加
    @ResponseStatus即可(HttpStatus.UNPROCESSABLE_实体)

    例外

    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
        }
    }
    

    第一个猜测:您之所以获得400,是因为您将json作为post或put的主体发送,您的web服务框架(SpringMVC?)尝试反序列化json但失败。无论原始原因如何,它都会生成400,因为预期的输入无效,这可能发生在将请求路由到代码之前。您的处理程序可能无法看到异常。我建议您在处理程序中设置断点或登录以查看是否调用过它。So、 我添加了日志反序列化程序,日志进入catch语句。您是否登录了处理程序,该处理程序是否被调用过?当Jackson反序列化失败时,您是否希望最终响应中的状态显示为422而不是400?