Binäre Datenformate in einer Spring-REST-API

1. Überblick

JSON und XML sind zwar bei REST-APIs weit verbreitete Datenübertragungsformate, aber nicht die einzigen verfügbaren Optionen.

Es gibt viele andere Formate mit unterschiedlichem Serialisierungsgrad und serialisierter Datengröße.

In diesem Artikel erfahren Sie, wie Sie einen Spring-REST-Mechanismus für die Verwendung von binären Datenformaten konfigurieren ** , den wir mit Kryo veranschaulichen.

Außerdem zeigen wir, wie Sie mehrere Datenformate unterstützen, indem Sie die Unterstützung für Google Protocol-Puffer hinzufügen.

2. HttpMessageConverter

Die Schnittstelle HttpMessageConverter ist im Wesentlichen die öffentliche API von Spring für die Konvertierung von REST-Datenformaten.

Es gibt verschiedene Möglichkeiten, die gewünschten Konverter anzugeben. Hier implementieren wir WebMvcConfigurer und stellen explizit die Konverter bereit, die in der überschriebenen configureMessageConverters -Methode verwendet werden sollen:

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
       //...
    }
}
  1. Kryo

** 3.1. Kryo-Übersicht und Maven

Kryo ist ein binäres Codierungsformat, das im Vergleich zu textbasierten Formaten eine gute Serialisierungs- und Deserialisierungsgeschwindigkeit und eine kleinere übertragene Datengröße bietet.

Während theoretisch Daten zwischen verschiedenen Arten von Systemen übertragen werden können, wurde sie hauptsächlich für die Verwendung von Java-Komponenten entwickelt.

Wir fügen die erforderlichen Kryo-Bibliotheken mit der folgenden Maven-Abhängigkeit hinzu:

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>4.0.0</version>
</dependency>

Um die neueste Version von kryo zu überprüfen, können Sie https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com.esotericsoftware%22%20A%3A%22kryo%22 einschalten Schau hier].

3.2. Kryo im Frühling REST

Um Kryo als Datenübertragungsformat zu verwenden, erstellen wir einen benutzerdefinierten HttpMessageConverter und implementieren die erforderliche Serialisierungs- und Deserialisierungslogik. Außerdem definieren wir unseren benutzerdefinierten HTTP-Header für Kryo:

anwendung/x-kryo . Hier ist ein vollständig vereinfachtes Arbeitsbeispiel, das wir zu Demonstrationszwecken verwenden:

public class KryoHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

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

    private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>() {
        @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<? extends Object> 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;
    }
}

Beachten Sie, dass wir hier ein ThreadLocal verwenden, nur weil die Erstellung von Kryo-Instanzen teuer werden kann, und wir möchten diese so weit wie möglich wiederverwenden.

Die Controller-Methode ist unkompliziert (beachten Sie, dass keine benutzerdefinierten protokollspezifischen Datentypen erforderlich sind, wir verwenden einfach Foo DTO):

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) {
    return fooRepository.findById(id);
}

Und ein Schnelltest, um zu beweisen, dass wir alles richtig miteinander verbunden haben:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(Arrays.asList(new KryoHttpMessageConverter()));

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(KryoHttpMessageConverter.KRYO));
HttpEntity<String> entity = new HttpEntity<String>(headers);

ResponseEntity<Foo> response = restTemplate.exchange("http://localhost:8080/spring-rest/foos/{id}",
  HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();

assertThat(resource, notNullValue());

4. Unterstützung mehrerer Datenformate

Häufig möchten Sie Unterstützung für mehrere Datenformate für denselben Dienst bereitstellen. Die Clients geben die gewünschten Datenformate im HTTP-Header Accept an, und der entsprechende Nachrichtenkonverter wird zur Serialisierung der Daten aufgerufen.

In der Regel müssen Sie nur einen anderen Konverter registrieren, um die Standardeinstellungen zu erfüllen. Spring wählt den entsprechenden Konverter automatisch aus, basierend auf dem Wert im Accept -Header und den unterstützten Medientypen, die in den Konvertern deklariert sind.

Um beispielsweise Unterstützung für JSON und Kryo hinzuzufügen, registrieren Sie KryoHttpMessageConverter und MappingJackson2HttpMessageConverter :

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

Nehmen wir nun an, wir möchten der Liste auch Google Protocol Buffer hinzufügen. In diesem Beispiel nehmen wir an, dass es eine Klasse FooProtos.Foo gibt, die mit dem protoc -Compiler basierend auf der folgenden proto -Datei generiert wird:

package baeldung;
option java__package = "org.baeldung.web.dto";
option java__outer__classname = "FooProtos";
message Foo {
    required int64 id = 1;
    required string name = 2;
}

Spring bietet einige integrierte Unterstützung für Protocol Buffer. Alles, was wir brauchen, damit es funktioniert, ist, ProtobufHttpMessageConverter in die Liste der unterstützten Konverter aufzunehmen:

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

Wir müssen jedoch eine separate Controller-Methode definieren, die FooProtos.Foo -Instanzen zurückgibt (JSON und Kryo behandeln beide mit __Foo __s, sodass im Controller keine Änderungen erforderlich sind, um die beiden zu unterscheiden).

Es gibt zwei Möglichkeiten, um die Unklarheit darüber zu lösen, welche Methode aufgerufen wird. Der erste Ansatz besteht darin, verschiedene URLs für Protobuf und andere Formate zu verwenden. Zum Beispiel für protobuf:

@RequestMapping(method = RequestMethod.GET, value = "/fooprotos/{id}")
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { ... }

und für die anderen:

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) { ... }

Beachten Sie, dass wir für protobuf value = "/fooprotos/\ {id}" und für die anderen Formate value = "/foos/\ {id}" verwenden.

  • Der zweite und bessere Ansatz besteht darin, dieselbe URL zu verwenden, das erzeugte Datenformat jedoch explizit im Request-Mapping für protobuf anzugeben:

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

Durch die Angabe des Medientyps im Annotation-Attribut produces geben wir dem zugrundeliegenden Spring-Mechanismus einen Hinweis darauf, welches Mapping basierend auf dem von den Clients bereitgestellten Wert im Accept -Header verwendet werden muss für die "foos/\ {id}" URL aufgerufen werden.

Der zweite Ansatz ermöglicht es uns, den Clients eine einheitliche und konsistente REST-API für alle Datenformate bereitzustellen.

Wenn Sie sich für die Verwendung von Protocol Buffers mit einer Spring-REST-API interessieren, schauen Sie sich link an:/spring-rest-api-with-protocol-puffer[Referenzartikel].

5. Registrieren zusätzlicher Nachrichtenkonverter

Beachten Sie unbedingt, dass Sie alle Standard-Nachrichtenkonverter verlieren, wenn Sie die Methode configureMessageConverters überschreiben.

Nur die von Ihnen angegebenen werden verwendet.

Manchmal ist es genau das, was Sie möchten. In vielen Fällen möchten Sie einfach neue Konverter hinzufügen, während die Standardkonverter beibehalten werden, die bereits Standarddatenformate wie JSON verwenden. Um dies zu erreichen, überschreiben Sie die Methode extendMessageConverters :

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

6. Schlussfolgerung

In diesem Lernprogramm haben wir untersucht, wie einfach es ist, ein beliebiges Datenübertragungsformat in Spring MVC zu verwenden. Dies wurde am Beispiel von Kryo untersucht.

Wir haben auch gezeigt, wie Sie Unterstützung für mehrere Formate hinzufügen können, damit verschiedene Clients unterschiedliche Formate verwenden können.

Die Implementierung dieser Binärdatenformate in einem Spring-REST-API-Tutorial ist selbstverständlich auf Github . Dies ist ein auf Maven basierendes Projekt. Es sollte daher einfach zu importieren und so auszuführen sein, wie es ist.