当前位置: 首页 > article >正文

【SpringBoot】用一个常见错误说一下@RequestParam属性

问题

  • 在开发过程中,我们经常会用到 @RequestParam,我们常常会遇到另外一个问题。当需要特别多的请求参数时,我们往往会忽略其中一些参数是否可选。例如存在类似这样的代码:
    @RequestMapping(path = "/hi4", method = RequestMethod.GET)
    public String hi4(@RequestParam("name") String name, @RequestParam("address") String address){
        return name + ":" + address;
    };
    
  • 在访问 http://localhost:8080/hi4?name=xiaoming&address=beijing 时并不会出问题,但是一旦用户仅仅使用 name 做请求(即 http://localhost:8080/hi4?name=xiaoming )时,则会直接报错如下:
    在这里插入图片描述
  • 此时,返回错误码 400,提示请求格式错误:此处缺少 address 参数。
  • 实际上,部分初学者即使面对这个错误,也会觉得惊讶,既然不存在 address,address 应该设置为 null,而不应该是直接报错不是么?接下来我们就分析下。

问题解析

  • 要了解这个错误出现的根本原因,你就需要了解请求参数的发生位置。
  • 实际上,这里我们也能按注解名(@RequestParam)来确定解析发生的位置是在 RequestParamMethodArgumentResolver 中。为什么是它?
  • 追根溯源,针对当前案例,当根据 URL 匹配上要执行的方法是 hi4 后,要反射调用它,必须解析出方法参数 name 和 address 才可以。而它们被 @RequestParam 注解修饰,所以解析器借助 RequestParamMethodArgumentResolver 就成了很自然的事情。
  • 接下来我们看下 RequestParamMethodArgumentResolver 对参数解析的一些关键操作,参考其父类方法 AbstractNamedValueMethodArgumentResolver#resolveArgument:
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
          NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
       NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
       MethodParameter nestedParameter = parameter.nestedIfOptional();
       //省略其他非关键代码
       //获取请求参数
       Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
       if (arg == null) {
          if (namedValueInfo.defaultValue != null) {
             arg = resolveStringValue(namedValueInfo.defaultValue);
          }
          else if (namedValueInfo.required && !nestedParameter.isOptional()) {
             handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
          }
          arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
       }
       //省略后续代码:类型转化等工作
       return arg;
    }
    
  • 如代码所示,当缺少请求参数的时候,通常我们会按照以下几个步骤进行处理。
1. 查看 namedValueInfo 的默认值,如果存在则使用它。
  • 这个变量实际是通过下面的方法来获取的,参考 RequestParamMethodArgumentResolver#createNamedValueInfo:
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
       RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
       return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
    }
    
  • 实际上就是 @RequestParam 的相关信息,我们调试下,就可以验证这个结论,具体如下图所示:
    在这里插入图片描述
2. 在 @RequestParam 没有指明默认值时,会查看这个参数是否必须,如果必须,则按错误处理
  • 判断参数是否必须的代码即为下述关键代码行:
    namedValueInfo.required && !nestedParameter.isOptional()
    
  • 很明显,若要判定一个参数是否是必须的,需要同时满足两个条件:条件 1 是 @RequestParam 指明了必须(即属性 required 为 true,实际上它也是默认值),条件 2 是要求 @RequestParam 标记的参数本身不是可选的。
  • 我们可以通过 MethodParameter#isOptional 方法看下可选的具体含义:
    public boolean isOptional() {
       return (getParameterType() == Optional.class || hasNullableAnnotation() ||
             (KotlinDetector.isKotlinReflectPresent() &&
                   KotlinDetector.isKotlinType(getContainingClass()) &&
                   KotlinDelegate.isOptional(this)));
    }
    
  • 在不使用 Kotlin 的情况下,所谓可选,就是参数的类型为 Optional,或者任何标记了注解名为 Nullable 且 RetentionPolicy 为 RUNTIM 的注解。
3. 如果不是必须,则按 null 去做具体处理
  • 如果接受类型是 boolean,返回 false,如果是基本类型则直接报错,这里不做展开。
  • 结合我们的案例,我们的参数符合步骤 2 中判定为必选的条件,所以最终会执行方法 AbstractNamedValueMethodArgumentResolver#handleMissingValue:
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
       throw new ServletRequestBindingException("Missing argument '" + name +
             "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
    }
    

解决办法

  • 通过案例解析,我们很容易就能修正这个问题,就是让参数有默认值或为非可选即可,具体方法包含以下几种。
1. 设置 @RequestParam 的默认值
@RequestParam(value = "address", defaultValue = "no address") String address
2. 设置 @RequestParam 的 required 值
@RequestParam(value = "address", required = false) String address)
3. 标记任何名为 Nullable 且 RetentionPolicy 为 RUNTIME 的注解
//org.springframework.lang.Nullable 可以
//edu.umd.cs.findbugs.annotations.Nullable 可以
@RequestParam(value = "address") @Nullable String address
4. 修改参数类型为 Optional
@RequestParam(value = "address") Optional address
  • 从这些修正方法不难看出:假设你不学习源码,解决方法就可能只局限于一两种,但是深入源码后,解决方法就变得格外多了。这里要特别强调的是:在 Spring Web 中,默认情况下,请求参数是必选项。

http://www.kler.cn/a/501887.html

相关文章:

  • 在 Ubuntu 上安装和配置 Redis
  • MySQL的安装
  • OceanBase数据库设计与管理:构建高效分布式数据架构基石
  • VS2015 + OpenCV + OnnxRuntime-Cpp + YOLOv8 部署
  • 【Leetcode 热题 100】84. 柱状图中最大的矩形
  • C#调用OpenCvSharp实现图像的开运算和闭运算
  • 解锁“搭子小程序”开发新机遇,助力企业数字化转型
  • 【SH】Xiaomi9刷Windows10系统研发记录 、手机刷Windows系统教程、小米9重装win10系统
  • HTML实战课堂之简单的拜年程序
  • 4G、5G移远模块SIM卡热插拔问题解决
  • 10.Linux 时间
  • 一文讲解常见API开发工具
  • 【机器学习:十、神经网络概述】
  • 1. 初识Scala
  • 一 rk3568 Android 11固件开发环境搭建 (docker)
  • NAT 代理服务器
  • 【芯片设计- RTL 数字逻辑设计入门 9.2 -- flip flop 与 寄存器的关系详细介绍】
  • 【LeetCode】力扣刷题热题100道(26-30题)附源码 轮转数组 乘积 矩阵 螺旋矩阵 旋转图像(C++)
  • 项目实战--网页五子棋(用户模块)(1)
  • 支持selenium的chrome driver更新到131.0.6778.264
  • JDK8新特性详解
  • mermaid大全(语法、流程图、时序图、甘特图、饼图、用户旅行图、类图)
  • 在 VS Code 中使用通义灵码:解锁 AI 编程新体验
  • 【DB-GPT】开启数据库交互新篇章的技术探索与实践
  • 学习华为熵减,激发组织活力
  • Data Mesh: 数据产品化