Spring REST API with Protocol Buffers – 带有协议缓冲区的Spring REST API

最后修改: 2016年 6月 19日

1. Overview


Protocol Buffers is a language and platform neutral mechanism for serialization and deserialization of structured data, which is proclaimed by Google, its creator, to be much faster, smaller and simpler than other types of payloads, such as XML and JSON.


This tutorial guides you through setting up a REST API to take advantage of this binary-based message structure.

本教程指导你设置一个REST API,以利用这种基于二进制的消息结构。

2. Protocol Buffers


This section gives some basic information on Protocol Buffers and how they are applied in the Java ecosystem.


2.1. Introduction to Protocol Buffers


In order to make use of Protocol Buffers, we need to define message structures in .proto files. Each file is a description of the data that might be transferred from one node to another, or stored in data sources. Here is an example of .proto files, which is named baeldung.proto and lives in the src/main/resources directory. This file will be used in this tutorial later on:


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;

In this tutorial, we use version 3 of both protocol buffer compiler and protocol buffer language, therefore the .proto file must start with the syntax = “proto3” declaration. If a compiler version 2 is in use, this declaration would be omitted. Next comes the package declaration, which is the namespace for this message structure to avoid naming conflicts with other projects.

在本教程中,我们使用版本3的协议缓冲区编译器和协议缓冲区语言,因此.proto文件必须以syntax = “proto3” 声明开始。如果使用的是编译器版本2,这个声明将被省略。接下来是package声明,它是这个消息结构的命名空间,以避免与其他项目的命名冲突。

The following two declarations are used for Java only: java_package option specifies the package for our generated classes to live in, and java_outer_classname option indicates name of the class enclosing all the types defined in this .proto file.


Subsection 2.3 below will describe the remaining elements and how those are compiled into Java code.


2.2. Protocol Buffers With Java


After a message structure is defined, we need a compiler to convert this language neutral content to Java code. You can follow the instructions in the Protocol Buffers repository in order to get an appropriate compiler version. Alternatively, you may download a pre-built binary compiler from the Maven central repository by searching for the com.google.protobuf:protoc artifact, then picking up an appropriate version for your platform.


Next, copy the compiler to the src/main directory of your project and execute the following command in the command line:


protoc --java_out=java resources/baeldung.proto

This should generate a source file for the BaeldungTraining class within the com.baeldung.protobuf package, as specified in the option declarations of the baeldung.proto file.


In addition to the compiler, Protocol Buffers runtime is required. This can be achieved by adding the following dependency to the Maven POM file:

除了编译器外,还需要Protocol Buffers运行时。这可以通过在Maven POM文件中添加以下依赖关系来实现。


We may use another version of the runtime, provided that it is the same as the compiler’s version. For the latest one, please check out this link.


2.3. Compiling a Message Description


By using a compiler, messages in a .proto file are compiled into static nested Java classes. In the above example, the Course and Student messages are converted to Course and Student Java classes, respectively. At the same time, messages’ fields are compiled into JavaBeans style getters and setters inside those generated types. The marker, composed of an equal sign and a number, at the end of each field declaration is the unique tag used to encode the associated field in the binary form.

通过使用编译器,.proto文件中的消息被编译成静态嵌套的Java类。在上面的例子中,CourseStudent消息被分别转换为CourseStudent Java类。同时,消息的字段被编译成这些生成的类型中的JavaBeans风格的getter和setter。每个字段声明末尾的标记,由一个等号和一个数字组成,是用来在二进制形式中对相关字段进行编码的唯一标记。

We will walk through typed fields of the messages to see how those are converted to accessor methods.


Let’s start with the Course message. It has two simple fields, including id and course_name. Their protocol buffer types, int32 and string, are translated into Java int and String types. Here are their associated getters after compilation (with implementations being left out for brevity):


public int getId();
public java.lang.String getCourseName();

Note that names of typed fields should be in snake case (individual words are separated by underscore characters) to maintain the cooperation with other languages. The compiler will convert those names to camel case according to Java conventions.


The last field of Course message, student, is of the Student complex type, which will be described below. This field is prepended by the repeated keyword, meaning that it may be repeated any number of times. The compiler generates some methods associated with the student field as follows (without implementations):


public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student> getStudentList();
public int getStudentCount();
public com.baeldung.protobuf.BaeldungTraining.Student getStudent(int index);

Now we will move on to the Student message, which is used as complex type of the student field of Course message. Its simple fields, including id, first_name, last_name and email are used to create Java accessor methods:

现在我们将继续讨论Student消息,它被用作Course消息的student字段的复杂类型。它的简单字段,包括id, first_name, last_nameemail,被用来创建Java访问器方法。

public int getId();
public java.lang.String getFirstName();
public java.lang.String getLastName();
public java.lang.String.getEmail();

The last field, phone, is of the PhoneNumber complex type. Similar to the student field of Course message, this field is repetitive and has several associated methods:


public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber> getPhoneList();
public int getPhoneCount();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber getPhone(int index);

The PhoneNumber message is compiled into the BaeldungTraining.Student.PhoneNumber nested type, with two getters corresponding to the message’s fields:


public java.lang.String getNumber();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneType getType();

PhoneType, the complex type of the type field of the PhoneNumber message, is an enumeration type, which will be transformed into a Java enum type nested within the BaeldungTraining.Student class:


public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
    // Other declarations

3. Protobuf in Spring REST API

3.Spring REST API中的Protobuf

This section will guide you through setting up a REST service using Spring Boot.

本节将指导你使用Spring Boot建立一个REST服务。

3.1. Bean Declaration


Let’s start with the definition of our main @SpringBootApplication:


public class Application {
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();

    public CourseRepository createTestCourses() {
        Map<Integer, Course> courses = new HashMap<>();
        Course course1 = Course.newBuilder()
          .setCourseName("REST with Spring")
        Course course2 = Course.newBuilder()
          .setCourseName("Learn Spring Security")
          .addAllStudent(new ArrayList<Student>())
        courses.put(course1.getId(), course1);
        courses.put(course2.getId(), course2);
        return new CourseRepository(courses);

    // Other declarations

The ProtobufHttpMessageConverter bean is used to convert responses returned by @RequestMapping annotated methods to protocol buffer messages.

ProtobufHttpMessageConverter bean用于将@RequestMapping注释的方法返回的响应转换为协议缓冲区消息。

The other bean, CourseRepository, contains some test data for our API.


What’s important here is that we’re operating with Protocol Buffer specific data – not with standard POJOs.

这里重要的是,我们是在用Protocol Buffer的特定数据进行操作–而不是用标准的POJO

Here’s the simple implementation of the CourseRepository:


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. Controller Configuration


We can define the @Controller class for a test URL as follows:


public class CourseController {
    CourseRepository courseRepo;

    Course customer(@PathVariable Integer id) {
        return courseRepo.getCourse(id);

And again – the important thing here is that the Course DTO that we’re returning from the controller layer is not a standard POJO. That’s going to be the trigger for it to be converted to protocol buffer messages before being transferred back to the Client.

再说一遍,重要的是,我们从控制器层返回的Course DTO不是一个标准的POJO。这将是触发它被转换为协议缓冲区消息的原因,然后再转回给客户端。

4. REST Clients and Testing


Now that we had a look at the simple API implementation – let’s now illustrate deserialization of protocol buffer messages on the client side – using two methods.


The first one takes advantage of the RestTemplate API with a pre-configured ProtobufHttpMessageConverter bean to automatically convert messages.

第一个是利用RestTemplate API与一个预先配置的ProtobufHttpMessageConverter Bean来自动转换消息。

The second is using protobuf-java-format to manually transform protocol buffer responses into JSON documents.


To begin, we need to set up the context for an integration test and instruct Spring Boot to find configuration information in the Application class by declaring a test class as follows:

首先,我们需要为集成测试设置上下文,并指示Spring Boot在Application类中寻找配置信息,方法是声明一个测试类如下。

@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTest {
    // Other declarations

All code snippets in this section will be placed in the ApplicationTest class.


4.1. Expected Response


The first step to access a REST service is to determine the request URL:


private static final String COURSE1_URL = "http://localhost:8080/courses/1";

This COURSE1_URL will be used for getting the first test double course from the REST service we created before. After a GET request is sent to the above URL, the corresponding response is verified using the following assertions:


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("[email protected]"));
    assertThat(response, containsString("[email protected]"));
    assertThat(response, containsString("[email protected]"));
    assertThat(response, containsString("phone"));
    assertThat(response, containsString("number"));
    assertThat(response, containsString("type"));

We will make use of this helper method in both test cases covered in the succeeding sub-sections.


4.2. Testing With RestTemplate


Here is how we create a client, send a GET request to the designated destination, receive the response in the form of protocol buffer messages and verify it using the RestTemplate API:

下面是我们如何创建一个客户端,向指定的目的地发送GET请求,以协议缓冲区信息的形式接收响应,并使用RestTemplate API进行验证。

private RestTemplate restTemplate;

public void whenUsingRestTemplate_thenSucceed() {
    ResponseEntity<Course> course = restTemplate.getForEntity(COURSE1_URL, Course.class);

To make this test case work, we need a bean of the RestTemplate type to be registered in a configuration class:


RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
    return new RestTemplate(Arrays.asList(hmc));

Another bean of the ProtobufHttpMessageConverter type is also required to automatically transform the received protocol buffer messages. This bean is the same as the one defined in sub-section 3.1. Since the client and server share the same application context in this tutorial, we may declare the RestTemplate bean in the Application class and re-use the ProtobufHttpMessageConverter bean.

还需要另一个ProtobufHttpMessageConverter类型的Bean来自动转换收到的协议缓冲区消息。这个Bean与第3.1小节中定义的Bean相同。由于在本教程中,客户端和服务器共享同一个应用程序上下文,我们可以在Application类中声明RestTemplate Bean,并重新使用ProtobufHttpMessageConverter Bean。

4.3. Testing With HttpClient


The first step to use the HttpClient API and manually convert protocol buffer messages is adding the following two dependencies to the Maven POM file:

使用HttpClient API和手动转换协议缓冲区消息的第一步是在Maven POM文件中添加以下两个依赖项。


For the latest versions of these dependencies, please have a look at protobuf-java-format and httpclient artifacts in Maven central repository.


Let’s move on to create a client, execute a GET request and convert the associated response to an InputStream instance using the given URL:


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();

Now, we will convert protocol buffer messages in the form of an InputStream object to a JSON document:


private String convertProtobufMessageStreamToJsonString(InputStream protobufStream) throws IOException {
    JsonFormat jsonFormat = new JsonFormat();
    Course course = Course.parseFrom(protobufStream);
    return jsonFormat.printToString(course);

And here is how a test case uses private helper methods declared above and validates the response:


public void whenUsingHttpClient_thenSucceed() throws IOException {
    InputStream responseStream = executeHttpRequest(COURSE1_URL);
    String jsonOutput = convertProtobufMessageStreamToJsonString(responseStream);

4.4. Response in JSON


In order to make it clear, JSON forms of the responses we received in the tests described in previous sub-sections are included herein:


id: 1
course_name: "REST with Spring"
student {
    id: 1
    first_name: "John"
    last_name: "Doe"
    email: "[email protected]"
    phone {
        number: "123456"
student {
    id: 2
    first_name: "Richard"
    last_name: "Roe"
    email: "[email protected]"
    phone {
        number: "234567"
        type: LANDLINE
student {
    id: 3
    first_name: "Jane"
    last_name: "Doe"
    email: "[email protected]"
    phone {
        number: "345678"
    phone {
        number: "456789"
        type: LANDLINE

5. Conclusion


This tutorial quickly introduced Protocol Buffers and illustrated the setting up of a REST API using the format with Spring. We then moved to client support and the serialization-deserialization mechanism.

本教程快速介绍了协议缓冲区,并说明了如何使用Spring的格式来设置REST API。然后,我们转向了客户端支持和序列化-反序列化机制。

The implementation of all the examples and code snippets can be found in a GitHub project.