1. 概述
雖然JSON和XML在REST API的數據傳輸格式中非常流行,但它們並不是唯一的選擇。
存在許多其他格式,具有不同的序列化速度和序列化數據大小。
在本文中,我們將探討如何配置 Spring REST機制以使用二進制數據格式 – 我們將通過Kryo進行説明。
此外,我們還將展示如何通過添加對Google Protocol buffers的支持來支持多種數據格式。
2. HttpMessageConverter
HttpMessageConverter 接口基本上是 Spring 的公共 API,用於 REST 數據格式的轉換。
有不同的方法可以指定所需的轉換器。這裏我們實現了 WebMvcConfigurer 並在覆蓋的 configureMessageConverters 方法中顯式提供我們想要使用的轉換器:
@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
//...
}
}
3. Kryo
3.1. Kryo Overview and Maven
Kryo 是一種二進制編碼格式,它提供良好的序列化和反序列化速度,以及與文本格式相比更小的傳輸數據大小。
雖然在理論上它可以用於不同類型的系統之間的數據傳輸,但它主要設計用於與 Java 組件一起使用。
我們添加了必要的 Kryo 庫,如下所示 Maven 依賴項:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>
要查看 Kryo 的最新版本,您可以在此處查看。
3.2. Kryo in Spring REST
為了利用 Kryo 作為數據傳輸格式,我們創建一個自定義 HttpMessageConverter 並實現必要的序列化和反序列化邏輯。 此外,我們定義了自定義 HTTP 標頭用於 Kryo: application/x-kryo。 下面是一個完整的簡化版工作示例,我們用於演示目的:
public class KryoHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
public static final MediaType KRYO = new MediaType("application", "x-kryo");
private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>() {
@Override
protected Kryo initialValue() {
Kryo kryo = new Kryo();
kryo.register(Foo.class, 1);
return kryo;
}
};
public KryoHttpMessageConverter() {
super(KRYO);
}
@Override
protected boolean supports(Class<?> clazz) {
return Object.class.isAssignableFrom(clazz);
}
@Override
protected Object readInternal(
Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException {
Input input = new Input(inputMessage.getBody());
return kryoThreadLocal.get().readClassAndObject(input);
}
@Override
protected void writeInternal(
Object object, HttpOutputMessage outputMessage) throws IOException {
Output output = new Output(outputMessage.getBody());
kryoThreadLocal.get().writeClassAndObject(output, object);
output.flush();
}
@Override
protected MediaType getDefaultContentType(Object object) {
return KRYO;
}
}
注意我們使用了 ThreadLocal,這僅僅是因為 Kryo 實例的創建可能很昂貴,我們希望儘可能地重用它們。
控制器方法很直觀(注意沒有必要為任何特定協議的數據類型而進行自定義),我們使用簡單的 Foo DTO:
@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) {
return fooRepository.findById(id);
}
以及一個快速測試,以證明我們已正確地連接一切:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(Arrays.asList(new KryoHttpMessageConverter()));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(KryoHttpMessageConverter.KRYO));
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<Foo> response = restTemplate.exchange("http://localhost:8080/spring-rest/foos/{id}",
HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
4. 支持多種數據格式
通常,您需要支持多種數據格式,以供同一服務使用。客户端指定所需的數據格式在 HTTP 頭部中,並調用相應的消息轉換器來序列化數據。
通常,您只需要註冊另一個轉換器,即可自動處理。Spring 根據 頭部的值和支持的媒體類型,自動選擇合適的轉換器。
例如,要添加對 JSON 和 Kryo 的支持,請註冊 和 :
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MappingJackson2HttpMessageConverter());
messageConverters.add(new KryoHttpMessageConverter());
super.configureMessageConverters(messageConverters);
}
現在,假設我們還要將 Google Protocol Buffer 添加到列表中。對於此示例,假設有一個類 ,該類已使用 編譯器根據以下 文件生成:
package baeldung;
option java_package = "com.baeldung.web.dto";
option java_outer_classname = "FooProtos";
message Foo {
required int64 id = 1;
required string name = 2;
}
Spring 提供了對 Protocol Buffer 的內置支持。只需將 添加到支持的轉換器列表中即可。
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MappingJackson2HttpMessageConverter());
messageConverters.add(new KryoHttpMessageConverter());
messageConverters.add(new ProtobufHttpMessageConverter());
}
但是,我們必須定義一個返回 實例的方法,該方法用於處理 Protocol Buffer。
有兩件可以解決歧義的方法。第一種方法是使用不同的 URL 來處理 Protocol Buffer 和其他格式。例如,對於 Protocol Buffer:
@RequestMapping(method = RequestMethod.GET, value = "/fooprotos/{id}")
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }
以及對於其他格式:
@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) { … }
請注意,對於 Protocol Buffer,我們使用 ,對於其他格式,我們使用 。
第二種 – 也是更好的方法是使用相同的 URL,但明確地在請求映射中指定生成的格式:
@RequestMapping(
method = RequestMethod.GET,
value = "/foos/{id}",
produces = { "application/x-protobuf" })
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }
請注意,通過在 註解屬性中指定媒體類型,我們向底層的 Spring 機制提供有關根據 頭部的值,選擇哪個映射的信息,因此對於 URL,沒有歧義。
第二種方法使我們能夠為客户端提供所有數據格式的統一和一致的 REST API。
最後,如果您想更深入地瞭解使用 Protocol Buffer 與 Spring REST API 的使用,請查看參考文章。
5. 註冊額外消息轉換器
請務必注意,您在覆蓋 configureMessageConverters 方法時,會丟失所有默認消息轉換器。 只有您提供的轉換器才會使用。
雖然在某些情況下這正是您想要的,但在大多數情況下,您可能只想添加新的轉換器,同時保留默認的轉換器,這些轉換器已經能夠處理標準數據格式,如 JSON。 要實現這一點,請覆蓋 extendMessageConverters 方法:
@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ProtobufHttpMessageConverter());
messageConverters.add(new KryoHttpMessageConverter());
}
}
6. 結論
在本教程中,我們探討瞭如何在 Spring MVC 中輕鬆使用任何數據傳輸格式,並通過 Kryo 作為一個例子進行了驗證。
我們還展示瞭如何添加對多種格式的支持,以便不同的客户端能夠使用不同的格式。