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
3.1. Kryo 概述与 Maven 依赖
Kryo 是一种二进制编码格式,相比文本格式具有以下优势:
- 更快的序列化/反序列化速度
- 更小的传输数据体积
虽然理论上可用于跨系统数据传输,但它主要设计用于 Java 组件间通信。通过以下 Maven 依赖添加 Kryo 库:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>
提示:最新版本可查阅 Maven 中央仓库
3.2. 在 Spring REST 中集成 Kryo
要使用 Kryo 作为数据传输格式,需执行以下步骤:
- 创建自定义
HttpMessageConverter
- 实现序列化/反序列化逻辑
- 定义自定义 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;
}
}
踩坑提醒:使用
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 会自动调用对应的转换器。
通常只需注册新转换器即可开箱即用。Spring 会根据 Accept
头值和转换器支持的媒体类型自动选择合适的转换器。
例如同时支持 JSON 和 Kryo:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MappingJackson2HttpMessageConverter());
messageConverters.add(new KryoHttpMessageConverter());
super.configureMessageConverters(messageConverters);
}
现在假设要添加 Google Protocol Buffers 支持。假设存在通过 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 内置 Protocol Buffer 支持,只需添加 ProtobufHttpMessageConverter
:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MappingJackson2HttpMessageConverter());
messageConverters.add(new KryoHttpMessageConverter());
messageConverters.add(new ProtobufHttpMessageConverter());
}
但需注意:必须定义单独的控制器方法返回 FooProtos.Foo
实例(JSON/Kryo 都处理 Foo
类型,无需区分)。
解决方法有两种:
方案一:使用不同 URL
// 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) { … }
方案二:使用相同 URL + 显式指定格式(推荐)
@RequestMapping(
method = RequestMethod.GET,
value = "/foos/{id}",
produces = { "application/x-protobuf" })
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }
关键点:通过
produces
属性指定媒体类型,Spring 会根据客户端Accept
头自动选择匹配的方法。这种方式能提供统一一致的 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 为例进行了实践演示。同时还实现了多格式支持,使不同客户端能按需选择数据格式。
完整实现代码可在 GitHub 项目 中获取,这是一个基于 Maven 的项目,可直接导入运行。