1. 引言
本快速教程演示瞭如何使用 Spring 的 RestTemplate 發送 JSON 內容進行 POST 請求。
2. 設置示例
讓我們首先添加一個簡單的 Person 模型類,用於表示要發佈的數據:
public class Person {
private Integer id;
private String name;
// standard constructor, getters, setters
}為了處理 Person 對象,我們將添加一個 PersonService 接口和實現,其中包含兩個方法:
public interface PersonService {
public Person saveUpdatePerson(Person person);
public Person findPersonById(Integer id);
}這些方法的實現將僅僅返回一個對象。我們這裏使用了一個佔位符的實現來專注於 Web 層。
3. REST API 設置
讓我們為我們的 Person 類定義一個簡單的 REST API:
@PostMapping(
value = "/createPerson", consumes = "application/json", produces = "application/json")
public Person createPerson(@RequestBody Person person) {
return personService.saveUpdatePerson(person);
}
@PostMapping(
value = "/updatePerson", consumes = "application/json", produces = "application/json")
public Person updatePerson(@RequestBody Person person, HttpServletResponse response) {
response.setHeader("Location", ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/findPerson/" + person.getId()).toUriString());
return personService.saveUpdatePerson(person);
}請記住,我們希望以 JSON 格式發佈數據。為此,我們為兩個方法都添加了 consumes 屬性到 @PostMapping 註解中,值為 “application/json”。
同樣,我們設置了 produces 屬性為 “application/json”,以便告訴 Spring 我們希望響應體為 JSON 格式。
我們使用 @RequestBody 註解對 person 參數進行了標註,這將在兩個方法中告訴 Spring,person 對象將被綁定到 HTTP 請求的 body。
最後,兩個方法都返回一個 Person 對象,該對象將被綁定到響應體。請注意,我們將使用 @RestController 註解來標註我們的 API 類,從而為所有 API 方法添加一個隱藏的 @ResponseBody 註解。
4. 使用 RestTemplate
現在我們可以編寫一些單元測試來測試我們的 Person REST API。在這裏,我們將嘗試通過使用 RestTemplate 中提供的 POST 方法(postForObject、postForEntity 和 postForLocation)向 Person API 發送 POST 請求。
在開始編寫單元測試之前,讓我們定義一個設置方法來初始化我們在所有單元測試方法中使用的對象:
@BeforeClass
public static void runBeforeAllTestMethods() {
createPersonUrl = "http://localhost:8080/spring-rest/createPerson";
updatePersonUrl = "http://localhost:8080/spring-rest/updatePerson";
restTemplate = new RestTemplate();
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
personJsonObject = new JSONObject();
personJsonObject.put("id", 1);
personJsonObject.put("name", "John");
}除了這種設置方法之外,請注意,我們將使用下面的映射器將 JSON 字符串轉換為單元測試中的 JSONNode 對象:
private final ObjectMapper objectMapper = new ObjectMapper();正如之前提到的,我們希望以JSON格式發佈數據。為了實現這一點,我們將向我們的請求添加一個Content-Type頭部,並使用APPLICATION_JSON媒體類型。
Spring的HttpHeaders類提供了訪問頭部的不同方法。在這裏,我們通過調用setContentType方法,將Content-Type頭部設置為application/json。 我們會將headers對象附加到我們的請求中。
4.1. 使用 postForObject 發送 JSON 數據
`RestTemplate 的 postForObject 方法通過將對象發佈到指定的 URI 模板創建一個新的資源。它將結果自動轉換為 responseType 參數中指定的類型後返回。
假設我們要向我們的 Person API 發送一個 POST 請求,以創建新的 Person 對象並將創建後的對象作為響應返回。
首先,我們將構建類型為 HttpEntity 的 request 對象,基於 personJsonObject 和包含 Content-Type 的頭部。 這允許 postForObject 方法發送 JSON 請求主體:
@Test
public void givenDataIsJson_whenDataIsPostedByPostForObject_thenResponseBodyIsNotNull()
throws IOException {
HttpEntity<String> request =
new HttpEntity<String>(personJsonObject.toString(), headers);
String personResultAsJsonStr =
restTemplate.postForObject(createPersonUrl, request, String.class);
JsonNode root = objectMapper.readTree(personResultAsJsonStr);
assertNotNull(personResultAsJsonStr);
assertNotNull(root);
assertNotNull(root.path("name").asText());
}postForObject() 方法返回響應體作為 String 類型。
我們還可以通過設置 responseType 參數將響應體返回為 Person 對象。
Person person = restTemplate.postForObject(createPersonUrl, request, Person.class);
assertNotNull(person);
assertNotNull(person.getName());實際上,我們的請求處理方法與 createPersonUrl URI 匹配時,會產生 JSON 格式的響應體。
但這並不會限制我們——postForObject 能夠自動將響應體轉換為請求的 Java 類型(例如,String、Person),具體類型由 responseType 參數指定。
4.2. 使用 postForEntity 發送 JSON 數據
與 postForObject() 相比,postForEntity() 返回響應為一個 ResponseEntity 對象。除此之外,這兩個方法的功能相同。
假設我們想要向我們的 Person API 發送一個 POST 請求,以創建新的 Person 對象,並將響應返回為 ResponseEntity 對象。
我們可以使用 postForEntity 方法來實現這一點:
@Test
public void givenDataIsJson_whenDataIsPostedByPostForEntity_thenResponseBodyIsNotNull()
throws IOException {
HttpEntity<String> request =
new HttpEntity<String>(personJsonObject.toString(), headers);
ResponseEntity<String> responseEntityStr = restTemplate.
postForEntity(createPersonUrl, request, String.class);
JsonNode root = objectMapper.readTree(responseEntityStr.getBody());
assertNotNull(responseEntityStr.getBody());
assertNotNull(root.path("name").asText());
}類似於 postForObject,postForEntity 具有 responseType 參數,用於將響應體轉換為請求的 Java 類型。
在這裏,我們能夠將響應體返回為 ResponseEntity<String> 對象。
通過將 responseType 參數設置為 Person.class,我們也可以將響應返回為 ResponseEntity<Person> 對象:
ResponseEntity<Person> responseEntityPerson = restTemplate.
postForEntity(createPersonUrl, request, Person.class);
assertNotNull(responseEntityPerson.getBody());
assertNotNull(responseEntityPerson.getBody().getName());4.3. 使用 postForLocation</h3
類似於 postForObject 和 postForEntity 方法,postForLocation 也通過將提供的對象發佈到指定的 URI 創建一個新的資源。唯一的區別在於它返回 Location 頭的的值。
請記住,我們之前已經看到了如何在上面的 updatePerson REST API 方法中設置響應的 Location 頭部。
response.setHeader("Location", ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/findPerson/" + person.getId()).toUriString());現在讓我們假設我們想要在更新後返回更新後的 person 對象所對應的 Location 響應頭。
我們可以通過使用 postForLocation 方法來實現這一點:
@Test
public void givenDataIsJson_whenDataIsPostedByPostForLocation_thenResponseBodyIsTheLocationHeader()
throws JsonProcessingException {
HttpEntity<String> request = new HttpEntity<String>(personJsonObject.toString(), headers);
URI locationHeader = restTemplate.postForLocation(updatePersonUrl, request);
assertNotNull(locationHeader);
}5. 處理JSON POST請求中的字符編碼
Spring Boot 使用 <em>server.servlet.encoding.charset</em> 屬性來配置服務器的默認編碼。 此外,如果 <em>server.servlet.encoding.charset</em> 屬性缺失,則默認使用 <em>UTF-8</em>。
然而,當應用程序使用非UTF-8編碼,例如 <em>ISO-8859-1</em> 時,服務器可能會錯誤地解釋傳入的請求。 在這種情況下,我們必須通過顯式設置 <em>Content-Type</em> 頭部來覆蓋編碼。 讓我們看看如何使用 <em>RestTemplate</em> 來實現。
5.1. 模擬場景
首先,我們需要在應用程序啓動時,將 <em >server.servlet.encoding.charset</em> 屬性設置為 <em >ISO-8859-1</em>,從而模擬該場景:
SpringApplication app = new SpringApplication(RestTemplateApplication.class);
app.setDefaultProperties(
Collections.singletonMap("server.servlet.encoding.charset", "ISO-8859-1"));
app.run(args);現在,讓我們創建一個 Person 類的實例,在名稱中使用日語字符:
Person japanese = new Person(100, "関連当");接下來,我們創建一個 request 對象,並使用 HttpHeaders 的實例:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Person> request = new HttpEntity<>(japanese, headers);
接下來,我們使用一個 RestTemplate 實例來向 createPersonUrl 端點發送一個 POST 請求:
Person person = restTemplate.postForObject(createPersonUrl, request, Person.class);最後,讓我們驗證結果中的 person 對象是否與請求中使用的名稱相同:
assertNotNull(person);
assertNotEquals("関連当", person.getName());此問題發生的原因是,日語字符未被 ISO-8859-1 編碼處理,而我們的應用程序使用該編碼。
5.2. 處理字符編碼
我們可以通過將 請求的字符編碼處理,設置 Content-type 頭部,指定 UTF-8 編碼來實現:
HttpHeaders headers = new HttpHeaders();
headers.set("Content-type", "application/json;charset=UTF-8");
HttpEntity<Person> request = new HttpEntity<>(japanese, headers);現在,讓我們使用 restTemplate 向 createPersonUrl 端點發送 POST 請求:
Person person = restTemplate.postForObject(createPersonUrl, request, Person.class);最後,我們可以驗證結果中 人 的名稱與預期名稱相同:
assertNotNull(person);
assertEquals("関連当", person.getName());看起來我們搞定了!
6. 結論
在本文中,我們探討了如何使用 <em >RestTemplate</em> 發送帶有 JSON 數據的 POST 請求。此外,我們還學習了在發送 POST 請求時如何處理字符編碼。