Spring之RestTemplate使用小結

Spring之RestTemplate使用小結

做爲一個Java後端,須要經過HTTP請求其餘的網絡資源能夠說是一個比較常見的case了;通常怎麼作呢?java

可能大部分的小夥伴直接撈起Apache的HttpClient開始作,或者用其餘的一些知名的開源庫如OkHttp, 固然原生的HttpURLConnection也是沒問題的git

本篇博文則主要關注點放在Sprig的生態下,利用RestTemplate來發起Http請求的使用姿式github

I. RestTempalate 基本使用

0. 目標

在介紹如何使用RestTemplate以前,咱們先拋出一些小目標,至少須要知道經過RestTemplate能夠作些什麼,以及咱們要用它來幹些什麼web

簡單的給出了一下常見的問題以下spring

  • 普通的Get請求獲取返回數據,怎麼玩?
  • post提交表達的請求,如何處理
  • post請求中RequestBody的請求方式與普通的請求方式區別
  • https/http兩種訪問如何分別處理
  • 如何在請求中帶上指定的Header
  • 有跨域的問題麼?若是有怎麼解決
  • 有登陸驗證的請求,該怎麼辦,怎樣攜帶身份信息
  • 上傳文件能夠支持麼
  • 對於須要代理才能訪問的http資源,加代理的姿式是怎樣的

上面的問題比較多,目測不是一篇博文能夠弄完的,所以對這個拆解一下,本篇主要關注在RestTemplate的簡單Get/Post請求的使用方式上json

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請求

從上面的接口命名上,能夠看出可使用的有兩種方式 getForObjectgetForEntity,那麼這兩種有什麼區別?數組

  • 從接口的簽名上,能夠看出一個是直接返回預期的對象,一個則是將對象包裝到 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

上面執行後的截圖以下

3AD423F4F3C673F2D366772612B4355A.jpg

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封裝中,多了哪些東西,截圖以下

C2D317A154B33CF76F6DB438D1589754.jpg

從上面能夠看出,多了兩個東西

  • 一個返回的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);
}
複製代碼

執行結果以下

C4272685267804B4A1C1837EAEB76762.jpg

獲取到的就是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信心

參數填充

  1. 形如 http://story.hhui.top?id={0} 的 url
  • 調用 getForObject(String url, Class<T> responseType, Object... uriVariables)
  • 模板中的0,表示 uriVariables 數組中的第0個, i,則表示第i個
  • 若是沒有url參數,也推薦用這個方法,不傳uriVariables便可
  1. 形如 http://story.hhui.top?id={id} 的 url
  • 調用 getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
  • map參數中的key,就是url參數中 {} 中的內容

其實還有一種傳參方式,就是path參數,填充方式和上面同樣,並無什麼特殊的玩法,上面沒有特別列出

返回結果

  1. 直接獲取返回的數據 getForObject
  2. 獲取待responseHeader的數據 getForEntity

2. Post請求

  • post請求的返回也有兩種,和上面同樣
  • post請求,參數能夠區分爲表單提交和url參數,其中url參數和前面的邏輯一致
  • post表單參數,請包裝在 MultiValueMap 中,做爲第二個參數 Request 來提交
  • post的方法,還有一個 postForLocation,返回的是一個URI對象,即適用於返回網絡資源的請求方式

3. 其餘

最前面提了多點關於網絡請求的常見case,可是上面的介紹,明顯只處於基礎篇,咱們還須要關注的有

  • 如何設置請求頭?
  • 有身份驗證的請求,如何攜帶身份信息?
  • 代理的設置
  • 文件上傳能夠怎麼作?
  • post提交json串(即RequestBody) 又能夠怎麼處理

上面可能還停留在應用篇,對於源碼和實現有興趣的話,問題也就來了

  • RestTemplaet的實現原理是怎樣的
  • 前面url參數的填充邏輯實現是否優雅
  • 返回的對象如何解析
  • ....

小小的一個工具類,其實東西還挺多的,接下來的小目標,就是針對上面提出的點,逐一進行研究

III. 其餘

1. 一灰灰Blogliuyueyi.github.io/hexblog

一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

2. 聲明

盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

小灰灰Blog&公衆號

QrCode

知識星球

zhishi
相關文章
相關標籤/搜索