Spring REST API mit Protokollpuffern

Spring REST API mit Protokollpuffern

1. Überblick

Protocol Buffers ist ein sprach- und plattformneutraler Mechanismus für die Serialisierung und Deserialisierung strukturierter Daten, der von Google, seinem Ersteller, als viel schneller, kleiner und einfacher als andere Arten von Nutzdaten wie XML und JSON bezeichnet wird.

Dieses Lernprogramm führt Sie durch das Einrichten einer REST-API, um diese binärbasierte Nachrichtenstruktur zu nutzen.

2. Protokollpuffer

Dieser Abschnitt enthält einige grundlegende Informationen zu Protokollpuffern und deren Anwendung im Java-Ökosystem.

2.1. Einführung in Protokollpuffer

Um Protokollpuffer verwenden zu können, müssen Nachrichtenstrukturen in.proto Dateien definiert werden. Jede Datei ist eine Beschreibung der Daten, die von einem Knoten zu einem anderen übertragen oder in Datenquellen gespeichert werden können. Hier ist ein Beispiel für.proto-Dateien, dieexample.proto heißen und sich im Verzeichnissrc/main/resources befinden. Diese Datei wird später in diesem Tutorial verwendet:

syntax = "proto3";
package example;
option java_package = "com.example.protobuf";
option java_outer_classname = "exampleTraining";

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 diesem Lernprogramm musswe use version 3 of both protocol buffer compiler and protocol buffer language, daher muss die Datei.proto mit der Deklarationsyntax = “proto3” beginnen. Wenn eine Compiler-Version 2 verwendet wird, wird diese Deklaration weggelassen. Als nächstes folgt die Deklarationpackage, die den Namespace für diese Nachrichtenstruktur darstellt, um Namenskonflikte mit anderen Projekten zu vermeiden.

Die folgenden zwei Deklarationen werden nur für Java verwendet: Die Optionjava_packagegibt das Paket an, in dem unsere generierten Klassen leben sollen, und die Optionjava_outer_classnamegibt den Namen der Klasse an, die alle in.protodefinierten Typen enthält. s Datei.

Unterabschnitt 2.3 unten beschreibt die verbleibenden Elemente und wie diese in Java-Code kompiliert werden.

2.2. Protokollpuffer mit Java

Nachdem eine Nachrichtenstruktur definiert wurde, benötigen wir einen Compiler, um diesen sprachneutralen Inhalt in Java-Code zu konvertieren. Sie können den Anweisungen inProtocol Buffers repository folgen, um eine geeignete Compilerversion zu erhalten. Alternativ können Sie einen vorgefertigten Binärcompiler aus dem zentralen Maven-Repository herunterladen, indem Sie nach dem Artefaktcom.google.protobuf:protocuchen und dann eine geeignete Version für Ihre Plattform auswählen.

Kopieren Sie anschließend den Compiler in das Verzeichnissrc/mainIhres Projekts und führen Sie den folgenden Befehl in der Befehlszeile aus:

protoc --java_out=java resources/example.proto

Dies sollte eine Quelldatei für die KlasseexampleTraininginnerhalb des Paketscom.example.protobufgenerieren, wie in den Deklarationenoptionder Dateiexample.protoangegeben.

Zusätzlich zum Compiler ist die Laufzeit des Protokollpuffers erforderlich. Dies kann durch Hinzufügen der folgenden Abhängigkeit zur Maven-POM-Datei erreicht werden:


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

Wir können eine andere Version der Laufzeit verwenden, sofern diese mit der Compilerversion identisch ist. Die neueste Version finden Sie unterthis link.

2.3. Kompilieren einer Nachrichtenbeschreibung

Mithilfe eines Compilers werden Nachrichten in einer.proto-Datei in statisch verschachtelte Java-Klassen kompiliert. Im obigen Beispiel werden die NachrichtenCourse undStudent in Java-KlassenCourse bzw.Studentkonvertiert. Gleichzeitig werden die Felder der Nachrichten in JavaBeans-Stil-Getter und -Setter innerhalb dieser generierten Typen kompiliert. Die Markierung, die sich aus einem Gleichheitszeichen und einer Zahl am Ende jeder Felddeklaration zusammensetzt, ist das eindeutige Tag, mit dem das zugehörige Feld in der Binärform codiert wird.

Wir werden durch typisierte Felder der Nachrichten gehen, um zu sehen, wie diese in Zugriffsmethoden konvertiert werden.

Beginnen wir mit der NachrichtCourse. Es hat zwei einfache Felder, einschließlichid undcourse_name. Ihre Protokollpuffertypenint32 undstring werden in Javaint undString übersetzt. Hier sind die zugehörigen Getter nach der Kompilierung (wobei die Implementierungen der Kürze halber weggelassen wurden):

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

Beachten Sie, dass die Namen der eingegebenen Felder in Schlangenschrift geschrieben werden sollten (einzelne Wörter werden durch Unterstriche getrennt), um die Zusammenarbeit mit anderen Sprachen zu gewährleisten. Der Compiler konvertiert diese Namen nach Java-Konventionen in Kamel-Groß- / Kleinschreibung.

Das letzte Feld derCourse-Nachricht,student, ist vom komplexen TypStudent, der nachstehend beschrieben wird. Diesem Feld wird das Schlüsselwortrepeatedvorangestellt, was bedeutet, dass es beliebig oft wiederholt werden kann. Der Compiler generiert einige Methoden, die dem Feldstudent zugeordnet sind, wie folgt (ohne Implementierungen):

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

Nun fahren wir mit der NachrichtStudentfort, die als komplexer Typ des Feldsstudentder NachrichtCourseverwendet wird. Die einfachen Felder, einschließlichid,first_name,last_name undemail, werden zum Erstellen von Java-Zugriffsmethoden verwendet:

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

Das letzte Feld,phone, ist vom komplexen TypPhoneNumber. Ähnlich wie das Feldstudentder NachrichtCoursewiederholt sich dieses Feld und verfügt über mehrere zugeordnete Methoden:

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

Die NachrichtPhoneNumberwird in den verschachtelten TypexampleTraining.Student.PhoneNumberkompiliert, wobei zwei Getter den Feldern der Nachricht entsprechen:

public java.lang.String getNumber();
public com.example.protobuf.exampleTraining.Student.PhoneType getType();

PhoneType, der komplexe Typ destype-Felds derPhoneNumber-Nachricht, ist ein Aufzählungstyp, der in einen Javaenum-Typ umgewandelt wird, der inexampleTraining.Studentverschachtelt ist ) s Klasse:

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

3. Protobuf In Spring REST API

Dieser Abschnitt führt Sie durch das Einrichten eines REST-Service mit Spring Boot.

3.1. Bohnenerklärung

Beginnen wir mit der Definition unserer Haupt@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
}

DieProtobufHttpMessageConverter-Bean wird verwendet, um von@RequestMapping annotierten Methoden zurückgegebene Antworten in Protokollpuffernachrichten umzuwandeln.

Die andere Bean,CourseRepository, enthält einige Testdaten für unsere API.

Wichtig ist hierbei, dass wir mitProtocol Buffer specific data – not with standard POJOs arbeiten.

Hier ist die einfache Implementierung derCourseRepository:

public class CourseRepository {
    Map courses;

    public CourseRepository (Map courses) {
        this.courses = courses;
    }

    public Course getCourse(int id) {
        return courses.get(id);
    }
}

3.2. Controller-Konfiguration

Wir können die@Controller-Klasse für eine Test-URL wie folgt definieren:

@RestController
public class CourseController {
    @Autowired
    CourseRepository courseRepo;

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

Und wieder - das Wichtigste hier ist, dass das Kurs-DTO, das wir von der Controller-Schicht zurückgeben, kein Standard-POJO ist. Dies ist der Auslöser dafür, dass es in Protokollpuffernachrichten konvertiert wird, bevor es zurück zum Client übertragen wird.

4. REST-Clients und Tests

Nachdem wir uns nun die einfache API-Implementierung angesehen haben - lassen Sie uns nundeserialization of protocol buffer messages on the client side veranschaulichen - mit zwei Methoden.

Der erste nutzt dieRestTemplate-API mit einer vorkonfiguriertenProtobufHttpMessageConverter-Bean, um Nachrichten automatisch zu konvertieren.

Die zweite Methode verwendetprotobuf-java-format, um Protokollpufferantworten manuell in JSON-Dokumente umzuwandeln.

Zunächst müssen wir den Kontext für einen Integrationstest einrichten und Spring Boot anweisen, Konfigurationsinformationen in der KlasseApplicationzu finden, indem wir eine Testklasse wie folgt deklarieren:

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

Alle Codefragmente in diesem Abschnitt werden in die KlasseApplicationTesteingefügt.

4.1. Erwartete Antwort

Der erste Schritt, um auf einen REST-Service zuzugreifen, besteht darin, die Anforderungs-URL zu ermitteln:

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

DieseCOURSE1_URL werden verwendet, um den ersten Testdoppelkurs vom zuvor erstellten REST-Service zu erhalten. Nachdem eine GET-Anforderung an die oben angegebene URL gesendet wurde, wird die entsprechende Antwort mit den folgenden Behauptungen überprüft:

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

Wir werden diese Hilfsmethode in beiden Testfällen anwenden, die in den folgenden Abschnitten behandelt werden.

4.2. Testen mitRestTemplate

So erstellen wir einen Client, senden eine GET-Anforderung an das angegebene Ziel, empfangen die Antwort in Form von Protokollpuffernachrichten und überprüfen sie mithilfe derRestTemplate-API:

@Autowired
private RestTemplate restTemplate;

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

Damit dieser Testfall funktioniert, muss eine Bean vom TypRestTemplatein einer Konfigurationsklasse registriert sein:

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

Eine weitere Bean vom TypProtobufHttpMessageConverterist ebenfalls erforderlich, um die empfangenen Protokollpuffernachrichten automatisch zu transformieren. Diese Bohne entspricht der in Unterabschnitt 3.1 definierten Bohne. Da Client und Server in diesem Lernprogramm denselben Anwendungskontext verwenden, können wir dieRestTemplate-Bean in derApplication-Klasse deklarieren und dieProtobufHttpMessageConverter-Bean erneut verwenden.

4.3. Testen mitHttpClient

Der erste Schritt, um dieHttpClient-API zu verwenden und Protokollpuffermeldungen manuell zu konvertieren, besteht darin, der Maven-POM-Datei die folgenden zwei Abhängigkeiten hinzuzufügen:


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


    org.apache.httpcomponents
    httpclient
    4.5.2

Die neuesten Versionen dieser Abhängigkeiten finden Sie unterprotobuf-java-format undhttpclient Artefakte im zentralen Maven-Repository.

Fahren Sie fort, um einen Client zu erstellen, eine GET-Anforderung auszuführen und die zugehörige Antwort unter Verwendung der angegebenen URL in eineInputStream-Instanz zu konvertieren:

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

Jetzt konvertieren wir Protokollpuffermeldungen in Form einesInputStream-Objekts in ein JSON-Dokument:

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

Und so verwendet ein Testfall die oben deklarierten privaten Hilfsmethoden und validiert die Antwort:

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

4.4. Antwort in JSON

Zur Verdeutlichung sind hier JSON-Formulare der Antworten aufgeführt, die wir in den in den vorherigen Unterabschnitten beschriebenen Tests erhalten haben:

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. Fazit

In diesem Lernprogramm wurden Protokollpuffer schnell eingeführt und die Einrichtung einer REST-API unter Verwendung des Formats mit Spring veranschaulicht. Wir sind dann zum Client-Support und zum Serialisierungs-Deserialisierungs-Mechanismus übergegangen.

Die Implementierung aller Beispiele und Codefragmente finden Sie ina GitHub project.