【问题标题】:Spring Boot custom exception handlingSpring Boot 自定义异常处理
【发布时间】:2021-07-10 02:00:15
【问题描述】:

我的要求是,如果 post 请求的 JSON 无效,我将需要发送 400 个 HTTP 响应代码,如果任何字段不可解析,则返回状态代码将为 422。示例 post 请求可以是:

    {
        "amount": "12.3343",
        "timestamp": "2018-07-17T09:59:51.312Z"
    }

Dto类提供如下,

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransactionDto {


    @NotNull
    @Min(0)
    private BigDecimal amount;

    @NotNull
    private LocalDateTime timestamp;
}

这是带有 POST 请求的控制器,

@Slf4j
@RestController
@RequestMapping("/")
@Validated
public class TransactionController {


@Autowired
private TransactionService transactionService;



    @Operation(description = "create a transaction using the provided JSON data")

    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "Create transaction", content = {
            @Content(mediaType = "application/json", schema = @Schema(implementation = Transaction.class))}),
        @ApiResponse(responseCode = "204", description = "Transaction is older than 60 seconds", content = @Content(mediaType = "application/json")),
        @ApiResponse(responseCode = "500", description = MessageConstant.INTERNAL_SERVER_ERROR_MSG, content = @Content)})

    @PostMapping(value = "/transactions")
    public ResponseEntity<Object> createProperty(@RequestBody @Valid TransactionDto transactionDto) {

        try {

            final LocalDateTime transactionTimestamp = transactionDto.getTimestamp();
            final LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneOffset.UTC);

            final long secondsDuration = Duration.between(transactionTimestamp, localDateTimeNow).toSeconds();

            final boolean isFuture = transactionTimestamp.isAfter(localDateTimeNow);

            if (isFuture) {

                return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
                    "Transaction is in future"), new HttpHeaders(), HttpStatus.ACCEPTED);
            }

            if (secondsDuration > 60) {

                return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NO_CONTENT,
                    "Transaction is older than 60 seconds"), new HttpHeaders(), HttpStatus.NO_CONTENT);
            }

            final Transaction transaction = transactionService.createTransaction(transactionDto);

            return new ResponseEntity<>(transaction, new HttpHeaders(), HttpStatus.CREATED);

        } catch (Exception ex) {

            log.error(MessageConstant.INTERNAL_SERVER_ERROR_MSG + ex.getMessage());
            return new ResponseEntity<>(ApiResponseMessage.getInternalServerError(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

如果“数量”是“sfdfd”,这不是 BigDecimal,我们应该提供 422。但是如果“数量”是“-12.3343”,这是一个约束验证错误,但数据是有效的并且可以解析。所以我们不能有 422。

这是我的异常处理类,

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {


    @Override
    @Nonnull
    protected ResponseEntity<Object> handleMissingServletRequestParameter(
        MissingServletRequestParameterException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {
        String error = ex.getParameterName() + " parameter is missing";

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            error), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
        @Nonnull HttpMediaTypeNotSupportedException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNSUPPORTED_MEDIA_TYPE,
            " media type is not supported. Supported media types "), new HttpHeaders(), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
    }

    @Override
    @NonNull
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
        @NonNull MethodArgumentNotValidException ex,
        @NonNull HttpHeaders headers,
        @NonNull HttpStatus status,
        @NonNull WebRequest request

    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
        apiError.setMessage("Validation error");
        apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
        apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
        return buildResponseEntity(apiError);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    protected ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex) {

        ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
        apiError.setMessage("Validation error");
        apiError.addValidationErrors(ex.getConstraintViolations());

        return buildResponseEntity(apiError);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {

        ServletWebRequest req = (ServletWebRequest) request;
        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        log.info("{} to {}", req.getHttpMethod(), req.getRequest().getServletPath());
        log.error(message, ex);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMessageNotReadable(
        @Nonnull HttpMessageNotReadableException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    private String prepareMessageFromException(Exception ex, ServletWebRequest request) {

        String message = ex.getMessage();

        log.info("{} to {}", request.getHttpMethod(), request.getRequest().getServletPath());
        log.error(message);

        if (message != null && !message.isEmpty()) {
            message = message.split(":")[0];
        }

        return message;
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMessageNotWritable(
        @Nonnull HttpMessageNotWritableException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String error = "Error writing JSON output";

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
            "Internal server error. please contact support !!"), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleNoHandlerFoundException(
        NoHandlerFoundException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            String.format("Could not find the %s method for URL %s", ex.getHttpMethod(), ex.getRequestURL())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    private ResponseEntity<Object> buildResponseEntity(ApiErrorResponse apiError) {
        return new ResponseEntity<>(apiError, apiError.getStatus());
    }

    @ExceptionHandler(EntityNotFoundException.class)
    protected ResponseEntity<Object> handleEntityNotFound(EntityNotFoundException ex) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NOT_FOUND,
            "Resource not found: "), new HttpHeaders(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    protected ResponseEntity<Object> handleDataIntegrityViolation(DataIntegrityViolationException ex, WebRequest request) {

        if (ex.getCause() instanceof ConstraintViolationException) {
            return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.CONFLICT,
                "Database error"), new HttpHeaders(), HttpStatus.CONFLICT);
        }

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
            "Internal server error. please contact support !!" + ex.getMessage()), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex, WebRequest request) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            String.format("The parameter '%s' of value '%s' could not be converted to type '%s'", ex.getName(), ex.getValue(), ex.getRequiredType())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }
}

目前,我在提到的两种情况下都得到 400。如何重构代码以获得正确的响应代码?

【问题讨论】:

  • HttpStatus.BAD_REQUEST 我相信会是 400
  • 是的,但是我是否根据需求修改代码以反映状态代码?
  • 试一试,看看会发生什么。
  • @TrevorKropp 这会更改两种情况的响应代码。对于 2 个不同的情况,我需要 400 和 422。
  • 约束冲突是例外之一。另一种情况是什么?

标签: java spring-boot rest


【解决方案1】:

在对现有代码进行一些修改后,我设法实现了这一点,

@Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMessageNotReadable(
        @Nonnull HttpMessageNotReadableException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);
        final Throwable throwableCause = ex.getCause();

        if (throwableCause instanceof InvalidFormatException) {

            return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
                message), new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY);
        }

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

所以当throwable为InvalidFormatException时,我会返回422响应状态码,其他方法和以前一样。

【讨论】:

    猜你喜欢
    • 2023-03-04
    • 2022-01-12
    • 1970-01-01
    • 2019-02-25
    • 2023-03-28
    • 2019-12-10
    • 2013-07-02
    • 2016-07-11
    • 2022-10-19
    相关资源
    最近更新 更多