1. 引言
後端 HTTP API 開發中最重要的一項能力之一是解析前端傳遞的請求查詢參數。
在本教程中,我們將介紹幾種直接從 <em >HttpServletRequest</em> 中獲取查詢參數的方法,以及 Spring MVC 提供的簡潔方法。
2. 使用 HttpServletRequest 中的方法
首先,讓我們看看 HttpServletRequest 提供的與參數相關的各種方法。
2.1. HttpServletRequest#getQueryString()
此示例展示了通過調用方法 <emHttpServletRequest#getQueryString()</em> 直接從 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 將始終獲取查詢字符串,無論 HTTP 方法如何。因此,我們可以在本教程中僅關注 GET 請求。為了簡化我們演示 HttpServletRequest 提供的方法,我們將在以下所有示例中使用相同的請求參數。
2.2. <em>HttpServletRequest#getParameter(String)</em>
為了簡化參數解析,<em>HttpServletRequest</em> 提供了一個方法 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:bob2.3. `HttpServletRequest#getParameterValues
該方法 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 框架,我們無需手動使用 <em >HttpServletRequest</em > 直接解析參數。 在第一個案例中,我們定義了一個具有兩個參數的方法,參數名稱分別為 <em >username</em > 和 <em >roles</em >>,從而避免了使用 <em >HttpServletRequest</em >>,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 解析參數時,可以顯著減少代碼量。