Рубрики
Без рубрики

API Spring REST с буферами протоколов

Краткое и практическое введение в использование буферов протоколов с API Spring REST.

Автор оригинала: baeldung.

1. Обзор

Буферы протокола – это нейтральный к языку и платформе механизм сериализации и десериализации структурированных данных, который, по словам его создателя Google, намного быстрее, меньше и проще, чем другие типы полезных нагрузок, такие как XML и JSON.

Этот учебник поможет вам настроить REST API, чтобы воспользоваться преимуществами этой двоичной структуры сообщений.

2. Буферы протокола

В этом разделе приведены некоторые основные сведения о буферах протоколов и о том, как они применяются в экосистеме Java.

2.1. Введение в буферы протокола

Чтобы использовать буферы протокола, нам нужно определить структуры сообщений в файлах .proto . Каждый файл представляет собой описание данных, которые могут быть переданы с одного узла на другой или сохранены в источниках данных. Вот пример файлов .proto , который называется baeldung.proto и находится в каталоге src/main/resources . Этот файл будет использоваться в этом уроке позже:

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;
    }
}

В этом учебнике мы используем версию 3 как компилятора буфера протокола, так и языка буфера протокола , поэтому файл .proto должен начинаться с объявления синтаксиса . Если используется компилятор версии 2, это объявление будет опущено. Далее следует объявление package , которое является пространством имен для этой структуры сообщений, чтобы избежать конфликтов имен с другими проектами.

Следующие два объявления используются только для Java: java_package опция указывает пакет, в котором будут жить наши сгенерированные классы, и java_outer_classname опция указывает имя класса, содержащего все типы, определенные в этом файле .proto .

В подразделе 2.3 ниже будут описаны остальные элементы и то, как они компилируются в код Java.

2.2. Буферы Протоколов С Java

После того, как структура сообщения определена, нам нужен компилятор для преобразования нейтрального содержимого этого языка в код Java. Вы можете следовать инструкциям в репозитории буферов протоколов , чтобы получить соответствующую версию компилятора. Кроме того, вы можете загрузить предварительно построенный двоичный компилятор из центрального репозитория Maven, выполнив поиск com.google.protobuf:protoc/| artifact, а затем выбрав подходящую версию для вашей платформы.

Затем скопируйте компилятор в каталог src/main вашего проекта и выполните следующую команду в командной строке:

protoc --java_out=java resources/baeldung.proto

Это должно создать исходный файл для класса Baeldung Training в пакете com.baeldung.protobuf , как указано в объявлениях option файла baeldung.proto|/.

В дополнение к компилятору требуется время выполнения буферов протокола. Это может быть достигнуто путем добавления следующей зависимости в файл Maven POM:


    com.google.protobuf
    protobuf-java
    3.0.0-beta-3

Мы можем использовать другую версию среды выполнения при условии, что она совпадает с версией компилятора. Для получения последней версии, пожалуйста, проверьте эту ссылку .

2.3. Составление описания сообщения

С помощью компилятора сообщения в файле .proto компилируются в статические вложенные классы Java. В приведенном выше примере сообщения Course и Student преобразуются в классы Course и Student Java соответственно. В то же время поля сообщений компилируются в геттеры и сеттеры стиля JavaBeans внутри этих сгенерированных типов. Маркер, состоящий из знака равенства и числа, в конце каждого объявления поля является уникальным тегом, используемым для кодирования связанного поля в двоичной форме.

Мы пройдемся по типизированным полям сообщений, чтобы увидеть, как они преобразуются в методы доступа.

Давайте начнем с сообщения Курс . Он имеет два простых поля, включая id и course_name . Их типы буферов протоколов, int32 и string , переводятся в типы Java int и String . Вот их связанные геттеры после компиляции (с реализациями, оставленными для краткости):

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

Обратите внимание, что имена типизированных полей должны быть в нижнем регистре (отдельные слова разделены символами подчеркивания), чтобы поддерживать взаимодействие с другими языками. Компилятор преобразует эти имена в регистр верблюда в соответствии с соглашениями Java.

Последнее поле сообщения Course , student , относится к типу Student complex, который будет описан ниже. Это поле предваряется ключевым словом repeated , что означает, что оно может повторяться любое количество раз. Компилятор генерирует некоторые методы, связанные с полем student следующим образом (без реализаций):

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

Теперь мы перейдем к сообщению Student , которое используется в качестве сложного типа поля student сообщения Course|/. Его простые поля, включая id , first_name , last_name и email , используются для создания методов доступа Java:

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

Последнее поле, телефон , имеет сложный тип Номер телефона|/. Подобно полю student сообщения Course , это поле является повторяющимся и имеет несколько связанных методов:

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

Сообщение Номер телефона компилируется в BaeldungTraining.Студент.Номер телефона вложенный тип, с двумя геттерами, соответствующими полям сообщения:

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

Тип телефона , сложный тип поля type сообщения Phone Number , является типом перечисления, который будет преобразован в тип Java enum , вложенный в BaeldungTraining.Студент класс:

public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
    MOBILE(0),
    LANDLINE(1),
    UNRECOGNIZED(-1),
    ;
    // Other declarations
}

3. Протобуф в API весеннего ОТДЫХА

В этом разделе вы узнаете, как настроить службу REST с помощью Spring Boot.

3.1. Декларация бобов

Давайте начнем с определения нашего основного @SpringBootApplication :

@SpringBootApplication
public class Application {
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }

    @Bean
    public CourseRepository createTestCourses() {
        Map 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())
          .build();
        courses.put(course1.getId(), course1);
        courses.put(course2.getId(), course2);
        return new CourseRepository(courses);
    }

    // Other declarations
}

Компонент Protobuf HttpMessageConverter используется для преобразования ответов, возвращаемых @RequestMapping аннотированными методами, в сообщения буфера протокола.

Другой компонент, Репозиторий курсов , содержит некоторые тестовые данные для нашего API.

Здесь важно то, что мы работаем с данными, специфичными для буфера протокола , а не со стандартными POJOS .

Вот простая реализация репозитория Course :

public class CourseRepository {
    Map courses;
    
    public CourseRepository (Map courses) {
        this.courses = courses;
    }
    
    public Course getCourse(int id) {
        return courses.get(id);
    }
}

3.2. Конфигурация контроллера

Мы можем определить класс @Controller для тестового URL-адреса следующим образом:

@RestController
public class CourseController {
    @Autowired
    CourseRepository courseRepo;

    @RequestMapping("/courses/{id}")
    Course customer(@PathVariable Integer id) {
        return courseRepo.getCourse(id);
    }
}

И снова – здесь важно то, что курс DTO, который мы возвращаем со уровня контроллера, не является стандартным POJO. Это будет триггером для его преобразования в сообщения буфера протокола перед передачей обратно Клиенту.

4. Отдых клиентов и тестирование

Теперь, когда мы рассмотрели простую реализацию API – давайте теперь проиллюстрируем десериализацию сообщений буфера протокола на стороне клиента – с использованием двух методов.

Первый использует преимущества RestTemplate API с предварительно настроенным Protobuf HttpMessageConverter bean для автоматического преобразования сообщений.

Второй – использование protobuf-java-format для ручного преобразования ответов буфера протокола в документы JSON.

Для начала нам нужно настроить контекст для интеграционного теста и дать команду Spring Boot найти информацию о конфигурации в классе Application , объявив тестовый класс следующим образом:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest
public class ApplicationTest {
    // Other declarations
}

Все фрагменты кода в этом разделе будут помещены в класс Application Test .

4.1. Ожидаемый ответ

Первым шагом для доступа к службе REST является определение URL-адреса запроса:

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

Этот КУРС 1_URL будет использоваться для получения первого тестового двойного курса из службы REST, которую мы создали ранее. После отправки запроса GET по указанному выше URL-адресу соответствующий ответ проверяется с помощью следующих утверждений:

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"));
}

Мы будем использовать этот вспомогательный метод в обоих тестовых случаях, описанных в последующих подразделах.

4.2. Тестирование С Помощью RestTemplate

Вот как мы создаем клиента, отправляем запрос GET в назначенное место назначения, получаем ответ в виде сообщений буфера протокола и проверяем его с помощью RestTemplate API:

@Autowired
private RestTemplate restTemplate;

@Test
public void whenUsingRestTemplate_thenSucceed() {
    ResponseEntity course = restTemplate.getForEntity(COURSE1_URL, Course.class);
    assertResponse(course.toString());
}

Чтобы этот тестовый случай работал, нам нужен компонент типа RestTemplate , который будет зарегистрирован в классе конфигурации:

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

Другой компонент типа Protobuf HttpMessageConverter также необходим для автоматического преобразования полученных сообщений буфера протокола. Этот компонент совпадает с тем, который определен в подразделе 3.1. Поскольку клиент и сервер используют один и тот же контекст приложения в этом руководстве, мы можем объявить компонент RestTemplate в классе Application и повторно использовать компонент ProtobufHttpMessageConverter .

4.3. Тестирование С Помощью HttpClient

Первым шагом для использования HttpClient API и ручного преобразования сообщений буфера протокола является добавление следующих двух зависимостей в файл Maven POM:


    com.googlecode.protobuf-java-format
    protobuf-java-format
    1.4


    org.apache.httpcomponents
    httpclient
    4.5.2

Для получения последних версий этих зависимостей, пожалуйста, ознакомьтесь с protobuf-java-format и httpclient артефактами в центральном репозитории Maven.

Давайте перейдем к созданию клиента, выполним запрос GET и преобразуем соответствующий ответ в экземпляр InputStream , используя данный 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();
}

Теперь мы преобразуем сообщения буфера протокола в виде объекта InputStream в документ JSON:

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: "[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. Заключение

В этом учебном пособии быстро представлены буферы протоколов и проиллюстрирована настройка REST API с использованием формата с Spring. Затем мы перешли к поддержке клиентов и механизму сериализации-десериализации.

Реализацию всех примеров и фрагментов кода можно найти в проекте GitHub .