知識庫 / Spring RSS 訂閱

Spring REST API 中的二進制數據格式

Data,REST,Spring
HongKong
4
03:58 AM · Dec 06 ,2025

1. 概述

雖然 JSON 和 XML 在 REST API 數據傳輸格式方面非常流行,但它們並不是唯一的選擇。

存在許多其他格式,具有不同的序列化速度和序列化數據大小。

在本文中,我們將探討如何配置 Spring REST 機制以使用二進制數據格式 – 我們將通過 Kryo 演示。

此外,我們還將展示如何通過添加對 Google Protocol buffers 的支持來支持多種數據格式。

2. HttpMessageConverter 接口

HttpMessageConverter 接口是 Spring 提供的 REST 數據格式轉換的公共 API。

可以通過多種方式指定所需的轉換器。這裏我們通過實現 WebMvcConfigurer 接口並顯式提供所需的轉換器,在覆蓋的 configureMessageConverters 方法中實現:

@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        //...
    }
}

3. Kryo

Kryo 是一個高性能、快速的序列化/反序列化庫,主要用於 Java 和 C++。它旨在提供比 Java 默認的 Serialization 機制(如 Java Serialization)更快的性能,尤其是在處理大型對象時。

Kryo 的核心優勢在於其基於字典的序列化方式。它使用一個字典來映射 Java 對象到其序列化的表示形式,從而避免了在序列化過程中進行冗餘的類型檢查和轉換。

主要特點:

  • 高性能: Kryo 的序列化/反序列化速度通常比 Java 默認的 Serialization 機制快得多。
  • 基於字典: 通過字典映射對象,減少了運行時類型檢查的開銷。
  • 支持多種數據類型: Kryo 支持 Java 對象、基本數據類型、XML、JSON 等多種數據類型。
  • 可定製性: Kryo 允許用户自定義序列化/反序列化策略,以滿足特定需求。

示例 (Java):

// 這是一個簡單的 Kryo 示例,用於序列化和反序列化一個 Person 對象。
// 注意:這裏只是一個示例,實際使用時需要根據具體情況進行調整。

import org.apache.kruger.kryo.serializers.JsonSerializer;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 序列化示例
Person person = new Person("John Doe", 30);
Kryo kryo = new Kryo();
kryo.register(Person.class);
byte[] serializedData = kryo.toBytes(person);

// 反序列化示例
Person deserializedPerson = kryo.fromBytes(serializedData);
System.out.println("Name: " + deserializedPerson.getName());
System.out.println("Age: " + deserializedPerson.getAge());

3.1. Kryo 概述與 Maven

Kryo 是一種二進制編碼格式,相比文本格式,它提供了更好的序列化和反序列化速度,以及更小的傳輸數據量。

雖然理論上它可以用於不同類型的系統之間的數據傳輸,但它主要設計用於與 Java 組件一起使用。

我們通過以下 Maven 依賴項添加了必要的 Kryo 庫:

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>4.0.0</version>
</dependency>

要檢查 kryo 的最新版本,您可以在此處查看

3.2. Kryo 在 Spring REST 中

為了利用 Kryo 作為數據傳輸格式,我們創建了一個自定義的 <em >HttpMessageConverter</em> 並實現了必要的序列化和反序列化邏輯。 此外,我們定義了自定義的 HTTP 頭部用於 Kryo:<em >application/x-kryo</em>。 下面是一個完整的簡化版工作示例,我們用於演示目的:

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. 支持多種數據格式

通常,您需要為同一服務提供對多種數據格式的支持。客户端通過 Accept HTTP 標頭指定所需的格式,然後調用相應的消息轉換器來序列化數據。

通常,只需註冊另一個轉換器,即可實現“開箱即用”。Spring 會根據 Accept 標頭中的值以及轉換器中聲明的媒體類型自動選擇合適的轉換器。

例如,要添加對 JSON 和 Kryo 的支持,請註冊 KryoHttpMessageConverterMappingJackson2HttpMessageConverter

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    super.configureMessageConverters(messageConverters);
}

現在,我們假設我們還想將 Google Protocol Buffer 添加到列表中。對於這個例子,我們假設有一個名為 FooProtos.Foo 的類,該類是使用 protoc 編譯器根據以下 proto 文件生成的:

package baeldung;
option java_package = "com.baeldung.web.dto";
option java_outer_classname = "FooProtos";
message Foo {
    required int64 id = 1;
    required string name = 2;
}
<p>春天內置了對 Protocol Buffer 的一些支持。 只需要將 <em >ProtobufHttpMessageConverter</em> 添加到支持轉換器列表中,就能使其正常工作。</p>
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    messageConverters.add(new ProtobufHttpMessageConverter());
}

然而,我們必須定義一個單獨的控制器方法,該方法返回 FooProtos.Foo 實例(protobuf 和 Kryo 都能處理 Foo,因此控制器無需進行區分)。

有以下兩種方法可以解決關於哪個方法被調用的歧義。第一種方法是使用不同的 URL 來區分 protobuf 和其他格式。例如,對於 protobuf:

@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) { … }

請注意,對於 protobuf,我們使用 value = “/fooprotos/{id}”,而對於其他格式,我們使用 value = “/foos/{id}”

更優的方案是使用相同的 URL,但明確在請求映射中指定生成的 protobuf 數據格式:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/foos/{id}", 
  produces = { "application/x-protobuf" })
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }

請注意,通過在 produces 註解屬性中指定媒體類型,我們向底層 Spring 機制提供提示,以便根據客户端提供的 Accept 請求頭中的值,選擇合適的映射方法,從而消除對 “foos/{id}” URL 中需要調用的方法存在的歧義。

這種第二種方法使我們能夠為客户端提供一致且統一的 REST API,適用於所有數據格式。

如果您想更深入地瞭解使用 Protocol Buffers 與 Spring REST API 的結合,請參閲參考文章。

5. 註冊額外的消息轉換器

需要特別注意的是,當你覆蓋 <em >configureMessageConverters</em> 方法時,你將丟失所有默認的消息轉換器。 只有你提供的轉換器才會使用。

雖然在某些情況下,這正是你想要的,但在許多情況下,你可能只想添加新的轉換器,同時仍然保留默認的轉換器,這些轉換器可以處理標準數據格式,如 JSON。 要實現這一點,請覆蓋 <em >extendMessageConverters</em> 方法:

@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 作為示例進行了説明。

我們還展示瞭如何添加對多種格式的支持,以便不同的客户端能夠使用不同的格式。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.