为杰克逊自定义反序列化器引发带有HTTP状态代码的自定义异常

时间:2019-03-18 02:41:43

标签: java spring spring-boot exception jackson

我有这个InstantDesrializer

<bean id="customerRequestJsonProvider"class="org.apache.cxf.jaxrs.provider.json.JSONProvider">
    <property name="writeXsiType" value="false" />
    <property name="serializeAsArray" value="true" />
    <property name="writeNullAsString" value="true" />

</bean>

然后在@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); } }

中对应的IOException
@ControllerAdvice

这在我的DTO中:

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

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

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

也许我只是缺少了一些小东西,我无法弄清楚。

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

2 个答案:

答案 0 :(得分:0)

从不调用您的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只是为了让他们保持平衡。

答案 1 :(得分:0)

您不需要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\"])"
}

Postman Response

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

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

响应:

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