Binary Data Formats in a Spring REST API – Spring REST API中的二进制数据格式

最后修改: 2016年 7月 12日

1. Overview

1.概述

While JSON and XML are widely popular data transfer formats when it comes to REST APIs, they’re not the only options available.

虽然JSON和XML是广泛流行的数据传输格式,当涉及到REST APIs时,它们不是唯一的选择。

There exist many other formats with varying degree of serialization speed and serialized data size.

还存在许多其他格式,它们的序列化速度和序列化数据大小各不相同。

In this article we explore how to configure a Spring REST mechanism to use binary data formats – which we illustrate with Kryo.

在这篇文章中,我们探讨了如何配置Spring REST机制以使用二进制数据格式–我们用Kryo进行说明。

Moreover we show how to support multiple data formats by adding support for Google Protocol buffers.

此外,我们展示了如何通过增加对谷歌协议缓冲区的支持来支持多种数据格式。

2. HttpMessageConverter

2.HttpMessageConverter

HttpMessageConverter interface is basically Spring’s public API for the conversion of REST data formats.

HttpMessageConverter接口基本上是Spring的公共API,用于转换REST数据格式。

There are different ways to specify the desired converters. Here we implement WebMvcConfigurer and explicitly provide the converters we want to use in the overridden configureMessageConverters method:

有不同的方法来指定所需的转换器。这里我们实现了WebMvcConfigurer,并在重载的configureMessageConverters方法中明确提供了我们想要使用的转换器。

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

3. Kryo

3.Kryo

3.1. Kryo Overview and Maven

3.1.Kryo概述和Maven

Kryo is a binary encoding format that provides good serialization and deserialization speed and smaller transferred data size compared to text-based formats.

Kryo是一种二进制编码格式,与基于文本的格式相比,它提供了良好的序列化和反序列化速度和较小的传输数据大小。

While in theory it can be used to transfer data between different kinds of systems, it is primarily designed to work with Java components.

虽然在理论上它可以用来在不同种类的系统之间传输数据,但它主要被设计成与Java组件一起工作。

We add the necessary Kryo libraries with the following Maven dependency:

我们通过以下Maven依赖关系添加必要的Kryo库。

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

To check the latest version of kryo you can have a look here.

要检查最新版本的kryo,你可以在这里看看

3.2. Kryo in Spring REST

3.2.Spring REST中的Kryo

In order to utilize Kryo as data transfer format, we create a custom HttpMessageConverter and implement the necessary serialization and deserialization logic. Also, we define our custom HTTP header for Kryo: application/x-kryo. Here is a full simplified working example which we use for demonstration purposes:

为了利用Kryo作为数据传输格式,我们创建了一个自定义的HttpMessageConverter并实现必要的序列化和反序列化逻辑。另外,我们为Kryo定义了我们的自定义HTTP头: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;
    }
}

Notice we’re using a ThreadLocal here simply because the creation of Kryo instances can get expensive, and we want to re-utilize these as much as we can.

注意,我们在这里使用ThreadLocal,只是因为创建Kryo实例会变得很昂贵,我们想尽可能地重新利用这些实例。

The controller method is straightforward (note there is no need for any custom protocol-specific data types, we use plain Foo DTO):

控制器的方法很简单(注意不需要任何定制的特定协议数据类型,我们使用普通的Foo DTO)。

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) {
    return fooRepository.findById(id);
}

And a quick test to prove that we have wired everything together correctly:

还有一个快速测试,以证明我们把所有东西都正确地连在一起。

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. Supporting Multiple Data Formats

4.支持多种数据格式

Often you would want to provide support for multiple data formats for the same service. The clients specify the desired data formats in the Accept HTTP header, and the corresponding message converter is invoked to serialize the data.

通常情况下,你会希望为同一服务提供多种数据格式的支持。客户端在Accept HTTP头中指定所需的数据格式,并调用相应的消息转换器来序列化数据。

Usually, you just have to register another converter for things to work out of the box. Spring picks the appropriate converter automatically based on the value in the Accept header and the supported media types declared in the converters.

通常情况下,你只需要注册另一个转换器就可以开箱工作了。Spring会根据Accept头中的值和转换器中声明的支持的媒体类型自动挑选合适的转换器。

For example, to add support for both JSON and Kryo, register both KryoHttpMessageConverter and MappingJackson2HttpMessageConverter:

例如,要添加对JSON和Kryo的支持,请同时注册KryoHttpMessageConverterMappingJackson2HttpMessageConverter

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

Now, let’s suppose that we want to add Google Protocol Buffer to the list as well. For this example, we assume there is a class FooProtos.Foo generated with the protoc compiler based on the following proto file:

现在,让我们假设我们也想把谷歌协议缓冲区加入到列表中。在这个例子中,我们假设有一个用protoc编译器生成的类FooProtos.Foo,基于以下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;
}

Spring comes with some built-in support for Protocol Buffer. All we need to make it work is to include ProtobufHttpMessageConverter in the list of supported converters:

Spring内置了一些对Protocol Buffer的支持。我们需要做的就是将ProtobufHttpMessageConverter列入支持的转换器列表中。

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

However, we have to define a separate controller method that returns FooProtos.Foo instances (JSON and Kryo both deal with Foos, so no changes are needed in the controller to distinguish the two).

然而,我们必须定义一个单独的控制器方法来返回FooProtos.Foo实例(JSON和Kryo都处理Foos,所以不需要改变控制器来区分两者)。

There are two ways to resolve the ambiguity about which method gets called. The first approach is to use different URLs for protobuf and other formats. For example, for protobuf:

有两种方法可以解决哪个方法被调用的模糊问题。第一种方法是对protobuf和其他格式使用不同的URL。例如,对于protobuf。

@RequestMapping(method = RequestMethod.GET, value = "/fooprotos/{id}")
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }

and for the others:

并为其他。

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) { … }

Notice that for protobuf we use value = “/fooprotos/{id}” and for the other formats value = “/foos/{id}”.

注意,对于protobuf,我们使用value = “/fooprotos/{id}”,对于其他格式,value = “/foos/{id}”

The second – and better approach is to use the same URL, but to explicitly specify the produced data format in the request mapping for protobuf:

第二种–也是更好的方法是使用相同的URL,但要在protobuf的请求映射中明确指定所产生的数据格式:

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

Note that by specifying the media type in the produces annotation attribute we give a hint to the underlying Spring mechanism about which mapping needs to be used based on the value in the Accept header provided by clients, so there is no ambiguity about which method needs to be invoked for the “foos/{id}” URL.

请注意,通过在produces注解属性中指定媒体类型,我们向Spring底层机制提供了一个提示,即需要根据客户提供的Accept头中的值来使用哪种映射,所以对于需要为“foos/{id}” URL调用哪个方法不会有任何歧义。

The second approach enables us to provide a uniform and consistent REST API to the clients for all data formats.

第二种方法使我们能够为所有的数据格式向客户提供一个统一的、一致的REST API。

Finally, if you’re interested in going deeper into using Protocol Buffers with a Spring REST API, have a look at the reference article.

最后,如果您有兴趣深入了解在Spring REST API中使用协议缓冲区,请查看参考文章

5. Registering Extra Message Converters

5.注册额外的信息转换器

It is very important to note that you lose all of the default message converters when you override the configureMessageConverters method. Only the ones you provide will be used.

非常重要的一点是,当你覆盖configureMessageConverters方法时,你会失去所有的默认消息转换器。只有你提供的那些才会被使用。

While sometimes this is exactly what you want, in many cases you just want to add new converters, while still keeping the default ones which already take care of standard data formats like JSON. To achieve this, override the extendMessageConverters method:

虽然有时这正是你想要的,但在许多情况下,你只是想添加新的转换器,同时仍然保留默认的转换器,这些转换器已经照顾到标准的数据格式,如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. Conclusion

6.结语

In this tutorial, we looked at how easy it is to use any data transfer format in Spring MVC, and we examined this by using Kryo as an example.

在本教程中,我们研究了在Spring MVC中使用任何数据传输格式是多么容易,我们通过使用Kryo作为例子来研究这个问题。

We also showed how to add support for multiple formats so that different clients are able to use different formats.

我们还展示了如何添加对多种格式的支持,以便不同的客户能够使用不同的格式。

The implementation of this Binary Data Formats in a Spring REST API Tutorial is of course on Github. This is a Maven based project, so it should be easy to import and run as it is.

这个二进制数据格式在Spring REST API教程中的实现当然是在Github上。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。