SpringBoot系列教程web篇之Get請求引數解析姿勢彙總
一般在開發web應用的時候,如果提供http介面,最常見的http請求方式為GET/POST,我們知道這兩種請求方式的一個顯著區別是GET請求的引數在url中,而post請求可以不在url中;那麼一個SpringBoot搭建的web應用可以如何解析發起的http請求引數呢?
下面我們將結合例項彙總一下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,4]%
# age不傳
➜ ~ curl 'http://localhost:8080/get/ano?name=%E4%B8%80%E7%81%B0%E7%81%B0blog&uids=1,4'
name: 一灰灰blog age: null uids: [1,4]%
# 必選引數name不傳時
➜ ~ curl 'http://localhost:8080/get/ano?uids=1,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,ha]%
➜ ~ curl 'http://localhost:8080/get/arg3?names=yi,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,4'
BaseReqDO(name=yihuihui,age=18,uIds=[1,4])%
# 沒有傳參的屬性為null;因此如果POJO中成員為基本型別,則引數必傳
➜ ~ curl 'http://127.0.0.1:8080/get/bean?name=yihuihui&age=18'
BaseReqDO(name=yihuihui,uIds=null)%
複製程式碼
II. 其他
0. 專案
1. 一灰灰Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰Blog個人部落格 blog.hhui.top
- 一灰灰Blog-Spring專題部落格 spring.hhui.top