Spring學(xué)習(xí)筆記之RestTemplate使用小結(jié)
前言
作為一個(gè)Java后端,需要通過(guò)HTTP請(qǐng)求其他的網(wǎng)絡(luò)資源可以說(shuō)是一個(gè)比較常見(jiàn)的case了;一般怎么做呢?
可能大部分的小伙伴直接撈起Apache的HttpClient開(kāi)始做,或者用其他的一些知名的開(kāi)源庫(kù)如OkHttp, 當(dāng)然原生的HttpURLConnection也是沒(méi)問(wèn)題的
本篇博文則主要關(guān)注點(diǎn)放在Sprig的生態(tài)下,利用RestTemplate來(lái)發(fā)起Http請(qǐng)求的使用姿勢(shì)
I. RestTempalate 基本使用
0. 目標(biāo)
在介紹如何使用RestTemplate之前,我們先拋出一些小目標(biāo),至少需要知道通過(guò)RestTemplate可以做些什么,以及我們要用它來(lái)干些什么
簡(jiǎn)單的給出了一下常見(jiàn)的問(wèn)題如下
- 普通的Get請(qǐng)求獲取返回?cái)?shù)據(jù),怎么玩?
- post提交表達(dá)的請(qǐng)求,如何處理
- post請(qǐng)求中RequestBody的請(qǐng)求方式與普通的請(qǐng)求方式區(qū)別
- https/http兩種訪問(wèn)如何分別處理
- 如何在請(qǐng)求中帶上指定的Header
- 有跨域的問(wèn)題么?如果有怎么解決
- 有登錄驗(yàn)證的請(qǐng)求,該怎么辦,怎樣攜帶身份信息
- 上傳文件可以支持么
- 對(duì)于需要代理才能訪問(wèn)的http資源,加代理的姿勢(shì)是怎樣的
上面的問(wèn)題比較多,目測(cè)不是一篇博文可以弄完的,因此對(duì)這個(gè)拆解一下,本篇主要關(guān)注在RestTemplate的簡(jiǎn)單Get/Post請(qǐng)求的使用方式上
1. 基本接口
撈出源碼,看一下其給出的一些常用接口,基本上可以分為下面幾種
// get 請(qǐng)求 public <T> T getForObject(); public <T> ResponseEntity<T> getForEntity(); // head 請(qǐng)求 public HttpHeaders headForHeaders(); // post 請(qǐng)求 public URI postForLocation(); public <T> T postForObject(); public <T> ResponseEntity<T> postForEntity(); // put 請(qǐng)求 public void put(); // pathch public <T> T patchForObject // delete public void delete() // options public Set<HttpMethod> optionsForAllow // exchange public <T> ResponseEntity<T> exchange()
上面提供的幾個(gè)接口,基本上就是Http提供的幾種訪問(wèn)方式的對(duì)應(yīng),其中exchange卻又不一樣,后面細(xì)說(shuō)
2. Get請(qǐng)求
從上面的接口命名上,可以看出可以使用的有兩種方式 getForObject 和 getForEntity,那么這兩種有什么區(qū)別?
- 從接口的簽名上,可以看出一個(gè)是直接返回預(yù)期的對(duì)象,一個(gè)則是將對(duì)象包裝到 ResponseEntity 封裝類(lèi)中
- 如果只關(guān)心返回結(jié)果,那么直接用 GetForObject 即可
- 如果除了返回的實(shí)體內(nèi)容之外,還需要獲取返回的header等信息,則可以使用 getForEntit
a. 創(chuàng)建Get接口
為了驗(yàn)證RestTemplate的使用姿勢(shì),當(dāng)然得先提供一個(gè)后端的REST服務(wù),這了直接用了我個(gè)人的一個(gè)古詩(shī)詞的后端接口,來(lái)作為簡(jiǎn)單的Get測(cè)試使用
請(qǐng)求連接: https://story.hhui.top/detail?id=666106231640
返回結(jié)果:
{ "status": { "code": 200, "msg": "SUCCESS" }, "result": { "id": 666106231640, "title": "西塞山二首(今謂之道士磯,即興國(guó)軍大冶縣", "author": "王周", "content": "西塞名山立翠屏,濃嵐橫入半江青。\n千尋鐵鎖無(wú)由問(wèn),石壁空存道者形。\n匹婦頑然莫問(wèn)因,匹夫何去望千春。\n翻思岵屺傳詩(shī)什,舉世曾無(wú)化石人。", "explain": "", "theme": "無(wú)", "dynasty": "唐詩(shī)" } }
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;
有三個(gè)重載的方法,從接口上也比較容易看出如何使用,其中有點(diǎn)疑惑的則是第一鐘,參數(shù)應(yīng)該怎么傳了,下面給出上面幾種的使用姿勢(shì)
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() { // 使用方法一,不帶參數(shù) 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訪問(wèn) URI uri = URI.create("https://story.hhui.top/detail?id=666106231640"); res = restTemplate.getForObject(uri, InnerRes.class); System.out.println(res); } }
看上面的testcase,后面兩個(gè)方法的使用沒(méi)什么好說(shuō)的,主要看一下org.springframework.web.client.RestTemplate#getForObject(java.lang.String, java.lang.Class<T>, java.lang.Object...)
的使用姿勢(shì)
- 根據(jù)實(shí)際傳參替換url模板中的內(nèi)容
- 使用方法一時(shí),模板中使用 {?} 來(lái)代表坑位,根據(jù)實(shí)際的傳參順序來(lái)填充
- 使用方法二時(shí),模板中使用 {xx}, 而這個(gè)xx,對(duì)應(yīng)的就是map中的key
上面執(zhí)行后的截圖如下
c. getForEntity方式
既然getForObject有三種使用方法,那么getForEntity理論上也應(yīng)該有對(duì)應(yīng)的三種方式
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;
因?yàn)槭褂米藙?shì)和上面一致,因此只拿一個(gè)進(jìn)行測(cè)試
@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); }
對(duì)這個(gè),我們主要關(guān)注的就是ResponseEntity封裝中,多了哪些東西,截圖如下
從上面可以看出,多了兩個(gè)東西
- 一個(gè)返回的http狀態(tài)碼,如200表示請(qǐng)求成功,500服務(wù)器錯(cuò)誤,404not found等
- 一個(gè) ResponseHeader
3. Post請(qǐng)求
從上面的接口說(shuō)明上看,post請(qǐng)求除了有forObject 和 forEntity之外,還多了個(gè)forLocation;其次post與get一個(gè)明顯的區(qū)別就是傳參的姿勢(shì)問(wèn)題,get的參數(shù)一般會(huì)待在url上;post的則更常見(jiàn)的是通過(guò)表單的方式提交
因此接下來(lái)關(guān)注的重點(diǎn)在于forLocation是什么,以及如何傳參
a. post接口mock
首先創(chuàng)建一個(gè)簡(jiǎn)單的提供POST請(qǐng)求的REST服務(wù),基于Spring-boot簡(jiǎn)單搭建一個(gè),如下
@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 ;
上面的三個(gè)方法,看起來(lái)和前面并沒(méi)有太大的區(qū)別,只是多了一個(gè)request參數(shù),那么具體的使用如何呢?
下面分別給出使用用例
@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); // 使用方法一,但是結(jié)合表單參數(shù)和uri參數(shù)的方式,其中uri參數(shù)的填充和get請(qǐng)求一致 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); }
復(fù)制代碼上面分別給出了三種方法的調(diào)用方式,其中post傳參區(qū)分為兩種,一個(gè)是uri參數(shù)即拼接在url中的,還有一個(gè)就是表單參數(shù)
- uri參數(shù),使用姿勢(shì)和get請(qǐng)求中一樣,填充uri中模板坑位
- 表單參數(shù),由MultiValueMap封裝,同樣是kv結(jié)構(gòu)
c. postForEntity
和前面的使用姿勢(shì)一樣,無(wú)非是多了一層包裝而已,略過(guò)不講
d. postForLocation
這個(gè)與前面有點(diǎn)區(qū)別,從接口定義上來(lái)說(shuō),主要是
POST 數(shù)據(jù)到一個(gè)URL,返回新創(chuàng)建資源的URL
同樣提供了三個(gè)接口,分別如下,需要注意的是返回結(jié)果,為URI對(duì)象,即網(wǎng)絡(luò)資源
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 ;
那么什么樣的接口適合用這種訪問(wèn)姿勢(shì)呢?
想一下我們一般登錄or注冊(cè)都是post請(qǐng)求,而這些操作完成之后呢?大部分都是跳轉(zhuǎn)到別的頁(yè)面去了,這種場(chǎng)景下,就可以使用 postForLocation 了,提交數(shù)據(jù),并獲取返回的URI,一個(gè)測(cè)試如下
首先mock一個(gè)后端接口
@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"; }
訪問(wèn)的測(cè)試用例,基本上和前面的一樣,沒(méi)有什么特別值得一說(shuō)的
@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); }
執(zhí)行結(jié)果如下
獲取到的就是302跳轉(zhuǎn)后端url,細(xì)心的朋友可能看到上面中文亂碼的問(wèn)題,如何解決呢?
一個(gè)簡(jiǎn)單的解決方案就是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. 小結(jié)
上面目前只給出了Get/Post兩種請(qǐng)求方式的基本使用方式,并沒(méi)有涉及到更高級(jí)的如添加請(qǐng)求頭,添加證書(shū),設(shè)置代理等,高級(jí)的使用篇等待下一篇出爐,下面小結(jié)一下上面的使用姿勢(shì)
1. Get請(qǐng)求
get請(qǐng)求中,參數(shù)一般都是帶在url上,對(duì)于參數(shù)的填充,有兩種方式,思路一致都是根據(jù)實(shí)際的參數(shù)來(lái)填充url中的占位符的內(nèi)容;根據(jù)返回結(jié)果,也有兩種方式,一個(gè)是只關(guān)心返回對(duì)象,另一個(gè)則包含了返回headers信心
參數(shù)填充
1、形如 http://story.hhui.top?id={0} 的 url
- 調(diào)用 getForObject(String url, Class<T> responseType, Object... uriVariables)
- 模板中的0,表示 uriVariables 數(shù)組中的第0個(gè), i,則表示第i個(gè)
- 如果沒(méi)有url參數(shù),也推薦用這個(gè)方法,不傳uriVariables即可
2、形如 http://story.hhui.top?id={id} 的 url
- 調(diào)用 getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
- map參數(shù)中的key,就是url參數(shù)中 {} 中的內(nèi)容
其實(shí)還有一種傳參方式,就是path參數(shù),填充方式和上面一樣,并沒(méi)有什么特殊的玩法,上面沒(méi)有特別列出
返回結(jié)果
- 直接獲取返回的數(shù)據(jù) getForObject
- 獲取待responseHeader的數(shù)據(jù) getForEntity
2. Post請(qǐng)求
- post請(qǐng)求的返回也有兩種,和上面一樣
- post請(qǐng)求,參數(shù)可以區(qū)分為表單提交和url參數(shù),其中url參數(shù)和前面的邏輯一致
- post表單參數(shù),請(qǐng)包裝在 MultiValueMap 中,作為第二個(gè)參數(shù) Request 來(lái)提交
- post的方法,還有一個(gè) postForLocation,返回的是一個(gè)URI對(duì)象,即適用于返回網(wǎng)絡(luò)資源的請(qǐng)求方式
3. 其他
最前面提了多點(diǎn)關(guān)于網(wǎng)絡(luò)請(qǐng)求的常見(jiàn)case,但是上面的介紹,明顯只處于基礎(chǔ)篇,我們還需要關(guān)注的有
- 如何設(shè)置請(qǐng)求頭?
- 有身份驗(yàn)證的請(qǐng)求,如何攜帶身份信息?
- 代理的設(shè)置
- 文件上傳可以怎么做?
- post提交json串(即RequestBody) 又可以怎么處理
上面可能還停留在應(yīng)用篇,對(duì)于源碼和實(shí)現(xiàn)有興趣的話,問(wèn)題也就來(lái)了
- RestTemplaet的實(shí)現(xiàn)原理是怎樣的
- 前面url參數(shù)的填充邏輯實(shí)現(xiàn)是否優(yōu)雅
- 返回的對(duì)象如何解析
- ....
小小的一個(gè)工具類(lèi),其實(shí)東西還挺多的,接下來(lái)的小目標(biāo),就是針對(duì)上面提出的點(diǎn),逐一進(jìn)行研究
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
clickhouse?批量插入數(shù)據(jù)及ClickHouse常用命令詳解
這篇文章主要介紹了clickhouse?批量插入數(shù)據(jù)及ClickHouse常用命令,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析
這篇文章主要介紹了spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10