
https://unsplash.com/photos/LmyPLbbUWhA
身為後端工程師,開發 RESTful API 給前端呼叫是相當常見的事,然而我們有時也會想使用其他第三方 API。例如想實作展示天氣預報的功能,可使用「氣象開放資料平台」的 API。想取得外幣匯率資料,某些銀行有提供 API。或者工作時有遇到系統的某個功能,其實是被獨立做成一個服務,運行在別的 server,我們也可能需要存取它。
若要使用 Java 建立網路連線,其中一種做法是使用 java.net 套件下的「HttpURLConnection」。但這會寫出繁瑣的程式碼,非常不方便。本文將介紹 Spring Boot 封裝好的 RestTemplate,它讓我們能以簡單的操作方式,發送請求與接收回應。而在第 22.2 課,則提供串接第三方 API 的實例。
一、準備 Spring Boot 專案
本文使用的 Java 版本為 17(zulu-17),Spring Boot 版本為 3.1.3。
筆者會使用測試程式的做法,來展示 RestTemplate 的使用方式。
public class ApplicationTests { | |
private final RestTemplate restTemplate = new RestTemplate(); | |
// TODO | |
} |
二、認識 Reqres 服務
為了透過 RestTemplate 來發送請求與接收回應,勢必要有一個伺服器讓我們串接。「Reqres」是一個免費的服務,它提供各種 RESTful API,且會回傳假資料。

從上圖中,可看到「GET https://reqres.in/api/users/2」這個 API 的用法。在串接外部服務時,我們必須注意其介面如何定義,比如 request 與 response 的欄位名稱、支援的 query string、HTTP 狀態碼等等。
三、發送 GET 請求
根據上一節的圖,我們知道該 API 的 response body,因此要先準備好對應的類別與欄位去接收。這個道理就像在 controller 設計 API,也會用專門的類別去接收 request body 一樣。
public class GetUserResponse { | |
private UserResponse data; | |
// getter, setter ... | |
} |
import com.fasterxml.jackson.annotation.JsonProperty; | |
public class UserResponse { | |
private int id; | |
private String email; | |
private String avatar; | |
@JsonProperty("first_name") | |
private String firstName; | |
@JsonProperty("last_name") | |
private String lastName; | |
// getter, setter ... | |
} |
若 response 的欄位名稱不喜歡,想換另一個名字,可利用 Jackson 提供的「@JsonProperty」標記來對接欄位。
下面是使用 RestTemplate 發送 GET 請求的範例程式。
public class ApplicationTests { | |
private final RestTemplate restTemplate = new RestTemplate(); | |
@Test | |
public void testGetForSingleData() { | |
// 發送請求並取得回應 | |
ResponseEntity<GetUserResponse> resEntity = restTemplate.getForEntity( | |
"https://reqres.in/api/users/1", | |
GetUserResponse.class | |
); | |
// 確認 HTTP 狀態碼 | |
assertEquals(HttpStatus.OK, resEntity.getStatusCode()); | |
// 確認 response header | |
assertNotNull(resEntity.getHeaders().getContentType()); | |
assertEquals("application/json;charset=utf-8", resEntity.getHeaders().getContentType().toString()); | |
// 確認 response body | |
GetUserResponse resBody = resEntity.getBody(); | |
assertNotNull(resBody); | |
UserResponse data = resBody.getData(); | |
assertEquals(1, data.getId()); | |
assertEquals("george.bluth@reqres.in", data.getEmail()); | |
assertEquals("George", data.getFirstName()); | |
assertEquals("Bluth", data.getLastName()); | |
assertEquals("https://reqres.in/img/faces/1-image.jpg", data.getAvatar()); | |
} | |
} |
此處呼叫了「getForEntity」方法,並傳入兩個參數。第一個是 API 路徑的 url,第二個是 response 要轉換成的類別。
而方法的回傳值型態,是帶泛型的「ResponseEntity」。它除了 body,還帶有 HTTP 狀態碼及 header 資料,在此一併做驗證。
這裡的範例是取得一筆使用者資料。至於另一支取得多筆資料的 API,筆者留到本文第五節再示範。
四、發送 POST 請求
首先一樣先觀察 request 與 response 的內容有哪些欄位。

接著再準備對應的類別與欄位,以發送請求與接收回應。
public class CreateUserRequest { | |
private String name; | |
private String job; | |
public static CreateUserRequest of(String name, String job) { | |
var req = new CreateUserRequest(); | |
req.name = name; | |
req.job = job; | |
return req; | |
} | |
// getter, setter ... | |
} |
import java.time.ZonedDateTime; | |
public class CreateUserResponse { | |
private String id; | |
private String name; | |
private String job; | |
private ZonedDateTime createdAt; | |
// getter, setter ... | |
} |
發送 POST 請求的做法與 GET 大同小異。除了有名稱類似的「postForEntity」方法可用,也能選擇下面範例的另一個 method。
public class ApplicationTests { | |
private final RestTemplate restTemplate = new RestTemplate(); | |
// ... | |
@Test | |
public void testPostData() { | |
CreateUserRequest createReq = CreateUserRequest.of("morpheus", "leader"); | |
CreateUserResponse createRes = restTemplate.postForObject( | |
"https://reqres.in/api/users", | |
createReq, | |
CreateUserResponse.class | |
); | |
assertNotNull(createRes); | |
assertEquals(createReq.getName(), createRes.getName()); | |
assertEquals(createReq.getJob(), createRes.getJob()); | |
assertNotNull(createRes.getId()); | |
assertNotNull(createRes.getCreatedAt()); | |
} | |
} |
此處呼叫了「postForObject」方法。與 postForEntity 的差異,在於前者只會回傳 response body 的物件,所以不會有 HTTP 狀態碼及 header 等資料。
五、搭配字串模板
關於前面兩節所介紹的 get 與 post 系列方法,還可傳入一個名為「uriVariables」的參數。它能讓我們搭配「字串模板」來組成 API 路徑。
(一)用於 API 路徑
舉例來說,取得 id 為 1 的使用者時,getForObject 方法的參數可以這麼傳入:
GetUserResponse res = restTemplate.getForObject( | |
"https://reqres.in/api/users/{id}", | |
GetUserResponse.class, | |
Map.of("id", 1) | |
); |
API 路徑的 url 寫了「{id}」當作預留位置(placeholder),而 Map 參數會提供對應的值。
(二)用於 query string
若想在 url 添加 query string,這種做法也是很方便的。
接下來以取得多個使用者的 API 來做示範。以下是 API 的定義,以及 RestTemplate 的使用範例。

該 API 支援分頁(pagination),故可傳入「per_page」與「page」的 query string。分別代表「每頁幾筆」和「第幾頁」。
根據 API response 內容,建立了對應的類別。
public class GetUserListResponse { | |
private List<UserResponse> data; | |
// getter, setter ... | |
} |
而以下是 RestTemplate 的使用範例。
@Test | |
public void testGetForMultipleData() { | |
GetUserListResponse res = restTemplate.getForObject( | |
"https://reqres.in/api/users?page={page}&per_page={per_page}", | |
GetUserListResponse.class, | |
Map.of("page", 2, "per_page", 6) | |
); | |
assertNotNull(res); | |
List<UserResponse> users = res.getData(); | |
assertEquals(6, users.size()); | |
assertEquals(7, users.get(0).getId()); | |
assertEquals(8, users.get(1).getId()); | |
assertEquals(9, users.get(2).getId()); | |
assertEquals(10, users.get(3).getId()); | |
assertEquals(11, users.get(4).getId()); | |
assertEquals(12, users.get(5).getId()); | |
} |
六、泛用的 exchange 方法
以下列出 RestTemplate 的數個 method,分別對應各種 HTTP 方法。讀者可自行探索它們接收的參數。
- ResponseEntity<T> getForEntity
- T getForObject
- ResponseEntity<T> postForEntity
- T postForObject
- void put
- void delete
- T patchForObject
然而 PUT、DELETE 與 PATCH 並不如 GET 與 POST 那麼全面,畢竟無法得到 response 資訊或 body。
另外還有一個重點,那就是在發送請求時,這些 method 無法給予 request header。要知道有些外部的 API,可能會要求呼叫方在「Authorization」這個 header 攜帶 access token 之類的值,來表明自身身份。
為了因應這些問題,我們可選擇泛用的「exchange」方法,才能做到客製化。以第四節的 POST 請求為例,用 exchange 方法來寫,會類似這樣子。
@Test | |
public void testPostData() { | |
HttpHeaders headers = new HttpHeaders(); | |
headers.setContentType(MediaType.APPLICATION_JSON); | |
headers.setBearerAuth("your_access_token"); | |
CreateUserRequest createReq = CreateUserRequest.of("morpheus", "leader"); | |
HttpEntity<CreateUserRequest> httpEntity = new HttpEntity<>(createReq, headers); | |
ResponseEntity<CreateUserResponse> resEntity = restTemplate.exchange( | |
"https://reqres.in/api/users", | |
HttpMethod.POST, | |
httpEntity, | |
CreateUserResponse.class | |
); | |
CreateUserResponse createRes = resEntity.getBody(); | |
// ... | |
} |
上面的 exchange 方法參數,接收了 HTTP 方法,以及由 header 與 request body 組成的「HttpEntity」物件。
七、接收 JSON array 回應
Reqres 所回傳的 response body 都是 JSON object,結構如下。
{ "foo": "...", "bar": [ { "...": "..." } ] }
然而其他服務的 API 也許會回傳 JSON array,結構如下。
[ { "foo": "...", "bar": "..." }, { "foo": "...", "bar": "..." } ]
針對 JSON array 的回應,使用 RestTemplate 時,response body 的類別(Class<T> responseType 參數)可使用「類別[].class」的寫法,例如「UserResponse[].class」,以陣列來接收資料。
若讀者更偏好直接用 List,則可提供「ParameterizedTypeReference<T> responseType」這個參數。兩者用途相同,具體寫法如下。
List<UserResponse> users = restTemplate.exchange( | |
"your_url", | |
HttpMethod.GET, | |
HttpEntity.EMPTY, | |
new ParameterizedTypeReference<List<YourResponse>>() {} | |
); |
「ParameterizedTypeReference」能傳入一個泛型類別,而這個類別還可以繼續包泛型。如此就能直接以指定類別的 List 來接收 response body 了。
本文的完成專案:
https://github.com/ntub46010/SpringBootTutorial/tree/Ch22-1
留言
張貼留言