You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot:拦截@RequestBody反序列化异常失效问题

解决Spring全局异常处理器无法捕获Jackson枚举反序列化InvalidFormatException的问题

问题根源

Jackson在将请求体JSON反序列化为AccountDto时,遇到非法货币标识(如UNK)会直接抛出InvalidFormatException,这个异常会被Spring包装为HttpMessageNotReadableException,且抛出时机在控制器方法执行之前的消息转换阶段。常规的@RestControllerAdvice默认仅捕获控制器层及后续流程抛出的异常,因此无法直接拦截到这个包装后的异常。

解决思路与实现

方案一:捕获包装后的HttpMessageNotReadableException

直接拦截Spring包装后的HttpMessageNotReadableException,从中提取原始的InvalidFormatException并判断是否为Currency枚举的反序列化错误,返回自定义响应:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<CustomErrorResponse> handleMessageParseError(HttpMessageNotReadableException ex) {
        Throwable rootCause = ex.getRootCause();
        // 检查根源异常是否为枚举反序列化错误
        if (rootCause instanceof InvalidFormatException invalidEx) {
            Class<?> targetType = invalidEx.getTargetType();
            if (targetType.isEnum() && targetType == Currency.class) {
                // 构造自定义错误信息,列出支持的货币类型
                String supportedCurrencies = Arrays.stream(Currency.values())
                        .map(Enum::name)
                        .collect(Collectors.joining(", "));
                String errorMsg = String.format("非法货币标识:%s,支持的类型为:%s", 
                        invalidEx.getValue(), supportedCurrencies);
                return ResponseEntity.badRequest()
                        .body(new CustomErrorResponse("INVALID_CURRENCY", errorMsg));
            }
        }
        // 处理其他消息解析错误
        return ResponseEntity.badRequest()
                .body(new CustomErrorResponse("INVALID_REQUEST", "请求格式不符合要求"));
    }

    // 自定义错误响应DTO
    public static class CustomErrorResponse {
        private String errorCode;
        private String errorMessage;

        public CustomErrorResponse(String errorCode, String errorMessage) {
            this.errorCode = errorCode;
            this.errorMessage = errorMessage;
        }

        // Getter方法(按需添加)
        public String getErrorCode() { return errorCode; }
        public String getErrorMessage() { return errorMessage; }
    }
}

方案二:自定义Jackson枚举反序列化器,抛出可控异常

给Currency枚举编写自定义反序列化器,遇到未知值时抛出自定义业务异常,再通过全局处理器捕获:

  1. 自定义反序列化器:
public class CurrencyDeserializer extends StdDeserializer<Currency> {

    protected CurrencyDeserializer() {
        super(Currency.class);
    }

    @Override
    public Currency deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String currencyStr = parser.getText().trim();
        try {
            return Currency.valueOf(currencyStr);
        } catch (IllegalArgumentException e) {
            // 抛出自定义异常
            throw new InvalidCurrencyException("非法货币标识:" + currencyStr);
        }
    }
}
  1. 给枚举添加反序列化注解:
@JsonDeserialize(using = CurrencyDeserializer.class)
public enum Currency {
    CNY, USD, EUR, GBP;
}
  1. 全局处理器捕获自定义异常:
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(InvalidCurrencyException.class)
    public ResponseEntity<CustomErrorResponse> handleInvalidCurrency(InvalidCurrencyException ex) {
        return ResponseEntity.badRequest()
                .body(new CustomErrorResponse("INVALID_CURRENCY", ex.getMessage()));
    }

    // 自定义业务异常
    public static class InvalidCurrencyException extends RuntimeException {
        public InvalidCurrencyException(String message) {
            super(message);
        }
    }

    // 自定义错误响应DTO同上
}

验证与注意事项

  • 确保@RestControllerAdvice所在类被Spring容器扫描(放置在主启动类的包或子包下)。
  • 检查是否存在其他优先级更高的异常处理器(如带有@Order注解的处理器)覆盖了当前逻辑。
  • 可以通过打印ex.getRootCause().getClass()确认异常的实际类型,确保判断逻辑准确。

内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:07:36