簡體   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