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.ListgetStudentList(); 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.ListgetPhoneList(); 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() { Mapcourses = 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 { Mapcourses; 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() { ResponseEntitycourse = 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 .