天天看點

Springboot — RestTemplate詳解

RestTemplate是Spring提供的用于通路Rest服務的用戶端,RestTemplate提供了多種便捷通路遠端Http服務的方法,能夠大大提高用戶端的編寫效率。

我之前的HTTP開發是用apache的HttpClient開發,代碼複雜,還得操心資源回收等。代碼很複雜,備援代碼多,稍微截個圖,這是我封裝好的一個post請求工具:

Springboot — RestTemplate詳解

本教程将帶領大家實作Spring生态内RestTemplate的Get請求和Post請求還有exchange指定請求類型的實踐和RestTemplate核心方法源碼的分析,看完你就會用優雅的方式來發HTTP請求。

1.簡述RestTemplate

是Spring用于同步client端的核心類,簡化了與http服務的通信,并滿足RestFul原則,程式代碼可以給它提供URL,并提取結果。預設情況下,RestTemplate預設依賴jdk的HTTP連接配接工具。當然你也可以 通過setRequestFactory屬性切換到不同的HTTP源,比如Apache HttpComponents、Netty和OkHttp。

RestTemplate能大幅簡化了送出表單資料的難度,并且附帶了自動轉換JSON資料的功能,但隻有了解了HttpEntity的組成結構(header與body),且了解了與uriVariables之間的差異,才能真正掌握其用法。這一點在Post請求更加突出,下面會介紹到。

該類的入口主要是根據HTTP的六個方法制定:

Springboot — RestTemplate詳解

此外,exchange和excute可以通用上述方法。

在内部,RestTemplate預設使用HttpMessageConverter執行個體将HTTP消息轉換成POJO或者從POJO轉換成HTTP消息。預設情況下會注冊主mime類型的轉換器,但也可以通過setMessageConverters注冊其他的轉換器。

其實這點在使用的時候是察覺不到的,很多方法有一個responseType 參數,它讓你傳入一個響應體所映射成的對象,然後底層用HttpMessageConverter将其做映射

1

2

HttpMessageConverterExtractor<T> responseExtractor =

new

HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);

 

HttpMessageConverter.java源碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public

interface

HttpMessageConverter<T> {

//訓示此轉換器是否可以讀取給定的類。

boolean

canRead(Class<?> clazz, 

@Nullable

MediaType mediaType);

//訓示此轉換器是否可以寫給定的類。

boolean

canWrite(Class<?> clazz, 

@Nullable

MediaType mediaType);

//傳回List<MediaType>

List<MediaType> getSupportedMediaTypes();

//讀取一個inputMessage

T read(Class<? 

extends

T> clazz, HttpInputMessage inputMessage)

throws

IOException, HttpMessageNotReadableException;

//往output message寫一個Object

void

write(T t, 

@Nullable

MediaType contentType, HttpOutputMessage outputMessage)

throws

IOException, HttpMessageNotWritableException;

}

  

在内部,RestTemplate預設使用SimpleClientHttpRequestFactory和DefaultResponseErrorHandler來分别處理HTTP的建立和錯誤,但也可以通過setRequestFactory和setErrorHandler來覆寫。

2.get請求實踐

2.1.getForObject()方法

1

2

3

public

<T> T getForObject(String url, Class<T> responseType, Object... uriVariables){}

public

<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

public

<T> T getForObject(URI url, Class<T> responseType)

getForObject()其實比getForEntity()多包含了将HTTP轉成POJO的功能,但是getForObject沒有處理response的能力。因為它拿到手的就是成型的pojo。省略了很多response的資訊。

2.1.1 POJO:

1

2

3

4

5

6

7

8

9

10

11

12

13

public

class

Notice {

private

int

status;

private

Object msg;

private

List<DataBean> data;

}

public

class

DataBean {

private

int

noticeId;

private

String noticeTitle;

private

Object noticeImg;

private

long

noticeCreateTime;

private

long

noticeUpdateTime;

private

String noticeContent;

}

  

示例:2.1.2 不帶參的get請求

1

2

3

4

5

6

7

8

9

10

@Test

public

void

restTemplateGetTest(){

RestTemplate restTemplate = 

new

RestTemplate();

Notice notice = restTemplate.getForObject(

"http://xxx.top/notice/list/1/5"

, Notice.

class

);

System.out.println(notice);

}

  

控制台列印:

1

2

3

4

5

6

7

INFO 

19076

--- [           main] c.w.s.c.w.c.HelloControllerTest         

: Started HelloControllerTest in 

5.532

seconds (JVM running 

for

7.233

)

Notice{status=

200

, msg=

null

, data=[DataBean{noticeId=

21

, noticeTitle=

'aaa'

, noticeImg=

null

,

noticeCreateTime=

1525292723000

, noticeUpdateTime=

1525292723000

, noticeContent=

'<p>aaa</p>'

},

DataBean{noticeId=

20

, noticeTitle=

'ahaha'

, noticeImg=

null

, noticeCreateTime=

1525291492000

,

noticeUpdateTime=

1525291492000

, noticeContent=

'<p>ah.......'

  

示例:2.1.3 帶參數的get請求1

Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{1}/{2}"
                , Notice.class,1,5);
           

明眼人一眼能看出是用了占位符{1}。

示例:2.1.4 帶參數的get請求2

1

2

3

4

5

Map<String,String> map = 

new

HashMap();

map.put(

"start"

,

"1"

);

map.put(

"page"

,

"5"

);

Notice notice = restTemplate.getForObject(

"http://fantj.top/notice/list/"

, Notice.

class

,map);

  

明眼人一看就是利用map裝載參數,不過它預設解析的是PathVariable的url形式。

2.2 getForEntity()方法

1

2

3

public

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}

public

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}

public

<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}

與getForObject()方法不同的是傳回的是ResponseEntity對象,如果需要轉換成pojo,還需要json工具類的引入,這個按個人喜好用。不會解析json的可以百度FastJson或者Jackson等工具類。然後我們就研究一下ResponseEntity下面有啥方法。

ResponseEntity、HttpStatus、BodyBuilder結構

ResponseEntity.java

1

2

3

4

5

6

7

8

9

public

HttpStatus getStatusCode(){}

public

int

getStatusCodeValue(){}

public

boolean

equals(

@Nullable

Object other) {}

public

String toString() {}

public

static

BodyBuilder status(HttpStatus status) {}

public

static

BodyBuilder ok() {}

public

static

<T> ResponseEntity<T> ok(T body) {}

public

static

BodyBuilder created(URI location) {}

...

  

HttpStatus.java

1

2

3

4

5

6

7

8

public

enum

HttpStatus {

public

boolean

is1xxInformational() {}

public

boolean

is2xxSuccessful() {}

public

boolean

is3xxRedirection() {}

public

boolean

is4xxClientError() {}

public

boolean

is5xxServerError() {}

public

boolean

isError() {}

}

  

BodyBuilder.java

1

2

3

4

5

6

7

8

public

interface

BodyBuilder 

extends

HeadersBuilder<BodyBuilder> {

//設定正文的長度,以位元組為機關,由Content-Length标頭

BodyBuilder contentLength(

long

contentLength);

//設定body的MediaType 類型

BodyBuilder contentType(MediaType contentType);

//設定響應實體的主體并傳回它。

<T> ResponseEntity<T> body(

@Nullable

T body);

可以看出來,ResponseEntity包含了HttpStatus和BodyBuilder的這些資訊,這更友善我們處理response原生的東西。

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@Test

public

void

rtGetEntity(){

RestTemplate restTemplate = 

new

RestTemplate();

ResponseEntity<Notice> entity = restTemplate.getForEntity(

"http://fantj.top/notice/list/1/5"

, Notice.

class

);

HttpStatus statusCode = entity.getStatusCode();

System.out.println(

"statusCode.is2xxSuccessful()"

+statusCode.is2xxSuccessful());

Notice body = entity.getBody();

System.out.println(

"entity.getBody()"

+body);

ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode);

status.contentLength(

100

);

status.body(

"我在這裡添加一句話"

);

ResponseEntity<Class<Notice>> body1 = status.body(Notice.

class

);

Class<Notice> body2 = body1.getBody();

System.out.println(

"body1.toString()"

+body1.toString());

}

statusCode.is2xxSuccessful()

true

entity.getBody()Notice{status=

200

, msg=

null

, data=[DataBean{noticeId=

21

, noticeTitle=

'aaa'

, ...

body1.toString()<

200

OK,

class

com.waylau.spring.cloud.weather.pojo.Notice,{Content-Length=[

100

]}>

  

當然,還有getHeaders()等方法沒有舉例。

3. post請求實踐

同樣的,post請求也有postForObject和postForEntity。

1

2

3

4

5

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 {}

  

示例

我用一個驗證郵箱的接口來測試。

1

2

3

4

5

6

7

8

9

10

11

12

13

@Test

public

void

rtPostObject(){

RestTemplate restTemplate = 

new

RestTemplate();

String url = 

"http://47.xxx.xxx.96/register/checkEmail"

;

HttpHeaders headers = 

new

HttpHeaders();

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> map= 

new

LinkedMultiValueMap<>();

map.add(

"email"

"[email protected]"

);

HttpEntity<MultiValueMap<String, String>> request = 

new

HttpEntity<>(map, headers);

ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.

class

);

System.out.println(response.getBody());

}

  

執行結果:

{"status":500,"msg":"該郵箱已被注冊","data":null}
           
Springboot — RestTemplate詳解

代碼中,MultiValueMap是Map的一個子類,它的一個key可以存儲多個value,簡單的看下這個接口:

1

public

interface

MultiValueMap<K, V> 

extends

Map<K, List<V>> {...}

為什麼用MultiValueMap?因為HttpEntity接受的request類型是它。

1

2

public

HttpEntity(

@Nullable

T body, 

@Nullable

MultiValueMap<String, String> headers){}

//我這裡隻展示它的一個construct,從它可以看到我們傳入的map是請求體,headers是請求頭。

  

為什麼用HttpEntity是因為restTemplate.postForEntity方法雖然表面上接收的request是@Nullable Object request類型,但是你追蹤下去會發現,這個request是用HttpEntity來解析。核心代碼如下:

1

2

3

4

5

6

7

if

(requestBody 

instanceof

HttpEntity) {

this

.requestEntity = (HttpEntity<?>) requestBody;

}

else

if

(requestBody != 

null

) {

this

.requestEntity = 

new

HttpEntity<>(requestBody);

}

else

{

this

.requestEntity = HttpEntity.EMPTY;

}

  

我曾嘗試用map來傳遞參數,編譯不會報錯,但是執行不了,是無效的url request請求(400 ERROR)。其實這樣的請求方式已經滿足post請求了,cookie也是屬于header的一部分。可以按需求設定請求頭和請求體。其它方法與之類似。

4.使用exchange指定調用方式

exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之處在于它可以指定請求的HTTP類型。

Springboot — RestTemplate詳解

但是你會發現exchange的方法中似乎都有@Nullable HttpEntity requestEntity這個參數,這就意味着我們至少要用HttpEntity來傳遞這個請求體,之前說過源碼是以建議就使用HttpEntity提高性能。

示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Test

public

void

rtExchangeTest() 

throws

JSONException {

RestTemplate restTemplate = 

new

RestTemplate();

String url = 

"http://xxx.top/notice/list"

;

HttpHeaders headers = 

new

HttpHeaders();

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

JSONObject jsonObj = 

new

JSONObject();

jsonObj.put(

"start"

,

1

);

jsonObj.put(

"page"

,

5

);

HttpEntity<String> entity = 

new

HttpEntity<>(jsonObj.toString(), headers);

ResponseEntity<JSONObject> exchange = restTemplate.exchange(url,

HttpMethod.GET, entity, JSONObject.

class

);

System.out.println(exchange.getBody());

}

這次可以看到,我使用了JSONObject對象傳入和傳回。

當然,HttpMethod方法還有很多,用法類似。

5.excute()指定調用方式

excute()的用法與exchange()大同小異了,它同樣可以指定不同的HttpMethod,不同的是它傳回的對象是響應體所映射成的對象,而不是ResponseEntity。

需要強調的是,execute()方法是以上所有方法的底層調用。随便看一個:

1

2

3

4

5

6

7

8

9

10

@Override

@Nullable

public

<T> T postForObject(String url, 

@Nullable

Object request, Class<T> responseType, Map<String, ?> uriVariables)

throws

RestClientException {

RequestCallback requestCallback = httpEntityCallback(request, responseType);

HttpMessageConverterExtractor<T> responseExtractor =

new

HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);

return

execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);

}