一、引言
本篇内容是“异常统一处理”系列文章的重要组成部分,主要聚焦于对 MissingServletRequestParameterException
的原理解析与异常处理机制,并给出测试案例。
- 关于 全局异常统一处理 的原理和完整实现逻辑,请参考文章:
《SpringBoot 全局异常统一处理(AOP):@RestControllerAdvice + @ExceptionHandler + @ResponseStatus》- 本文仅详细解析 MissingServletRequestParameterException 的异常处理;其他类型异常的原理和处理方法,请参阅本文所在专栏内的其他文章。
二、异常原理
MissingServletRequestParameterException
是Spring MVC框架中处理HTTP请求时抛出的一种异常,它继承自ServletException
,表示客户端的HTTP请求缺少了
服务器端所期望的一个或多个必需参数
。
当一个控制器方法通过@RequestParam
注解指定了某个参数是必需的(默认情况下就是必需的),而客户端在发起HTTP请求时未提供这个参数或提供的值为空时,服务器端Spring MVC在尝试将请求参数绑定到方法参数的过程中,就会抛出MissingServletRequestParameterException异常。
三、异常处理代码
在Spring Boot应用中,我们可以通过使用@ExceptionHandler
注解来捕获并处理MissingServletRequestParameterException
异常。这种异常会在请求参数缺失,且该参数被@RequestParam
修饰且required = true
时抛出。
3.1 异常处理示意图
异常处理核心代码的示例如下:
3.2 异常处理核心代码
异常处理策略的核心代码如下:
package com.example.core.advice;
import com.example.core.advice.util.UserTipGenerator;
import com.example.core.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
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 {
/**
* 参数被 @RequestParam 修饰,且 required = true,则被修饰的参数为必传参数,不能为空;否则就会抛出异常 MissingServletRequestParameterException。
* <p>
* 若存在多个参数被@RequestParam注解修饰,并且设置required = true,则Spring MVC框架会按照参数在方法签名中声明的顺序逐一进行校验。
* 一次异常只会包含一个参数的错误信息,而不是一次性列出所有缺失的必填参数。
* <p>
* 参数不传时,报错示例:
* DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException:
* Required request parameter 'name' for method parameter type String is not present]
*/
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<String> handle(MissingServletRequestParameterException e, HandlerMethod handlerMethod) {
String userMessage = UserTipGenerator.getUserMessage(e, handlerMethod);
String errorMessage = String.format("MissingServletRequestParameterException(遗漏Servlet请求参数异常):%s", e.getMessage());
return Result.fail(userMessage, String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage);
}
}
上述代码中,当出现MissingServletRequestParameterException
异常时,系统将返回一个状态码为400(Bad Request)的结果,并附带具体的错误信息,包括未传递的必传参数名称以及错误原因,从而提供清晰的错误反馈。
3.3 获取参数的描述或字段名
在生成用户提示信息时,需要明确告知用户哪个参数未传,此时需要获取参数的描述。
- 如果接口文档注解中有此参数的描述,则使用文档中的描述;
- 如果接口文档注解中没有此参数的描述,则使用参数的字段名作为描述。
package com.example.core.advice.util;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.HandlerMethod;
/**
* 用户提示生成器。
*
* @author songguanxun
* @since 2023-8-24
*/
public class UserTipGenerator {
public static String getUserMessage(MissingServletRequestParameterException e, HandlerMethod handlerMethod) {
String parameterDescription = getParameterDescription(e, handlerMethod);
return String.format("%s,不能为空", parameterDescription);
}
/**
* 获取参数的描述或字段名。
* <p>
* 如果接口文档注解中有此参数的描述,则使用文档中的描述;如果接口文档注解中没有描述,则使用参数的字段名作为描述。
*/
private static String getParameterDescription(MissingServletRequestParameterException e, HandlerMethod handlerMethod) {
String parameterName = e.getParameterName();
Parameter[] annotationsByType = handlerMethod.getMethod().getAnnotationsByType(Parameter.class);
for (Parameter parameter : annotationsByType) {
String name = parameter.name();
if (name != null && name.equals(parameterName)) {
String description = parameter.description();
if (StringUtils.hasText(description)) {
return description;
}
}
}
return parameterName;
}
}
四、@RequestParam修饰多个必填参数
在Spring Boot应用中,对于控制器方法中的参数处理机制,若存在多个参数被@RequestParam
注解修饰,并且设置required = true
(表示这些参数是必需的),则Spring MVC框架会按照参数在方法签名中声明的顺序逐一进行校验。
具体来说,当接收到一个HTTP请求时,Spring MVC会从请求中获取相应的参数并尝试绑定到控制器方法的参数上。如果在这一过程中发现首个必填参数缺失(即未在请求中提供对应的值),框架将立即抛出MissingServletRequestParameterException
异常,并停止对后续必填参数的校验。这意味着一次异常只会包含一个参数的错误信息,而不是一次性列出所有缺失的必填参数。
五、测试案例
5.1 测试代码
package com.example.web.exception.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("exception")
@Tag(name = "异常统一处理")
public class ExceptionController {
/**
* 参数被 @RequestParam 修饰,且 required = true(默认情况下就是 true),则此参数为必传参数,不能为空。
* <p>
* 参数不传时,报错示例:
* DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException:
* Required request parameter 'name' for method parameter type String is not present]
*/
@GetMapping("MissingServletRequestParameterException")
@Operation(summary = "异常:MissingServletRequestParameterException")
@Parameter(name = "name", description = "姓名", example = "张三")
@Parameter(name = "mobilePhone", description = "手机号码", example = "18612345678")
public String getByNameAndMobilePhone(@RequestParam String name, @RequestParam String mobilePhone) {
log.info("name = {}, mobilePhone = {}", name, mobilePhone);
return "请求成功";
}
/**
* 参数被 @RequestParam 修饰,且 required = false,则此参数可以为空。
*/
@GetMapping("MissingServletRequestParameterException/requiredEqualsFalse")
@Operation(summary = "异常:MissingServletRequestParameterException,可以为空")
@Parameter(name = "name", description = "姓名", example = "张三")
public String getRequiredEqualsFalse(@RequestParam(required = false) String name) {
log.info("name = {}", name);
return "请求成功";
}
/**
* 参数在接口文档注解中没有描述,则使用参数的字段名作为描述。
*/
@GetMapping("MissingServletRequestParameterException/withoutParamDescription")
@Operation(summary = "异常:MissingServletRequestParameterException,参数在接口文档注解中没有描述")
@Parameter(name = "name", example = "张三")
public String getWithoutParameterDescription(@RequestParam String name) {
log.info("name = {}", name);
return "请求成功";
}
}
5.2 未处理时异常时的报错
- 请求响应
- 控制台的错误日志
5.3 测试结果
(1)一个必填参数未传
姓名
参数,被@RequestParam
注解修饰,且为必填参数(默认为必填);此时请求中未携带此参数,则会抛出异常。
异常处理后的返回结果如下:
(2)多个必填参数未传,按照参数顺序依次处理
多个参数被被@RequestParam注解修饰,并且设置required = true(表示这些参数是必需的),会按照参数在方法签名中声明的顺序逐一进行校验。
在示例接口中,请求中的姓名
参数和手机号码
参数都为空
;姓名
参数在手机号码
参数前面,所以会先校验姓名参数;当姓名参数未传时,会直接抛出异常,不会对后续必填参数继续进行校验。
当姓名参数已经传递,此参数就通过了校验,这时会继续向下校验手机号码参数;如果手机号码参数没传(为空),则会抛出异常,异常处理后的返回结果如下: