1. 概述
Protocol Buffers(简称Protobuf)是Google推出的一种跨语言、跨平台的结构化数据序列化机制。相比XML和JSON,它具备三大优势:
- 更快的处理速度
- 更小的数据体积
- 更简单的数据结构
本教程将指导你如何构建一个REST API,利用这种二进制消息格式提升性能。
2. Protocol Buffers基础
2.1 Protobuf简介
使用Protobuf需要先在.proto
文件中定义消息结构。这些文件描述了节点间传输或存储的数据结构。以下是一个典型示例(位于src/main/resources/baeldung.proto
):
syntax = "proto3";
package baeldung;
option java_package = "com.baeldung.protobuf";
option java_outer_classname = "BaeldungTraining";
message Course {
int32 id = 1;
string course_name = 2;
repeated Student student = 3;
}
message Student {
int32 id = 1;
string first_name = 2;
string last_name = 3;
string email = 4;
repeated PhoneNumber phone = 5;
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
enum PhoneType {
MOBILE = 0;
LANDLINE = 1;
}
}
关键点说明:
syntax = "proto3"
:声明使用Protobuf v3语法(v2可省略)package
:命名空间,避免命名冲突java_package
:指定生成Java类的包名java_outer_classname
:指定外层类名
2.2 Java环境集成
定义消息结构后,需通过编译器生成Java代码。有两种获取编译器的方式:
- 从官方仓库编译源码
- 直接下载预编译二进制文件(Maven Central搜索
com.google.protobuf:protoc
)
将编译器复制到项目src/main
目录后执行:
protoc --java_out=java resources/baeldung.proto
这将在com.baeldung.protobuf
包下生成BaeldungTraining
类。同时需添加运行时依赖:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.25.3</version>
</dependency>
踩坑提示:编译器和运行库版本必须严格匹配!
2.3 消息编译原理
Protobuf将.proto
文件中的消息编译为Java静态嵌套类。核心转换规则:
基础类型字段(如Course的id和course_name):
public int getId();
public java.lang.String getCourseName();
重复字段(如Course的student):
public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student> getStudentList();
public int getStudentCount();
public com.baeldung.protobuf.BaeldungTraining.Student getStudent(int index);
嵌套消息(如Student的PhoneNumber):
public java.lang.String getNumber();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneType getType();
枚举类型(如PhoneType):
public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
MOBILE(0),
LANDLINE(1),
UNRECOGNIZED(-1),
;
// 其他声明
}
技术要点:字段名使用snake_case(如
first_name
),编译器会自动转换为Java风格的camelCase(getFirstName()
)
3. Spring REST API集成
3.1 核心Bean配置
主应用类配置如下:
@SpringBootApplication
public class Application {
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
@Bean
public CourseRepository createTestCourses() {
Map<Integer, Course> courses = new HashMap<>();
Course course1 = Course.newBuilder()
.setId(1)
.setCourseName("REST with Spring")
.addAllStudent(createTestStudents())
.build();
Course course2 = Course.newBuilder()
.setId(2)
.setCourseName("Learn Spring Security")
.addAllStudent(new ArrayList<Student>())
.build();
courses.put(course1.getId(), course1);
courses.put(course2.getId(), course2);
return new CourseRepository(courses);
}
// 其他声明
}
关键组件说明:
ProtobufHttpMessageConverter
:将响应自动转换为Protobuf消息CourseRepository
:提供测试数据(注意:操作的是Protobuf对象而非POJO)
仓库类实现:
public class CourseRepository {
Map<Integer, Course> courses;
public CourseRepository (Map<Integer, Course> courses) {
this.courses = courses;
}
public Course getCourse(int id) {
return courses.get(id);
}
}
3.2 控制器配置
控制器实现简单粗暴:
@RestController
public class CourseController {
@Autowired
CourseRepository courseRepo;
@RequestMapping("/courses/{id}")
Course customer(@PathVariable Integer id) {
return courseRepo.getCourse(id);
}
}
核心机制:返回的Course
对象是Protobuf类型而非POJO,这会触发ProtobufHttpMessageConverter
的自动转换。
4. 客户端调用与测试
4.1 测试环境准备
集成测试基类配置:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest
public class ApplicationTest {
// 其他声明
}
定义测试URL:
private static final String COURSE1_URL = "http://localhost:8080/courses/1";
响应验证方法:
private void assertResponse(String response) {
assertThat(response, containsString("id"));
assertThat(response, containsString("course_name"));
assertThat(response, containsString("REST with Spring"));
assertThat(response, containsString("student"));
assertThat(response, containsString("first_name"));
assertThat(response, containsString("last_name"));
assertThat(response, containsString("email"));
assertThat(response, containsString("john.doe@example.com"));
assertThat(response, containsString("richard.roe@example.com"));
assertThat(response, containsString("jane.doe@example.com"));
assertThat(response, containsString("phone"));
assertThat(response, containsString("number"));
assertThat(response, containsString("type"));
}
邮箱Mock说明:将原脱敏邮箱替换为测试专用邮箱
4.2 RestTemplate测试
自动转换测试:
@Autowired
private RestTemplate restTemplate;
@Test
public void whenUsingRestTemplate_thenSucceed() {
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE1_URL, Course.class);
assertResponse(course.toString());
}
需配置RestTemplate Bean:
@Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc));
}
4.3 HttpClient手动转换测试
添加依赖:
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
手动转换实现:
private InputStream executeHttpRequest(String url) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(request);
return httpResponse.getEntity().getContent();
}
private String convertProtobufMessageStreamToJsonString(InputStream protobufStream) throws IOException {
JsonFormat jsonFormat = new JsonFormat();
Course course = Course.parseFrom(protobufStream);
return jsonFormat.printToString(course);
}
@Test
public void whenUsingHttpClient_thenSucceed() throws IOException {
InputStream responseStream = executeHttpRequest(COURSE1_URL);
String jsonOutput = convertProtobufMessageStreamToJsonString(responseStream);
assertResponse(jsonOutput);
}
4.4 JSON响应示例
测试返回的JSON结构示例:
id: 1
course_name: "REST with Spring"
student {
id: 1
first_name: "John"
last_name: "Doe"
email: "john.doe@example.com"
phone {
number: "123456"
}
}
student {
id: 2
first_name: "Richard"
last_name: "Roe"
email: "richard.roe@example.com"
phone {
number: "234567"
type: LANDLINE
}
}
student {
id: 3
first_name: "Jane"
last_name: "Doe"
email: "jane.doe@example.com"
phone {
number: "345678"
}
phone {
number: "456789"
type: LANDLINE
}
}
5. 总结
本教程介绍了:
- Protobuf核心概念与Java集成
- 在Spring Boot中构建Protobuf REST API
- 客户端调用与测试方案
所有示例代码可在GitHub项目中获取。相比传统JSON,Protobuf在以下场景优势明显:
- 高频微服务通信
- 移动端数据传输
- 存储敏感数据场景
简单粗暴的建议:当性能成为瓶颈时,Protobuf是值得考虑的优化方案。