一般在开发web应用的时候,如果提供http接口,最常见的http请求方式为GET/POST,我们知道这两种请求方式的一个显著区别是GET请求的参数在url中,而post请求可以不在url中;那么一个SpringBoot搭建的web应用可以如何解析发起的http请求参数呢?

下面我们将结合实例汇总一下GET请求参数的几种常见的解析姿势

原文:190824-SpringBoot系列教程web篇之Get请求参数解析姿势汇总

I. 环境搭建

首先得搭建一个web应用才有可能继续后续的测试,借助SpringBoot搭建一个web应用属于比较简单的活;

创建一个maven项目,pom文件如下

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7</version>
<relativePath/> <!-- lookup parent from update -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies> <build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

添加项目启动类Application.cass

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}

在演示请求参数的解析实例中,我们使用终端的curl命令来发起http请求(主要原因是截图上传太麻烦,还是终端的文本输出比较方便;缺点是不太直观)

II. GET请求参数解析

接下来我们正式进入参数解析的妖娆姿势篇,会介绍一下常见的一些case(并不能说包含了所有的使用case)

下面所有的方法都放在 ParamGetRest 这个Controller中

@RestController
@RequestMapping(path = "get")
public class ParamGetRest {
}

1. HttpServletRequest

直接使用HttpServletRequest来获取请求参数,属于比较原始,但是灵活性最高的使用方法了。

常规使用姿势是方法的请求参数中有一个HttpServletRequest,我们通过ServletRequest#getParameter(参数名)来获取具体的请求参数,下面演示返回所有请求参数的case

@GetMapping(path = "req")
public String requestParam(HttpServletRequest httpRequest) {
Map<String, String[]> ans = httpRequest.getParameterMap();
return JSON.toJSONString(ans);
}

测试case,注意下使用curl请求参数中有中文时,进行了url编码(后续会针对这个问题进行说明)

➜  ~ curl 'http://127.0.0.1:8080/get/req?name=yihuihiu&age=19'
{"name":["yihuihiu"],"age":["19"]}% ➜ ~ curl 'http://127.0.0.1:8080/get/req?name=%E4%B8%80%E7%81%B0%E7%81%B0&age=19'
{"name":["一灰灰"],"age":["19"]}%

使用HttpServletRequest获取请求参数,还有另外一种使用case,不通过参数传递的方式获取Request实例,而是借助RequestContextHolder;这样的一个好处就是,假设我们想写一个AOP,拦截GET请求并输出请求参数时,可以通过下面这种方式来处理

@GetMapping(path = "req2")
public String requestParam2() {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String name = request.getParameter("name");
return "param Name=" + name;
}

测试case

➜  ~ curl 'http://127.0.0.1:8080/get/req2?name=%E4%B8%80%E7%81%B0%E7%81%B0&age=19'
param Name=一灰灰%

2. 方法参数

这种解析方式比较厉害了,将GET参数与方法的参数根据参数名进行映射,从感官上来看,就像是直接调用这个一样

@GetMapping(path = "arg")
public String argParam(String name, Integer age) {
return "name: " + name + " age: " + age;
}

针对上面提供的方式,我们的测试自然会区分为下面几种,看下会怎样

  • 正好两个参数,与定义一直
  • 缺少一个请求参数
  • 多一个请求参数
  • 参数类型不一致
# 参数解析正常
➜ ~ curl 'http://127.0.0.1:8080/get/arg?name=%E4%B8%80%E7%81%B0%E7%81%B0&age=19'
name: 一灰灰 age: 19%
# 缺少一个参数时,为null
➜ ~ curl 'http://127.0.0.1:8080/get/arg?name=%E4%B8%80%E7%81%B0%E7%81%B0'
name: 一灰灰 age: null%
# 多了一个参数,无法被解析
➜ ~ curl 'http://127.0.0.1:8080/get/arg?name=%E4%B8%80%E7%81%B0%E7%81%B0&age=19&id=10'
name: 一灰灰 age: 19%
# 类型不一致,500
➜ ~ curl 'http://127.0.0.1:8080/get/arg?name=%E4%B8%80%E7%81%B0%E7%81%B0&age=haha' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 01:45:14 GMT
Connection: close

从上面实际的case可以看出,利用方法参数解析GET传参时,实际效果是:

  • 方法参数与GET传参,通过参数签名进行绑定
  • 方法参数类型,需要与接收的GET传参类型一致
  • 方法参数非基本类型时,若传参没有,则为null;(也就是说如果为基本类型,无法转null,抛异常)
  • 实际的GET传参可以多于方法定义的参数

接下来给一个数组传参解析的实例

@GetMapping(path = "arg2")
public String argParam2(String[] names, int size) {
return "name: " + (names != null ? Arrays.asList(names) : "null") + " size: " + size;
}

测试case如下,传数组时参数值用逗号分隔;基本类型,必须传参,否则解析异常

➜  ~ curl 'http://127.0.0.1:8080/get/arg2?name=yihui,erhui&size=2'
name: null size: 2% ➜ ~ curl 'http://127.0.0.1:8080/get/arg2?name=yihui,erhui' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 01:53:30 GMT
Connection: close

3. RequestParam 注解

这种方式看起来和前面有些相似,但更加灵活,我们先看一下注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
// 指定请求参数名
String value() default "";
// 指定请求参数名
String name() default "";
// true表示发起请求时这个参数必须存在
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}

有两个参数需要注意,一个是name表示这个参数与GET传参的哪个关联;required表示这个参数是否可选

下面是一个简单的使用方式

@GetMapping(path = "ano")
public String anoParam(@RequestParam(name = "name") String uname,
@RequestParam(name = "age", required = false) Integer age,
@RequestParam(name = "uids", required = false) Integer[] uids) {
return "name: " + uname + " age: " + age + " uids: " + (uids != null ? Arrays.asList(uids) : "null");
}

测试如下:

# 三个参数全在
➜ ~ curl 'http://localhost:8080/get/ano?name=%E4%B8%80%E7%81%B0%E7%81%B0blog&age=18&uids=1,3,4'
name: 一灰灰blog age: 18 uids: [1, 3, 4]%
# age不传
➜ ~ curl 'http://localhost:8080/get/ano?name=%E4%B8%80%E7%81%B0%E7%81%B0blog&uids=1,3,4'
name: 一灰灰blog age: null uids: [1, 3, 4]%
# 必选参数name不传时
➜ ~ curl 'http://localhost:8080/get/ano?uids=1,3,4' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 13:09:07 GMT
Connection: close

使用RequestParam注解时,如果指定了name/value,这个参数就与指定的GETGET传参关联;如果不指定时,则根据参数签名来关联

下面给出两个更有意思的使用方式,一个是枚举参数解析,一个是Map容纳参数,一个是数组参数解析

public enum TYPE {
A, B, C;
} @GetMapping(path = "enum")
public String enumParam(TYPE type) {
return type.name();
} @GetMapping(path = "enum2")
public String enumParam2(@RequestParam TYPE type) {
return type.name();
} @GetMapping(path = "mapper")
public String mapperParam(@RequestParam Map<String, Object> params) {
return params.toString();
} // 注意下面这个写法,无法正常获取请求参数,这里用来对比列出
@GetMapping(path = "mapper2")
public String mapperParam2(Map<String, Object> params) {
return params.toString();
} @GetMapping(path = "ano1")
public String anoParam1(@RequestParam(name = "names") List<String> names) {
return "name: " + names;
} // 注意下面这个写法无法正常解析数组
@GetMapping(path = "arg3")
public String anoParam2(List<String> names) {
return "names: " + names;
}

测试case如下

➜  ~ curl 'http://localhost:8080/get/enum?type=A'
A%
➜ ~ curl 'http://localhost:8080/get/enum2?type=A'
A%
➜ ~ curl 'http://localhost:8080/get/mapper?type=A&age=3'
{type=A, age=3}%
➜ ~ curl 'http://localhost:8080/get/mapper2?type=A&age=3'
{}%
➜ ~ curl 'http://localhost:8080/get/ano1?names=yi,hui,ha'
name: [yi, hui, ha]%
➜ ~ curl 'http://localhost:8080/get/arg3?names=yi,hui,ha' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 13:50:55 GMT
Connection: close

从测试结果可以知道:

  • GET传参映射到枚举时,根据enum.valueOf()来实例的
  • 如果希望使用Map来容纳所有的传参,需要加上注解@RequestParam
  • 如果参数为List类型,必须添加注解@RequestParam;否则用数组来接收

4. PathVariable

从请求的url路径中解析参数,使用方法和前面的差别不大

@GetMapping(path = "url/{name}/{index}")
public String urlParam(@PathVariable(name = "name") String name,
@PathVariable(name = "index", required = false) Integer index) {
return "name: " + name + " index: " + index;
}

上面是一个常见的使用方式,对此我们带着几个疑问设计case

  • 只有name没有index,会怎样?
  • 有name,有index,后面还有路径,会怎样?
➜  ~ curl http://127.0.0.1:8080/get/url/yihhuihui/1
name: yihhuihui index: 1% ➜ ~ curl 'http://127.0.0.1:8080/get/url/yihhuihui' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 13:27:08 GMT
Connection: close ➜ ~ curl 'http://127.0.0.1:8080/get/url/yihhuihui/1/test' -i
HTTP/1.1 500
Content-Length: 0
Date: Sat, 24 Aug 2019 13:27:12 GMT
Connection: close

从path中获取参数时,对url有相对严格的要求,注意使用


5. POJO

这种case,我个人用得比较多,特别是基于SpringCloud的生态下,借助Feign来调用第三方微服务,可以说是很舒爽了;下面看一下这种方式的使用姿势

首先定义一个POJO

@Data
public class BaseReqDO implements Serializable {
private static final long serialVersionUID = 8706843673978981262L; private String name; private Integer age; private List<Integer> uIds;
}

提供一个服务

@GetMapping(path = "bean")
public String beanParam(BaseReqDO req) {
return req.toString();
}

POJO中定义了三个参数,我们再测试的时候,看一下这些参数是否必选

# GET传参与POJO中成员名进行关联
➜ ~ curl 'http://127.0.0.1:8080/get/bean?name=yihuihui&age=18&uIds=1,3,4'
BaseReqDO(name=yihuihui, age=18, uIds=[1, 3, 4])%
# 没有传参的属性为null;因此如果POJO中成员为基本类型,则参数必传
➜ ~ curl 'http://127.0.0.1:8080/get/bean?name=yihuihui&age=18'
BaseReqDO(name=yihuihui, age=18, uIds=null)%

II. 其他

0. 项目

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

SpringBoot系列教程web篇之Get请求参数解析姿势汇总的更多相关文章

  1. SpringBoot系列教程web篇之Post请求参数解析姿势汇总

    作为一个常年提供各种Http接口的后端而言,如何获取请求参数可以说是一项基本技能了,本篇为<190824-SpringBoot系列教程web篇之Get请求参数解析姿势汇总>之后的第二篇,对 ...

  2. SpringBoot系列教程web篇之如何自定义参数解析器

    title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器 banner: /spring-blog/imgs/190831/logo.jpg tags: 请求参数 cat ...

  3. SpringBoot系列教程web篇Servlet 注册的四种姿势

    原文: 191122-SpringBoot系列教程web篇Servlet 注册的四种姿势 前面介绍了 java web 三要素中 filter 的使用指南与常见的易错事项,接下来我们来看一下 Serv ...

  4. SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇

    前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filte ...

  5. SpringBoot系列教程Web篇之开启GZIP数据压缩

    本篇可以归纳在性能调优篇,虽然内容非常简单,但效果可能出乎预料的好: 分享一个真实案例,我们的服务部署在海外,国内访问时访问服务时,响应有点夸张:某些返回数据比较大的接口,耗时在 600ms+上,然而 ...

  6. SpringBoot系列教程web篇Listener四种注册姿势

    java web三要素Filter, Servlet前面分别进行了介绍,接下来我们看一下Listener的相关知识点,本篇博文主要内容为SpringBoot环境下,如何自定义Listener并注册到s ...

  7. SpringBoot系列教程web篇之过滤器Filter使用指南

    web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多.基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用) ...

  8. SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

    关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理: 本篇博文则带来另外一种并不常见的使用方式,通过实 ...

  9. SpringBoot系列教程web篇之全局异常处理

    当我们的后端应用出现异常时,通常会将异常状况包装之后再返回给调用方或者前端,在实际的项目中,不可能对每一个地方都做好异常处理,再优雅的代码也可能抛出异常,那么在 Spring 项目中,可以怎样优雅的处 ...

随机推荐

  1. 11 open source business models

    https://www.zdnet.com/article/11-open-source-business-models/ Critics are always claiming open sourc ...

  2. Python的可变类型和不可变类型?

    Python的每个对象都分为可变和不可变可变:列表.字典   不可变:数字.字符串.元组

  3. 11-Flutter移动电商实战-首页_屏幕适配方案和制作

    1.flutter_ScreenUtil插件简介 flutter_ScreenUtil屏幕适配方案,让你的UI在不同尺寸的屏幕上都能显示合理的布局. 插件会让你先设置一个UI稿的尺寸,他会根据这个尺寸 ...

  4. 全局异常捕获处理-@ControllerAdvice+@HandleException

    涂涂影院管理系统这个demo中有个异常管理的标签,用于捕获 涂涂影院APP用户异常信息 ,有小伙伴好奇,排除APP,后台端的是如何处理全局异常的,故项目中的实际应用已记之. 关于目前的异常处理 在使用 ...

  5. 【洛谷P5158】 【模板】多项式快速插值

    卡常严重,可有采用如下优化方案: 1.预处理单位根 2.少取几次模 3.复制数组时用 memcpy 4.进行多项式乘法项数少的时候直接暴力乘 5.进行多项式多点求值时如果项数小于500的话直接秦九昭展 ...

  6. 2019.12.07 java基础

    编译时报错,叫做编译失败 class Demo01 { public static void main(String[] args) { int a; a=12; System.out.println ...

  7. Python面向对象 -- slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

    面向对象设计中最基础的3个概念:数据封装.继承和多态 动态给class增加功能 正常情况下,当定义了一个class,然后创建了一个class的实例后,可以在程序运行的过程中给该实例绑定任何属性和方法, ...

  8. rbenv mac&&linux 安装简单说明

    mac 可以通过brew linux 官方提供了运行脚本 # with curl curl -fsSL https://github.com/rbenv/rbenv-installer/raw/mas ...

  9. linux命令之------快捷键说明

    linux快捷键说明 (1)命令或目录补齐:Tab (2)遍历历史记录:history 上移:ctrl+p,下移:ctrl+n (3)光标移动 左移:ctrl+b:右移:ctrl+f:移到首部:ctr ...

  10. vue子组件与子组件之前传值-----最简单办法

    1.在main.js中定义一个值(红色为重点) new Vue({ el: '#app', data: { Bus: new Vue() }, router, store, render: h =&g ...