Создайте пользовательское исключение с кодом статуса HTTP для десериализатора Jackson Custom - PullRequest
2 голосов
/ 18 марта 2019

У меня есть этот 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);
    }
}

И затем соответствующее IOException в @ControllerAdvice

@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.

Может быть, я просто скучаю по чему-то настолько маленькому, что не могу понять.

Этот подход был предложен здесь: Создайте пользовательское исключение при десериализации поля Date, используя Джексона в Java

Ответы [ 2 ]

0 голосов
/ 19 марта 2019

Вам не нужен метод 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
    }
}
0 голосов
/ 18 марта 2019

Ваш метод Controller никогда не вызывается, потому что при разборе тела JSON возникло исключение.

  • Ваш @ContollerAdvice не применяется, поскольку метод Controller не вызывается.

  • Ваш метод handleIOException не вызывается, и ваш статус 422. не применяется.

Я подозреваю, что это ситуация более подробно ...

  1. HTTP-запрос включает тело json.

  2. Метод контроллера, соответствующий @RequestMapping и другим аннотациям запроса, принимает в качестве параметра экземпляр класса DTO.

  3. Spring пытается десериализовать входящее тело json до вызова вашего метода управления. Это должно сделать это для передачи объекта DTO.

  4. Десериализация использует ваш собственный десериализатор, который генерирует IOException.

  5. Это IOException происходит до вашего метода контроллера. Фактически, ваш метод контроллера никогда не вызывается для этого запроса.

  6. Spring обрабатывает исключение, используя его поведение по умолчанию, возвращая HTTP 400. Spring имеет очень широкую концепцию HTTP 400 RFC 7231.

  7. Поскольку ваш метод контроллера никогда не вызывается, @ControllerAdvice никогда не применяется, и ваш @ExceptionHandler не видит исключения. Статус не установлен на 422.

Почему я верю в это?

Я часто вижу такое поведение из Spring, и я думаю, что это ожидаемое поведение. Но я не отслеживал документацию и не читал источник, чтобы быть уверенным.

Что вы можете с этим поделать?

Простой подход, который вам может не понравиться, заключается в объявлении метода вашего контроллера для приема входных данных, которые почти никогда не дают сбой, таких как String.

  • Вы берете на себя ответственность за проверку и десериализацию входных данных, и вы решаете, какой статус и сообщения возвращать.

  • Вы звоните Джексону для десериализации. Используются ваши методы @ExceptionHandler.

  • Бонус: Вы можете вернуть текст часто полезных сообщений об ошибках синтаксического анализа Джексона. Они могут помочь клиентам понять, почему их JSON отклонен.

Я не удивлюсь, если Spring предложит более стильный подход, класс для подкласса, специальную аннотацию. Я этого не преследовал.

Что с этим делать?

400 против 422 - дело, которое я предпочитаю не судить. В зависимости от ваших приоритетов может быть лучше принять конвенцию Спринга.

RFC 7231 On Status 400

Код состояния 400 (неверный запрос) указывает, что сервер не может или не будет обрабатывать запрос из-за того, что воспринимается как ошибка клиента (например, синтаксис некорректного запроса, неверный запрос) фрейминг сообщений или обманчивая маршрутизация запросов).

Если полиция получит от вас код состояния HTTP, вы можете указать на это и сказать: « Я воспринимаю этот ввод как ошибку клиента. » Затем утверждайте, что 422 неуместно, если вы просто не обслуживаете WebDAV. чтобы вывести их из равновесия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...