从 HttpServletRequest 获取查询参数

1、概览

后端 HTTP API 开发最重要的功能之一是解析前端传递的请求查询参数。

本文将带你了解几种直接从 HttpServletRequest 获取查询参数的方法,以及 Spring MVC 提供的一些简洁方法。

2、HttpServletRequest 中的方法

首先,来看看 HttpServletRequest 提供的与参数相关的方法。

2.1、HttpServletRequest#getQueryString()

HttpServletRequest#getQueryString() 可以直接从 URL 获取查询字符串信息:

@GetMapping("/api/byGetQueryString")
public String byGetQueryString(HttpServletRequest request) {
    return request.getQueryString();
}

使用 curl 向该 API 发送一个包含多个参数的 GET 请求时,getQueryString() 方法会返回 ? 后面的所有字符:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetQueryString?username=bob&roles=admin&roles=stuff'
username=bob&roles=admin&roles=stuff

如果将 @GetMapping 更改为 @RequestMapping,当使用 POST/PUT/PATCH/DELETE HTTP 方法发送请求时,返回的响应相同。也就是说 HttpServletRequest#getQueryString 始终获取到的是 URL 中的查询参数,无论 HTTP 方法是什么。因此,本教程只关注GET请求。

2.2、HttpServletRequest#getParameter(String)

为了简化参数的解析,HttpServletRequest 提供了一个 getParameter 方法,可以通过参数名获取参数值:

@GetMapping("/api/byGetParameter")
public String byGetParameter(HttpServletRequest request) {
    String username = request.getParameter("username");
    return "username:" + username;
}

发送一个查询字符串为 username=bob 的 GET 请求时,调用 getParameter("username") 会返回 bob

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetParameter?username=bob&roles=admin&roles=stuff'
username:bob

2.3、HttpServletRequest#getParameterValues(String)

getParameterValues 方法的作用与 getParameter 方法类似,但它返回的是 String[] 而不是 String。这是因为 HTTP 规范允许传递多个同名参数。

@GetMapping("/api/byGetParameterValues")
public String byGetParameterValues(HttpServletRequest request) {
    String[] roles = request.getParameterValues("roles");
    return "roles:" + Arrays.toString(roles);
}

因此,当传递两个 roles 参数时,数组中就会出现两个值:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetParameterValues?username=bob&roles=admin&roles=stuff'
roles:[admin, stuff]

2.4、HttpServletRequest#getParameterMap()

假设有以下 UserDto POJO,用于封装请求参数:

public class UserDto {
    private String username;
    private List<String> roles;
    // get、set 方法省略
}

如你所见,可以有多个不同的参数名和一个或多个值。针对这些情况,HttpServletRequest 提供了另一种方法 getParameterMap(),它返回 Map<String, String[]>。该方法允许通过 Map 获取参数值。

@GetMapping("/api/byGetParameterMap")
public UserDto byGetParameterMap(HttpServletRequest request) {
    Map parameterMap = request.getParameterMap();
    String[] usernames = parameterMap.get("username");
    String[] roles = parameterMap.get("roles");
    UserDto userDto = new UserDto();
    userDto.setUsername(usernames[0]);
    userDto.setRoles(Arrays.asList(roles));
    return userDto;
}

测试如下:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetParameterMap?username=bob&roles=admin&roles=stuff'
{"username":"bob","roles":["admin","stuff"]}

3、在 Spring MVC 中获取参数

来看看 Spring MVC 为解析查询字符串提供了哪些方便。

3.1、名称参数

有了 Spring MVC 框架,我们就不必直接使用 HttpServletRequest 手动解析参数了。

对于第一种情况,可以在 Handler 方法中定义两个参数,这两个参数的查询参数名称分别是 usernameroles,然后删除了 HttpServletRequest 参数,由 Spring MVC 处理。

@GetMapping("/api/byParameterName")
public UserDto byParameterName(String username, String[] roles) {
    UserDto userDto = new UserDto();
    userDto.setUsername(username);
    userDto.setRoles(Arrays.asList(roles));
    return userDto;
}

测试,响应结果和上面一样:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byParameterName?username=bob&roles=admin&roles=stuff'
{"username":"bob","roles":["admin","stuff"]}

3.2、@RequestParam

如果 HTTP 查询参数名和 Java 方法参数名不同,或者编译字节码中不会保留方法参数名,可以在方法参数名上配置注解 @RequestParam,以应对这种情况。

使用 @RequestParam("username")@RequestParam("roles") 指定参数名称,如下:

@GetMapping("/api/byAnnoRequestParam")
public UserDto byAnnoRequestParam(@RequestParam("username") String var1, @RequestParam("roles") List<String> var2) {
    UserDto userDto = new UserDto();
    userDto.setUsername(var1);
    userDto.setRoles(var2);
    return userDto;
}

测试:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byAnnoRequestParam?username=bob&roles=admin&roles=stuff'
{"username":"bob","roles":["admin","stuff"]}

3.3、POJO

更简单一点,可以直接使用 POJO 作为参数类型:

@GetMapping("/api/byPojo")
public UserDto byPojo(UserDto userDto) {
    return userDto;
}

Spring MVC 会解析参数,创建 POJO 实例,并自动填充所需的参数:

$ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byPojo?username=bob&roles=admin&roles=stuff'
{"username":"bob","roles":["admin","stuff"]}

最后,通过单元测试来确保最后四个方法提供的功能完全相同。

@ParameterizedTest
@CsvSource(textBlock = """
    /api/byGetParameterMap
    /api/byParameterName
    /api/byAnnoRequestParam
    /api/byPojo
    """)
public void whenPassParameters_thenReturnResolvedModel(String path) throws Exception {
    this.mockMvc.perform(get(path + "?username=bob&roles=admin&roles=stuff"))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.username").value("bob"))
      .andExpect(jsonPath("$.roles").value(containsInRelativeOrder("admin", "stuff")));
}

4、总结

本文介绍了如何从 HttpServletRequest 中获取查询参数,以及 Spring MVC 提供了哪些对查询参数的封装方式,


Ref:https://www.baeldung.com/java-httpservletrequest-get-query-parameters