1. 引言
后端HTTP API开发的重要能力之一是处理来自前端的查询参数。本教程将介绍如何直接从HttpServletRequest
获取这些参数,并使用Spring MVC提供的简洁方法。
2. HttpServletRequest
的方法
2.1. HttpServletRequest#getQueryString()
下面的例子展示了通过调用HttpServletRequest#getQueryString()
方法从URL中直接获取的内容:
@GetMapping("/api/byGetQueryString")
public String byGetQueryString(HttpServletRequest request) {
return request.getQueryString();
}
当我们使用curl发送带有多个参数的GET请求到此API时,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
始终能获取查询字符串,不论HTTP方法是什么。因此,在本文档中,我们将主要关注GET请求。为了简化展示HttpServletRequest
提供的方法,我们在后续示例中都会使用相同的请求参数。
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作为JSON API示例的一部分:
public class UserDto {
private String username;
private List<String> roles;
// standard getter/setters...
}
可以看到,一个或多个值可以对应多个不同的参数名。为此,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;
}
对于这个例子,我们将得到一个JSON响应:
$ 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
手动解析参数。在第一个示例中,我们定义了一个带有查询参数名称username
和roles
的方法,并移除了对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. 结论
在这篇文章中,我们介绍了如何使用Spring MVC从HttpServletRequest
获取参数。从这些示例中,我们可以看到,使用Spring MVC解析参数时,可以大大减少代码量。
如往常一样,本文档中的所有代码片段可在GitHub上找到。