赞
踩
本篇内容是“异常统一处理”系列文章的重要组成部分,主要聚焦于对 HttpMessageNotReadableException
的原理解析与异常处理机制,并给出测试案例。
- 关于 全局异常统一处理 的原理和完整实现逻辑,请参考文章:
《SpringBoot 全局异常统一处理(AOP):@RestControllerAdvice + @ExceptionHandler + @ResponseStatus》- 本文仅详细解析 HttpMessageNotReadableException 的异常处理;其他类型异常的原理和处理方法,请参阅本文所在专栏内的其他文章。
HttpMessageNotReadableException
是 Spring Framework(尤其是 Spring Web MVC 和 Spring Boot 应用程序中)处理 HTTP 请求时抛出的一个异常。当 Spring 在尝试将接收到的 HTTP 请求消息体转换为 Java 对象,例如使用 @RequestBody
注解的方法参数时,如果无法正确解析或读取消息内容,就会抛出此异常。
这个异常通常发生在以下几种情况:
请求体为空:
@RequestBody
注解期待接收请求体中的数据,但实际请求中并没有提供任何有效的内容。请求体格式不正确:
内容类型不匹配:
类型转换失败:
GET 请求与 @RequestBody 使用:
@RequestBody
,且实际请求没有请求体,则会引发此异常。解决该异常的一般步骤包括:
@RequestBody
注解是否合理,特别是对于 GET 请求来说,一般应避免使用此注解。在Spring Boot应用中,我们可以通过使用@ExceptionHandler
注解来捕获并处理HttpMessageNotReadableException
异常。
package com.example.core.advice; import com.example.core.advice.util.UserTipGenerator; import com.example.core.model.Result; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.HandlerMethod; /** * 全局异常处理器 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * Http消息不可读异常。 * <p> * 报错原因包括(不完全的列举): * <p> * (1)缺少请求体(RequestBody)异常; * <p> * (2)无效格式异常。比如:参数为数字,但是前端传递的是字符串且无法解析成数字。 * <p> * (3)Json解析异常(非法Json格式)。传递的数据不是合法的Json格式。比如:key-value对中的value(值)为String类型,却没有用双引号括起来。 * <p> * 举例: * (1)缺少请求体(RequestBody)异常。报错: * DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: * Required request body is missing: * public void com.example.web.user.controller.UserController.addUser(com.example.web.model.param.UserAddParam)] */ @ExceptionHandler @ResponseStatus(value = HttpStatus.BAD_REQUEST) public Result<String> handle(HttpMessageNotReadableException e, HandlerMethod handlerMethod) { Throwable rootCause = e.getRootCause(); // 无效格式异常处理。比如:目标格式为数值,输入为非数字的字符串("80.5%"、"8.5.1"、"张三")。 if (rootCause instanceof InvalidFormatException) { String userMessage = UserTipGenerator.getUserMessage((InvalidFormatException) rootCause); String format = "HttpMessageNotReadableException-InvalidFormatException(Http消息不可读异常-无效格式异常):%s"; String errorMessage = String.format(format, e.getMessage()); return Result.fail(userMessage, String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage); } String userMessage = "Http消息不可读异常!请稍后重试,或联系业务人员处理。"; String errorMessage = String.format("HttpMessageNotReadableException(Http消息不可读异常):%s", e.getMessage()); return Result.fail(userMessage, String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage); } }
上述代码中,当出现HttpMessageNotReadableException
异常时,系统将返回一个状态码为400(Bad Request)的结果,并附带具体的错误信息。
package com.example.core.advice.util; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import java.math.BigDecimal; import java.math.BigInteger; /** * 用户提示生成器。 * * @author songguanxun * @since 2023-8-24 */ public class UserTipGenerator { public static String getUserMessage(InvalidFormatException rootCause) { // 目标类型 Class<?> targetType = rootCause.getTargetType(); // 目标类型提示信息 String targetTypeNotification = ""; if (targetType == BigInteger.class || targetType == Integer.class || targetType == Long.class || targetType == Short.class || targetType == Byte.class) { targetTypeNotification = "参数类型应为:整数;"; } else if (targetType == BigDecimal.class || targetType == Double.class || targetType == Float.class) { targetTypeNotification = "参数类型应为:数值;"; } Object value = rootCause.getValue(); return String.format("参数格式错误!%s当前输入参数:[%s]", targetTypeNotification, value); } }
本文以新增用户
接口为例,进行测试。
package com.example.web.user.controller; import com.example.web.model.param.UserAddParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @Slf4j @RestController @RequestMapping("users") @Tag(name = "用户管理") public class UserController { @PostMapping @Operation(summary = "新增用户") public void addUser(@Valid @RequestBody UserAddParam param) { log.info("测试,新增用户。param={}", param); } }
package com.example.web.model.param; import com.example.core.constant.RegexConstant; import com.example.core.validation.idcard.IdCard; import com.example.core.validation.phone.strict.MobilePhone; import com.example.core.validation.zipcode.ZipCode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import javax.validation.constraints.*; import java.util.Date; @Data @Schema(name = "新增用户Param") public class UserAddParam { @NotBlank(message = "姓名,不能为空") @Schema(description = "姓名", example = "张三") private String name; @NotEmpty(message = "手机号码,不能为空") @MobilePhone @Schema(description = "手机号码", example = "18612345678", pattern = RegexConstant.MOBILE_PHONE) private String mobilePhone; @Email @Schema(description = "电子邮箱", example = "zhangsan@example.com") private String email; @Schema(description = "开始时间", example = "2023-01-01 01:20:30") private Date beginTime; @Schema(description = "结束时间", example = "2023-01-01 01:20:30") private Date endTime; @ZipCode @Schema(description = "邮政编码", example = "201100", pattern = RegexConstant.ZIP_CODE) private String zipCode; @IdCard @Schema(description = "身份证号码", example = "110101202301024130") private String idCard; @Min(value = 18, message = "年龄必须大于18周岁") @Schema(description = "年龄", example = "18") private Integer age; @Max(value = 100, message = "上限必须小于100") @Schema(description = "上限", example = "100") private Double upperLimit; }
请求响应
控制台的错误日志
请求响应
参数应为整数
,但是前端传递的是字符串且无法解析成整数。
参数应为Double
,但是前端传递的是字符串且无法解析成数字。
控制台的错误日志
请求响应
控制台的错误日志
参数应为整数
,但是前端传递的是字符串且无法解析成整数。
参数应为Double
,但是前端传递的是字符串且无法解析成数字。
传递的数据不是合法的Json格式。比如:key-value对中的value(值)为String类型,却没有用双引号括起来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。