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枚举编写自定义反序列化器,遇到未知值时抛出自定义业务异常,再通过全局处理器捕获:
- 自定义反序列化器:
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); } } }
- 给枚举添加反序列化注解:
@JsonDeserialize(using = CurrencyDeserializer.class) public enum Currency { CNY, USD, EUR, GBP; }
- 全局处理器捕获自定义异常:
@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
相关产品推荐
相关产品推荐

