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 屬性,其值為 “application/json”
同樣,我們設置了 produces 屬性為 “application/json”,以便告訴 Spring 我們希望響應體為 JSON 格式。
我們使用 @RequestBody 註解對 person 參數進行了標註,以便在兩個方法中,Spring 將 person 對象綁定到 HTTP 請求的 body 中。
最後,兩個方法返回一個 Person 對象,該對象將綁定到響應體中。請注意,我們將使用 @RestController 註解來標註我們的 API 類,以便為所有 API 方法添加一個隱藏的 @ResponseBody 註解。
4. Using RestTemplate
Now we can write a few unit tests to test our Person REST API. Here, we’ll try to send POST requests to the Person API by using the POST methods provided by the RestTemplate: postForObject, postForEntity, and postForLocation.
Before we start to implement our unit tests, let’s define a setup method to initialize the objects that we’ll use in all our unit test methods:
@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");
}
Besides this setup method, note that we’ll refer to the following mapper to convert the JSON String to a JSONNode object in our unit tests:
private final ObjectMapper objectMapper = new ObjectMapper();
As previously mentioned, we want to post the data in JSON format. To achieve this, we’ll add a Content-Type header to our request with the APPLICATION_JSON media type.
Spring’s HttpHeaders class provides different methods to access the headers. Here, we set the Content-Type header to application/json by calling the setContentType method. We’ll attach the headers object to our requests.
4.1. Posting JSON With postForObject
RestTemplate‘s postForObject method creates a new resource by posting an object to the given URI template. It returns the result as automatically converted to the type specified in the responseType parameter.
Let’s say that we want to make a POST request to our Person API to create a new Person object and return this newly created object in the response.
First, we’ll build the request object of type HttpEntity based on the personJsonObject and the headers containing the Content-Type. This allows the postForObject method to send a JSON request body:
@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());
}
The postForObject() method returns the response body as a String type.
We can also return the response as a Person object by setting the responseType parameter:
Person person = restTemplate.postForObject(createPersonUrl, request, Person.class);
assertNotNull(person);
assertNotNull(person.getName());
Actually, our request handler method matching with the createPersonUrl URI produces the response body in JSON format.
But this is not a limitation for us — postForObject is able to automatically convert the response body into the requested Java type (e.g. String, Person) specified in the responseType parameter.
4.2. Posting JSON With postForEntity
Compared to postForObject(), postForEntity() returns the response as a ResponseEntity object. Other than that, both methods do the same job.
Let’s say that we want to make a POST request to our Person API to create a new Person object and return the response as a ResponseEntity.
We can make use of the postForEntity method to implement this:
@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());
}
Similar to the postForObject, postForEntity has the responseType parameter to convert the response body to the requested Java type.
Here, we were able to return the response body as a ResponseEntity<String>.
We can also return the response as a ResponseEntity<Person> object by setting the responseType parameter to Person.class:
ResponseEntity<Person> responseEntityPerson = restTemplate.
postForEntity(createPersonUrl, request, Person.class);
assertNotNull(responseEntityPerson.getBody());
assertNotNull(responseEntityPerson.getBody().getName());
4.3. Posting JSON With postForLocation
Similar to the postForObject and postForEntity methods, postForLocation also creates a new resource by posting the given object to the given URI. The only difference is that it returns the value of the Location header.
Remember, we already saw how to set the Location header of a response in our updatePerson REST API method above:
response.setHeader("Location", ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/findPerson/" + person.getId()).toUriString());
Now let’s imagine that we want to return the Location header of the response after updating the person object we posted.
We can implement this by using the postForLocation method:
@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 使用 server.servlet.encoding.charset 屬性來配置服務器的默認編碼。 此外,如果 server.servlet.encoding.charset 屬性缺失,它使用 UTF-8 作為默認值。
但是,當應用程序使用非 UTF-8 編碼(例如 ISO-8859-1)時,服務器可能會誤解傳入的請求。 在這種情況下,我們必須通過顯式設置 Content-Type 標頭來覆蓋編碼。 讓我們看看如何使用 RestTemplate 來做到這一點。
5.1. 模擬場景
首先,我們必須 模擬場景,在應用程序啓動期間將 server.servlet.encoding.charset 屬性設置為 ISO-8859-1:
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);
最後,我們可以驗證結果的 person 是否與預期的一樣名稱:
assertNotNull(person);
assertEquals("関連當", person.getName());
看起來我們已經搞定了這個問題!
6. 結論
在本文中,我們探討了如何使用 RestTemplate 發送帶有 JSON 數據的 POST 請求。此外,我們還學習了在發送 POST 請求時處理字符編碼的方法。