为 Jackson 自定义反序列化程序抛出带有 HTTP 状态代码的自定义异常
Posted
技术标签:
【中文标题】为 Jackson 自定义反序列化程序抛出带有 HTTP 状态代码的自定义异常【英文标题】:Throw custom Exception with HTTP status code for Jackson Custom deserializer 【发布时间】:2019-08-08 09:23:21 【问题描述】:我有这个 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
【问题讨论】:
第一个猜测:您得到 400 是因为您将 json 作为 post 或 put 的主体发送,您的 Web 服务框架(spring mvc?)尝试反序列化 json 并失败。无论最初的原因如何,它都会生成 400,因为预期的输入是无效的,这可能在将请求路由到您的代码之前发生。您的处理程序可能看不到异常。我建议放置一个断点或登录你的处理程序,看看它是否被调用过。 于是,我在Deserializer中添加了日志,日志进入catch语句。 你登录处理程序了吗,处理程序是否被调用过? Jackson 反序列化失败时,您希望在最终响应中显示 422 而不是 400 的状态吗? 【参考方案1】:您不需要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
【讨论】:
【参考方案2】:永远不会调用您的 Controller 方法,因为 JSON 正文解析引发了异常。
您的 @ContollerAdvice 未应用,因为未调用 Controller 方法。
没有调用你的handleIOException方法,也没有应用你的422状态。
我怀疑这是更详细的情况......
HTTP 请求包含 json 正文。
与请求的@RequestMapping 和其他注释匹配的控制器方法将您的DTO 类的实例作为参数。
Spring 尝试在调用您的控制方法之前 反序列化传入的 json 主体。它必须这样做才能传递 DTO 对象。
反序列化使用您的自定义反序列化程序,该反序列化程序会引发 IOException。
此 IOException 发生在您的控制器方法被调用之前。事实上,这个请求永远不会调用你的控制器方法。
Spring 使用其默认行为处理异常,返回 HTTP 400。Spring 具有非常广泛的 RFC 7231 概念,即 HTTP 400。
由于从未调用过您的控制器方法,因此永远不会应用 @ControllerAdvice 并且您的 @ExceptionHandler 不会看到异常。状态未设置为 422。
为什么我会相信这一点?
我经常从 Spring 中看到这种行为,我认为这是预期的行为。但我还没有找到文档或阅读源代码来确定。
你能做些什么?
您可能不喜欢的一种简单方法是声明您的控制器方法以获取几乎永远不会失败的输入,例如字符串。
您负责验证和反序列化输入,并决定返回什么状态和消息。
你调用 Jackson 来反序列化。使用了您的 @ExceptionHandler 方法。
奖励:您可以返回 Jackson 经常有用的解析错误消息的文本。这些可以帮助客户找出他们的 json 被拒绝的原因。
如果 Spring 提供了一种更时尚的方法,一个要子类化的类,一个特殊的注解,我不会感到惊讶。我没有追求过。
你应该怎么做?
400 vs. 422 是我不想打官司的案例。根据您的优先级,最好接受 Spring 的约定。
RFC 7231 开启状态 400
400(Bad Request)状态码表示服务器不能或 由于某些被认为是 客户端错误(例如,格式错误的请求语法、无效请求 消息框架或欺骗性请求路由)。
如果 HTTP 状态代码警察发现了你,你可以指向这个并说“我认为这个输入是一个客户端错误。”然后争辩说 422 是不合适的,除非你提供 WebDAV只是为了让他们失去平衡。
【讨论】:
以上是关于为 Jackson 自定义反序列化程序抛出带有 HTTP 状态代码的自定义异常的主要内容,如果未能解决你的问题,请参考以下文章
在 Jackson / Spring Boot 中测试自定义 Json Deserializer
当值为“null”时,Jackson 忽略自定义字段反序列化器