1. 概述
在本教程中,我們將學習如何在 Spring 中配置 HttpMessageConverters。
簡單來説,我們可以使用消息轉換器將 Java 對象轉換為和從 JSON 和 XML 通過 HTTP 進行轉換。
2. 基礎知識
2.1. 啓用 Web MVC
首先,Web 應用程序需要配置 Spring MVC 支持。 使用 @EnableWebMvc 註解,這是一種方便且高度可定製的方式:
@EnableWebMvc
@Configuration
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
// ...
}請注意,該類實現了 WebMvcConfigurer,這將允許我們使用自定義的 Http 轉換器替換默認列表。
2.2. 默認消息轉換器
默認情況下,以下 <HttpMessageConverter> 實例已預先啓用:
- ByteArrayHttpMessageConverter – 轉換字節數組
- StringHttpMessageConverter – 轉換字符串
- ResourceHttpMessageConverter – 轉換 org.springframework.core.io.Resource 用於任何類型的八進制流
- SourceHttpMessageConverter – 轉換 javax.xml.transform.Source
- FormHttpMessageConverter – 將表單數據轉換為/從 MultiValueMap<String, String>
- Jaxb2RootElementHttpMessageConverter – 將 Java 對象轉換為/從 XML (僅當 JAXB2 在類路徑上存在時添加)
- MappingJackson2HttpMessageConverter – 轉換 JSON (僅當 Jackson 2 在類路徑上存在時添加)
- MappingJackson2HttpMessageConverter – 轉換 JSON (僅當 Jackson 在類路徑上存在時添加)
- AtomFeedHttpMessageConverter – 轉換 Atom feeds (僅當 Rome 在類路徑上存在時添加)
- RssChannelHttpMessageConverter – 轉換 RSS feeds (僅當 Rome 在類路徑上存在時添加)
3. 客户端-服務器通信 – JSON 僅限
3.1. 高級內容協商
每個 <em>HttpMessageConverter</em> 實現都有一個或多個關聯的 MIME 類型。
當接收到新的請求時,`Spring 將使用 “Accept” 頭來確定需要響應的媒體類型。
它會嘗試找到一個能夠處理該特定媒體類型的註冊轉換器。 最終,它將使用此轉換器來轉換實體並返回響應。
對於接收包含 JSON 信息的請求,該過程類似。 框架將 `使用 “Content-Type” 頭來確定請求體中的媒體類型。
然後它會搜索一個 <em style="line-height: 1.5em">HttpMessageConverter</em> 能夠將客户端發送的體轉換為 Java 對象。
讓我們通過一個快速示例來闡明這一點:
- 客户端向
<em>/foos,</em>發送一個 GET 請求,<em>Accept</em>頭設置為<em>application/json</em>,以獲取所有<em>Foo</em>資源作為 JSON。 <em>Foo</em>Spring 控制器被命中,並返回相應的<em>Foo</em>Java 實體。- 然後 Spring 使用 Jackson 消息轉換器將實體轉換為 JSON。
現在讓我們看看這個過程的具體細節,以及如何利用 @ResponseBody 和 @RequestBody 註解。
3.2. @ResponseBody
@ResponseBody 在 Controller 方法上指示 Spring 將 方法的返回值直接序列化到 HTTP Response 的 body 中。 如前所述,“Accept” 頭部由客户端指定,將用於選擇用於將實體進行轉換的適當 Http Converter。
@GetMapping("/{id}")
public @ResponseBody Foo findById(@PathVariable long id) {
return fooService.findById(id);
}現在客户端將指定“Accept”頭為請求中的 application/json (例如,使用 curl 命令):
curl --header "Accept: application/json"
http://localhost:8080/spring-boot-rest/foos/1Foo 級聯(Foo Pattern):
public class Foo {
private long id;
private String name;
}以及 HTTP 響應體:
{
"id": 1,
"name": "Paul",
}<div>
</div>
3.3. <em style="line-height: 1.5em">@RequestBody</em>
我們可以使用 <em style="line-height: 1.5em">@RequestBody</em> 註解,在 Controller 方法的參數上指示 HTTP 請求的主體內容將被反序列化為指定的 Java 實體。 Spring 將使用客户端請求的“Content-Type”頭信息來確定合適的轉換器。
@PutMapping("/{id}")
public @ResponseBody void update(@RequestBody Foo foo, @PathVariable String id) {
fooService.update(foo);
}接下來,我們將使用 JSON 對象來消費它,指定“Content-Type”為 application/json:
curl -i -X PUT -H "Content-Type: application/json"
-d '{"id":"83","name":"klik"}' http://localhost:8080/spring-boot-rest/foos/1我們將會收到一個 200 OK 狀態碼,表示成功響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Fri, 10 Jan 2014 11:18:54 GMT4. 自定義消息轉換器配置
我們還可以通過實現 WebMvcConfigurer 接口並覆蓋 configureMessageConverters 方法來自定義消息轉換器。
@EnableWebMvc
@Configuration
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(createXmlHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
}
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
XStreamMarshaller xstreamMarshaller = new XStreamMarshaller();
xmlConverter.setMarshaller(xstreamMarshaller);
xmlConverter.setUnmarshaller(xstreamMarshaller);
return xmlConverter;
}
}在此示例中,我們正在創建一個新的轉換器,即 MarshallingHttpMessageConverter,並使用 Spring XStream 支持進行配置。這提供了很大的靈活性,因為 我們正在使用底層 marshalling 框架的低級 API,在本例中是 XStream,並且我們可以以我們想要的方式進行配置。
請注意,此示例需要將 XStream 庫添加到類路徑中。
此外,通過擴展此支持類,我們正在失去之前預先註冊的默認消息轉換器。
當然,我們現在也可以通過定義自己的 MappingJackson2HttpMessageConverter 來實現同樣的效果。我們可以將自定義 ObjectMapper 設置在此轉換器上,並按照我們的需要進行配置。
在本例中,XStream 被選為 marshaller/unmarshaller 實現,但 其他,如 JibxMarshaller,也可以使用。
此時,在後端啓用了 XML 後,我們可以使用 XML 表示形式消費 API。
curl --header "Accept: application/xml"
http://localhost:8080/spring-boot-rest/foos/14.1. Spring Boot 支持
如果使用 Spring Boot,我們可以避免實現 WebMvcConfigurer 並手動添加所有 Message Converter,就像我們之前所做的那樣。
我們可以只需在上下文中定義不同的 HttpMessageConverter 實例,Spring Boot 將會自動將其添加到它創建的自動配置中:
@Bean
public HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
// ...
return xmlConverter;
}5. 使用 Spring 的 RestTemplate 結合 HTTP 消息轉換器
除了在服務端,HTTP 消息轉換也可以配置在 Spring 的 RestTemplate 的客户端端。
我們將配置模板,在適當的情況下設置 “Accept” 和 “Content-Type” 請求頭。然後,我們將嘗試使用完整的映射和反向映射來消費 REST API,具體包括 Foo 資源,以 JSON 和 XML 兩種格式。
5.1. 不帶 Accept 標頭檢索資源
@Test
public void whenRetrievingAFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
Foo resource = restTemplate.getForObject(URI, Foo.class, "1");
assertThat(resource, notNullValue());
}5.2. 使用 application/xml Accept 標頭檢索資源
現在,我們明確地以 XML 表示形式檢索資源。我們將定義一組轉換器並將它們設置為 RestTemplate。
由於我們正在消費 XML,因此我們將使用之前使用的相同的 XStream 序列化器:
@Test
public void givenConsumingXml_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getXmlMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List<HttpMessageConverter<?>> getXmlMessageConverters() {
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setAnnotatedClasses(Foo.class);
MarshallingHttpMessageConverter marshallingConverter =
new MarshallingHttpMessageConverter(marshaller);
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(marshallingConverter);
return converters;
}5.3. 使用 application/json Accept 標頭檢索資源
同樣,現在我們通過請求 JSON 來消費 REST API:
@Test
public void givenConsumingJson_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getJsonMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List<HttpMessageConverter<?>> getJsonMessageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
return converters;
}5.4. 使用 XML 內容類型更新資源
最後,我們將向 REST API 發送 JSON 數據,並通過 Content-Type 標頭指定該數據的媒體類型:
@Test
public void givenConsumingXml_whenWritingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getJsonAndXmlMessageConverters());
Foo resource = new Foo("jason");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType((MediaType.APPLICATION_XML));
HttpEntity<Foo> entity = new HttpEntity<>(resource, headers);
ResponseEntity<Foo> response =
restTemplate.exchange(URI, HttpMethod.POST, entity, Foo.class);
Foo fooResponse = response.getBody();
assertThat(fooResponse, notNullValue());
assertEquals(resource.getName(), fooResponse.getName());
}
private List<HttpMessageConverter<?>> getJsonAndXmlMessageConverters() {
List<HttpMessageConverter<?>> converters = getJsonMessageConverters();
converters.addAll(getXmlMessageConverters());
return converters;
}有趣的地方在於我們能夠混合不同的媒體類型。我們正在發送 XML 數據,但正在等待服務器返回 JSON 數據。 這充分展示了 Spring 轉換機制的強大之處。
6. 結論
在本文中,我們學習了 Spring MVC 如何允許我們指定並完全自定義 Http Message Converter,以自動將 Java 實體映射到和從 XML 或 JSON。這當然只是一個簡化的定義,正如最後一個測試示例所表明的,消息轉換機制可以做的事情遠不止於此。
我們還探討了如何利用相同的強大機制與 RestTemplate 客户端,從而實現完全類型安全的 API 消費。