异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)

一、引言

本篇内容是“异常统一处理”系列文章的重要组成部分,主要聚焦于对 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 异常处理示意图

异常处理核心代码的示例如下:

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第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。
     * 

* 若存在多个参数被@RequestParam注解修饰,并且设置required = true,则Spring MVC框架会按照参数在方法签名中声明的顺序逐一进行校验。 * 一次异常只会包含一个参数的错误信息,而不是一次性列出所有缺失的必填参数。 *

* 参数不传时,报错示例: * 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);
    }

    /**
     * 获取参数的描述或字段名。
     * 

* 如果接口文档注解中有此参数的描述,则使用文档中的描述;如果接口文档注解中没有描述,则使用参数的字段名作为描述。 */ 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),则此参数为必传参数,不能为空。
     * 

* 参数不传时,报错示例: * 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 未处理时异常时的报错

  • 请求响应

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第2张图片

  • 控制台的错误日志

在这里插入图片描述

5.3 测试结果

(1)一个必填参数未传

姓名参数,被@RequestParam注解修饰,且为必填参数(默认为必填);此时请求中未携带此参数,则会抛出异常。

异常处理后的返回结果如下:

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第3张图片

(2)多个必填参数未传,按照参数顺序依次处理

多个参数被被@RequestParam注解修饰,并且设置required = true(表示这些参数是必需的),会按照参数在方法签名中声明的顺序逐一进行校验。

在示例接口中,请求中的姓名参数和手机号码参数都为空姓名参数在手机号码参数前面,所以会先校验姓名参数;当姓名参数未传时,会直接抛出异常,不会对后续必填参数继续进行校验。

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第4张图片

当姓名参数已经传递,此参数就通过了校验,这时会继续向下校验手机号码参数;如果手机号码参数没传(为空),则会抛出异常,异常处理后的返回结果如下:

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第5张图片

(3)@RequestParam(required = false):参数可以为空

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第6张图片

(5)参数在接口文档注解中没有描述,则使用参数的字段名作为描述

异常统一处理:MissingServletRequestParameterException(遗漏Servlet请求参数异常)_第7张图片

你可能感兴趣的:(#,spring,boot,异常统一处理)