180813-Spring之RestTemplate使用小結一
Spring之RestTemplate使用小結
作為一個Java後端,需要通過HTTP請求其他的網路資源可以說是一個比較常見的case了;一般怎麼做呢?
可能大部分的小夥伴直接撈起Apache的HttpClient開始做,或者用其他的一些知名的開源庫如OkHttp, 當然原生的HttpURLConnection也是沒問題的
本篇博文則主要關注點放在Sprig的生態下,利用RestTemplate
來發起Http請求的使用姿勢
I. RestTempalate 基本使用
0. 目標
在介紹如何使用RestTemplate之前,我們先丟擲一些小目標,至少需要知道通過RestTemplate可以做些什麼,以及我們要用它來幹些什麼
簡單的給出了一下常見的問題如下
- 普通的Get請求獲取返回資料,怎麼玩?
- post提交表達的請求,如何處理
- post請求中RequestBody的請求方式與普通的請求方式區別
- https/http兩種訪問如何分別處理
- 如何在請求中帶上指定的Header
- 有跨域的問題麼?如果有怎麼解決
- 有登入驗證的請求,該怎麼辦,怎樣攜帶身份資訊
- 上傳檔案可以支援麼
- 對於需要代理才能訪問的http資源,加代理的姿勢是怎樣的
上面的問題比較多,目測不是一篇博文可以弄完的,因此對這個拆解一下,本篇主要關注在RestTemplate的簡單Get/Post請求的使用方式上
1. 基本介面
撈出原始碼,看一下其給出的一些常用介面,基本上可以分為下面幾種
// get 請求
public <T> T getForObject();
public <T> ResponseEntity<T> getForEntity();
// head 請求
public HttpHeaders headForHeaders();
// post 請求
public URI postForLocation();
public <T> T postForObject();
public <T> ResponseEntity<T> postForEntity();
// put 請求
public void put();
// pathch
public <T> T patchForObject
// delete
public void delete()
// options
public Set<HttpMethod> optionsForAllow
// exchange
public <T> ResponseEntity<T> exchange()
上面提供的幾個介面,基本上就是Http提供的幾種訪問方式的對應,其中exchange卻又不一樣,後面細說
2. Get請求
從上面的介面命名上,可以看出可以使用的有兩種方式 getForObject
和 getForEntity
,那麼這兩種有什麼區別?
- 從介面的簽名上,可以看出一個是直接返回預期的物件,一個則是將物件包裝到
ResponseEntity
封裝類中 - 如果只關心返回結果,那麼直接用
GetForObject
即可 - 如果除了返回的實體內容之外,還需要獲取返回的header等資訊,則可以使用
getForEntit
a. 建立Get介面
為了驗證RestTemplate的使用姿勢,當然得先提供一個後端的REST服務,這了直接用了我個人的一個古詩詞的後端介面,來作為簡單的Get測試使用
請求連線: https://story.hhui.top/detail?id=666106231640
返回結果:
{
"status": {
"code": 200,
"msg": "SUCCESS"
},
"result": {
"id": 666106231640,
"title": "西塞山二首(今謂之道士磯,即興國軍大冶縣",
"author": "王周",
"content": "西塞名山立翠屏,濃嵐橫入半江青。\n千尋鐵鎖無由問,石壁空存道者形。\n匹婦頑然莫問因,匹夫何去望千春。\n翻思岵屺傳詩什,舉世曾無化石人。",
"explain": "",
"theme": "無",
"dynasty": "唐詩"
}
}
b. getForObject方式
首先看下完整的介面簽名
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException ;
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException ;
@Nullable
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
有三個過載的方法,從介面上也比較容易看出如何使用,其中有點疑惑的則是第一鍾,引數應該怎麼傳了,下面給出上面幾種的使用姿勢
public class RestTestmplateTest {
private RestTemplate restTemplate;
@Before
public void init() {
restTemplate = new RestTemplate();
}
@lombok.Data
static class InnerRes {
private Status status;
private Data result;
}
@lombok.Data
static class Status {
int code;
String msg;
}
@lombok.Data
static class Data {
long id;
String theme;
String title;
String dynasty;
String explain;
String content;
String author;
}
@Test
public void testGet() {
// 使用方法一,不帶引數
String url = "https://story.hhui.top/detail?id=666106231640";
InnerRes res = restTemplate.getForObject(url, InnerRes.class);
System.out.println(res);
// 使用方法一,傳參替換
url = "https://story.hhui.top/detail?id={?}";
res = restTemplate.getForObject(url, InnerRes.class, "666106231640");
System.out.println(res);
// 使用方法二,map傳參
url = "https://story.hhui.top/detail?id={id}";
Map<String, Object> params = new HashMap<>();
params.put("id", 666106231640L);
res = restTemplate.getForObject(url, InnerRes.class, params);
System.out.println(res);
// 使用方法三,URI訪問
URI uri = URI.create("https://story.hhui.top/detail?id=666106231640");
res = restTemplate.getForObject(uri, InnerRes.class);
System.out.println(res);
}
}
看上面的testcase,後面兩個方法的使用沒什麼好說的,主要看一下org.springframework.web.client.RestTemplate#getForObject(java.lang.String, java.lang.Class<T>, java.lang.Object...)
的使用姿勢
- 根據實際傳參替換url模板中的內容
- 使用方法一時,模板中使用
{?}
來代表坑位,根據實際的傳參順序來填充 - 使用方法二時,模板中使用
{xx}
, 而這個xx,對應的就是map中的key
上面執行後的截圖如下
c. getForEntity方式
既然getForObject有三種使用方法,那麼getForEntity理論上也應該有對應的三種方式
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException ;
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
因為使用姿勢和上面一致,因此只拿一個進行測試
@Test
public void testGetForEntity() {
String url = "https://story.hhui.top/detail?id=666106231640";
ResponseEntity<InnerRes> res = restTemplate.getForEntity(url, InnerRes.class);
System.out.println(res);
}
對這個,我們主要關注的就是ResponseEntity封裝中,多了哪些東西,截圖如下
從上面可以看出,多了兩個東西
- 一個返回的http狀態碼,如200表示請求成功,500伺服器錯誤,404not found等
- 一個 ResponseHeader
3. Post請求
從上面的介面說明上看,post請求除了有forObject 和 forEntity之外,還多了個forLocation;其次post與get一個明顯的區別就是傳參的姿勢問題,get的引數一般會待在url上;post的則更常見的是通過表單的方式提交
因此接下來關注的重點在於forLocation是什麼,以及如何傳參
a. post介面mock
首先建立一個簡單的提供POST請求的REST服務,基於Spring-boot簡單搭建一個,如下
@ResponseBody
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST})
public String post(HttpServletRequest request,
@RequestParam(value = "email", required = false) String email,
@RequestParam(value = "nick", required = false) String nick) {
Map<String, Object> map = new HashMap<>();
map.put("code", "200");
map.put("result", "add " + email + " # " + nick + " success!");
return JSON.toJSONString(map);
}
b. postForObject方法
首先看一下介面簽名
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException ;
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException ;
上面的三個方法,看起來和前面並沒有太大的區別,只是多了一個request引數,那麼具體的使用如何呢?
下面分別給出使用用例
@Test
public void testPost() {
String url = "http://localhost:8080/post";
String email = "test@hhui.top";
String nick = "一灰灰Blog";
MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
request.add("email", email);
request.add("nick", nick);
// 使用方法三
URI uri = URI.create(url);
String ans = restTemplate.postForObject(uri, request, String.class);
System.out.println(ans);
// 使用方法一
ans = restTemplate.postForObject(url, request, String.class);
System.out.println(ans);
// 使用方法一,但是結合表單引數和uri引數的方式,其中uri引數的填充和get請求一致
request.clear();
request.add("email", email);
ans = restTemplate.postForObject(url + "?nick={?}", request, String.class, nick);
System.out.println(ans);
// 使用方法二
Map<String, String> params = new HashMap<>();
params.put("nick", nick);
ans = restTemplate.postForObject(url + "?nick={nick}", request, String.class, params);
System.out.println(ans);
}
上面分別給出了三種方法的呼叫方式,其中post傳參區分為兩種,一個是uri引數即拼接在url中的,還有一個就是表單引數
- uri引數,使用姿勢和get請求中一樣,填充uri中模板坑位
- 表單引數,由
MultiValueMap
封裝,同樣是kv結構
c. postForEntity
和前面的使用姿勢一樣,無非是多了一層包裝而已,略過不講
d. postForLocation
這個與前面有點區別,從介面定義上來說,主要是
POST 資料到一個URL,返回新建立資源的URL
同樣提供了三個介面,分別如下,需要注意的是返回結果,為URI物件,即網路資源
public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)
throws RestClientException ;
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
throws RestClientException ;
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException ;
那麼什麼樣的介面適合用這種訪問姿勢呢?
想一下我們一般登入or註冊都是post請求,而這些操作完成之後呢?大部分都是跳轉到別的頁面去了,這種場景下,就可以使用 postForLocation
了,提交資料,並獲取返回的URI,一個測試如下
首先mock一個後端介面
@ResponseBody
@RequestMapping(path = "success")
public String loginSuccess(String email, String nick) {
return "welcome " + nick;
}
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST})
public String post(HttpServletRequest request, @RequestParam(value = "email", required = false) String email,
@RequestParam(value = "nick", required = false) String nick) {
return "redirect:/success?email=" + email + "&nick=" + nick + "&status=success";
}
訪問的測試用例,基本上和前面的一樣,沒有什麼特別值得一說的
@Test
public void testPostLocation() {
String url = "http://localhost:8080/post";
String email = "test@hhui.top";
String nick = "一灰灰Blog";
MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
request.add("email", email);
request.add("nick", nick);
// 使用方法三
URI uri = restTemplate.postForLocation(url, request);
System.out.println(uri);
}
執行結果如下
獲取到的就是302跳轉後端url,細心的朋友可能看到上面中文亂碼的問題,如何解決呢?
一個簡單的解決方案就是url編碼一下
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST},
produces = "charset/utf8")
public String post(HttpServletRequest request, @RequestParam(value = "email", required = false) String email,
@RequestParam(value = "nick", required = false) String nick) throws UnsupportedEncodingException {
return "redirect:/success?email=" + email + "&nick=" + URLEncoder.encode(nick, "UTF-8") + "&status=success";
}
II. 小結
上面目前只給出了Get/Post兩種請求方式的基本使用方式,並沒有涉及到更高階的如新增請求頭,新增證照,設定代理等,高階的使用篇等待下一篇出爐,下面小結一下上面的使用姿勢
1. Get請求
get請求中,引數一般都是帶在url上,對於引數的填充,有兩種方式,思路一致都是根據實際的引數來填充url中的佔位符的內容;根據返回結果,也有兩種方式,一個是隻關心返回物件,另一個則包含了返回headers信心
引數填充
- 形如
http://story.hhui.top?id={0}
的 url
- 呼叫
getForObject(String url, Class<T> responseType, Object... uriVariables)
- 模板中的0,表示 uriVariables 陣列中的第0個, i,則表示第i個
- 如果沒有url引數,也推薦用這個方法,不傳uriVariables即可
- 形如
http://story.hhui.top?id={id}
的 url
- 呼叫
getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
- map引數中的key,就是url引數中 {} 中的內容
其實還有一種傳參方式,就是path引數,填充方式和上面一樣,並沒有什麼特殊的玩法,上面沒有特別列出
返回結果
- 直接獲取返回的資料
getForObject
- 獲取待responseHeader的資料
getForEntity
2. Post請求
- post請求的返回也有兩種,和上面一樣
- post請求,引數可以區分為表單提交和url引數,其中url引數和前面的邏輯一致
- post表單引數,請包裝在
MultiValueMap
中,作為第二個引數Request
來提交 - post的方法,還有一個
postForLocation
,返回的是一個URI物件,即適用於返回網路資源的請求方式
3. 其他
最前面提了多點關於網路請求的常見case,但是上面的介紹,明顯只處於基礎篇,我們還需要關注的有
- 如何設定請求頭?
- 有身份驗證的請求,如何攜帶身份資訊?
- 代理的設定
- 檔案上傳可以怎麼做?
- post提交json串(即RequestBody) 又可以怎麼處理
上面可能還停留在應用篇,對於原始碼和實現有興趣的話,問題也就來了
- RestTemplaet的實現原理是怎樣的
- 前面url引數的填充邏輯實現是否優雅
- 返回的物件如何解析
- ....
小小的一個工具類,其實東西還挺多的,接下來的小目標,就是針對上面提出的點,逐一進行研究
III. 其他
1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
小灰灰Blog&公眾號
知識星球
相關文章
- Spring之RestTemplate使用小結SpringREST
- Spring之RestTemplate中級使用篇SpringREST
- 今日教學:RestTemplate 結合 Ribbon 使用REST
- 一起學 Spring 之 RestTemplateSpringREST
- RestTemplate使用REST
- RestTemplate的使用REST
- 聊一聊 RestTemplateREST
- Python之時間和日期使用小結Python
- Spring之RequestBody的使用姿勢小結Spring
- DNS之BIND使用小結(Forward轉發)DNSForward
- 【API知識】RestTemplate的使用APIREST
- RestTemplate和 apache HttpClient 使用方式RESTApacheHTTPclient
- RestTemplate全網最強總結(永久更新)REST
- Spring之Config小結Spring
- CocoaPods使用小結
- Git使用小結Git
- FlatBuffers使用小結
- 如何使用RestTemplate訪問restful服務REST
- 命令列使用小結命令列
- PyCharm工具使用小結PyCharm
- DM TDD使用小結
- 04-spring-boot-resttemplate netty定製使用SpringbootRESTNetty
- SpringCloud(一)微服務遠端呼叫 -- RestTemplateSpringGCCloud微服務REST
- (18)企業採購招標系統之Ribbon結合RestTemplate實現負載均衡REST負載
- 小程式實踐小坑小結(一)
- Iptables之recent模組小結
- CSS 小結筆記之emCSS筆記
- CSS 小結筆記之定位CSS筆記
- CSS 小結筆記之背景CSS筆記
- CSS 小結筆記之BFCCSS筆記
- Seata之小結和測試
- Android ConstraintLayout 最新使用小結AndroidAI
- XGBoost類庫使用小結
- git 子模組使用小結Git
- Editor.md 使用小結
- W5500 使用小結
- 面試小結(一)面試
- RestTemplate實踐REST