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

Двоичные форматы данных в API Spring REST

В этой статье мы рассмотрим, как настроить механизм Spring REST для использования двоичных форматов данных, которые мы проиллюстрируем на примере Kryo.

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

1. Обзор

Хотя JSON и XML являются широко популярными форматами передачи данных, когда дело доходит до API REST, они не являются единственными доступными вариантами.

Существует множество других форматов с различной степенью скорости сериализации и размером сериализованных данных.

В этой статье мы рассмотрим, как настроить механизм пружинного покоя для использования двоичных форматов данных , которые мы проиллюстрируем с помощью Kryo.

Кроме того, мы покажем, как поддерживать несколько форматов данных, добавив поддержку буферов протокола Google.

2. HttpMessageConverter

Интерфейс HttpMessageConverter – это в основном общедоступный API Spring для преобразования форматов данных REST.

Существуют различные способы указания желаемых преобразователей. Здесь мы реализуем WebMvcConfigurer и явно предоставляем преобразователи, которые мы хотим использовать в переопределенном методе configureMessageConverters :

@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List> messageConverters) {
        //...
    }
}

3. Крио

3.1. Обзор Крио и Maven

Kryo-это формат двоичного кодирования, который обеспечивает хорошую скорость сериализации и десериализации и меньший размер передаваемых данных по сравнению с текстовыми форматами.

Хотя теоретически его можно использовать для передачи данных между различными типами систем, он в первую очередь предназначен для работы с компонентами Java.

Мы добавляем необходимые библиотеки Kryo со следующей зависимостью Maven:


    com.esotericsoftware
    kryo
    4.0.0

Чтобы проверить последнюю версию kryo , вы можете посмотреть здесь .

3.2. Крио в весеннем ОТДЫХЕ

Чтобы использовать Kryo в качестве формата передачи данных, мы создаем пользовательский HttpMessageConverter и реализовать необходимую логику сериализации и десериализации. Кроме того, мы определяем наш пользовательский HTTP-заголовок для Kryo: application/x-kryo . Вот полный упрощенный рабочий пример, который мы используем в демонстрационных целях:

public class KryoHttpMessageConverter extends AbstractHttpMessageConverter {

    public static final MediaType KRYO = new MediaType("application", "x-kryo");

    private static final ThreadLocal kryoThreadLocal = new ThreadLocal() {
        @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 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 entity = new HttpEntity(headers);

ResponseEntity 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 автоматически выбирает соответствующий преобразователь на основе значения в заголовке Accept и поддерживаемых типов носителей, объявленных в преобразователях.

Например, чтобы добавить поддержку как для JSON, так и для Kryo, зарегистрируйте оба Kryo HttpMessageConverter и MappingJackson2HttpMessageConverter :

@Override
public void configureMessageConverters(List> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    super.configureMessageConverters(messageConverters);
}

Теперь предположим, что мы также хотим добавить буфер протокола Google в список. В этом примере мы предполагаем, что существует класс Foo Proto.Foo генерируется с помощью компилятора protoc на основе следующего файла 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 поставляется с некоторой встроенной поддержкой буфера протокола. Все, что нам нужно, чтобы это сработало, – это включить Protobuf HttpMessageConverter в список поддерживаемых конвертеров:

@Override
public void configureMessageConverters(List> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    messageConverters.add(new ProtobufHttpMessageConverter());
}

Однако мы должны определить отдельный метод контроллера, который возвращает Foo Proto.Экземпляры Foo (JSON и Kryo оба имеют дело с Foo s, поэтому в контроллере не требуется никаких изменений, чтобы различать их).

Существует два способа устранить двусмысленность в отношении того, какой метод вызывается. Первый подход заключается в использовании различных URL-адресов для protobuf и других форматов. Например, для 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) { … }

Обратите внимание, что для protobuf мы используем value , а для других форматов value .

Второй и лучший подход состоит в том, чтобы использовать тот же URL – адрес, но явно указать формат полученных данных в сопоставлении запросов для protobuf:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/foos/{id}", 
  produces = { "application/x-protobuf" })
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }

Обратите внимание, что, указывая тип носителя в атрибуте produces annotation, мы даем подсказку базовому механизму Spring о том, какое сопоставление должно использоваться на основе значения в заголовке Accept , предоставленного клиентами, поэтому нет никакой двусмысленности в отношении того, какой метод должен быть вызван для “foos/{id}” URL.

Второй подход позволяет нам предоставлять клиентам единый и согласованный REST API для всех форматов данных.

Наконец, если вы заинтересованы в более глубоком изучении использования буферов протоколов с помощью API Spring REST, ознакомьтесь со справочной статьей .

5. Регистрация Дополнительных Конвертеров Сообщений

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

Хотя иногда это именно то, что вам нужно, во многих случаях вы просто хотите добавить новые конвертеры, сохраняя при этом стандартные, которые уже заботятся о стандартных форматах данных, таких как JSON. Для этого переопределите метод extend MessageConverters :

@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List> messageConverters) {
        messageConverters.add(new ProtobufHttpMessageConverter());
        messageConverters.add(new KryoHttpMessageConverter());
    }
}

6. Заключение

В этом уроке мы рассмотрели, насколько легко использовать любой формат передачи данных в Spring MVC, и мы рассмотрели это на примере Kryo.

Мы также показали, как добавить поддержку нескольких форматов, чтобы разные клиенты могли использовать разные форматы.

Реализация этих двоичных форматов данных в учебнике по API Spring REST , конечно, находится на Github . Это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.