1. 概要

Protocol Buffers は、構造化データのシリアル化と逆シリアル化のための言語とプラットフォームに依存しないメカニズムであり、その作成者であるGoogleによって、XMLやJSON。

このチュートリアルでは、このバイナリベースのメッセージ構造を利用するためのRESTAPIの設定について説明します。

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ファイルはsyntax=“ proto3”で始まる必要があります。宣言。 コンパイラバージョン2が使用されている場合、この宣言は省略されます。 次に、 package 宣言があります。これは、他のプロジェクトとの名前の競合を回避するためのこのメッセージ構造の名前空間です。

次の2つの宣言はJavaでのみ使用されます。java_packageオプションは、生成されたクラスが存在するパッケージを指定し、 java_outer_classname オプションは、これで定義されたすべてのタイプを囲むクラスの名前を示します.protoファイル。

以下のサブセクション2.3では、残りの要素と、それらがJavaコードにコンパイルされる方法について説明します。

2.2. Javaを使用したプロトコルバッファ

メッセージ構造を定義した後、この言語に依存しないコンテンツをJavaコードに変換するコンパイラが必要です。 Protocol Buffersリポジトリの指示に従って、適切なコンパイラバージョンを取得できます。 または、 com.google.protobuf:protoc アーティファクトを検索し、プラットフォームに適したバージョンを選択して、Maven中央リポジトリからビルド済みのバイナリコンパイラをダウンロードすることもできます。

次に、コンパイラをプロジェクトの src / main ディレクトリにコピーし、コマンドラインで次のコマンドを実行します。

protoc --java_out=java resources/baeldung.proto

これにより、baeldungのoption 宣言で指定されているように、com.baeldung.protobufパッケージ内のBaeldungTrainingクラスのソースファイルが生成されます。 protoファイル。

コンパイラに加えて、ProtocolBuffersランタイムが必要です。 これは、MavenPOMファイルに次の依存関係を追加することで実現できます。

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.0.0-beta-3</version>
</dependency>

コンパイラのバージョンと同じであれば、別のバージョンのランタイムを使用できます。 最新のものについては、このリンクをチェックしてください。

2.3. メッセージの説明のコンパイル

コンパイラを使用することにより、.protoファイル内のメッセージは静的にネストされたJavaクラスにコンパイルされます。 上記の例では、CourseおよびStudentメッセージはそれぞれCourseおよびStudentJavaクラスに変換されます。 同時に、メッセージのフィールドは、生成された型内のJavaBeansスタイルのゲッターおよびセッターにコンパイルされます。 各フィールド宣言の最後にある等号と数字で構成されるマーカーは、関連するフィールドをバイナリ形式でエンコードするために使用される一意のタグです。

メッセージの型指定されたフィールドをウォークスルーして、それらがアクセサメソッドにどのように変換されるかを確認します。

Courseメッセージから始めましょう。 idcourse_nameを含む2つの単純なフィールドがあります。 それらのプロトコルバッファタイプint32およびstringは、Java intおよびStringタイプに変換されます。 コンパイル後の関連するゲッターは次のとおりです(簡潔にするために実装は省略されています)。

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

他の言語との連携を維持するために、型指定されたフィールドの名前はスネークケース(個々の単語はアンダースコア文字で区切られます)にする必要があることに注意してください。 コンパイラは、Javaの規則に従って、これらの名前をキャメルケースに変換します。

Courseメッセージの最後のフィールドであるstudentは、Student複合タイプです。これについては以下で説明します。 このフィールドの前にはrepeatedキーワードが付いています。これは、このフィールドを何度でも繰り返すことができることを意味します。 コンパイラは、 student フィールドに関連付けられたいくつかのメソッドを次のように生成します(実装なし)。

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

次に、 Student メッセージに移ります。これは、Courseメッセージのstudentフィールドの複合型として使用されます。 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();

最後のフィールドphoneは、PhoneNumber複合型です。 Courseメッセージのstudentフィールドと同様に、このフィールドは反復的であり、いくつかの関連するメソッドがあります。

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

PhoneNumber メッセージは、 BaeldungTraining.Student.PhoneNumber ネストされたタイプにコンパイルされ、メッセージのフィールドに対応する2つのゲッターがあります。

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

PhoneNumberメッセージのtypeフィールドの複合型であるPhoneTypeは列挙型であり、Javaenumに変換されます。 BaeldungTraining.Student クラス内にネストされたタイプ:

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

3. SpringRESTAPIのProtobuf

このセクションでは、SpringBootを使用してRESTサービスを設定する方法について説明します。

3.1. Bean宣言

メインの@SpringBootApplicationの定義から始めましょう。

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

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

    // Other declarations
}

ProtobufHttpMessageConverter Beanは、@RequestMapping注釈付きメソッドによって返される応答をプロトコルバッファメッセージに変換するために使用されます。

もう1つのBeanCourseRepository には、APIのテストデータが含まれています。

ここで重要なのは、標準のPOJO ではなく、ProtocolBuffer固有のデータを使用していることです。

CourseRepositoryの簡単な実装は次のとおりです。

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

3.2. コントローラの構成

テストURLの@Controllerクラスは次のように定義できます。

@RestController
public class CourseController {
    @Autowired
    CourseRepository courseRepo;

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

ここでも重要なのは、コントローラーレイヤーから返されるコースDTOが標準のPOJOではないということです。 これが、クライアントに転送される前にプロトコルバッファメッセージに変換されるトリガーになります。

4. RESTクライアントとテスト

簡単なAPI実装を見てきたので、2つの方法を使用したクライアント側でのプロトコルバッファメッセージの逆シリアル化を説明しましょう。

1つ目は、 RestTemplateAPIと事前構成されたProtobufHttpMessageConverter Beanを利用して、メッセージを自動的に変換します。

2つ目は、 protobuf-java-format を使用して、プロトコルバッファ応答をJSONドキュメントに手動で変換することです。

まず、統合テストのコンテキストを設定し、次のようにテストクラスを宣言して、Applicationクラスで構成情報を検索するようにSpringBootに指示する必要があります。

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

このセクションのすべてのコードスニペットは、ApplicationTestクラスに配置されます。

4.1. 期待される応答

RESTサービスにアクセスするための最初のステップは、リクエストURLを決定することです。

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

このCOURSE1_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("john.doe@baeldung.com"));
    assertThat(response, containsString("richard.roe@baeldung.com"));
    assertThat(response, containsString("jane.doe@baeldung.com"));
    assertThat(response, containsString("phone"));
    assertThat(response, containsString("number"));
    assertThat(response, containsString("type"));
}

以降のサブセクションで説明する両方のテストケースで、このヘルパーメソッドを使用します。

4.2. RestTemplateを使用したテスト

クライアントを作成し、指定された宛先にGETリクエストを送信し、プロトコルバッファメッセージの形式で応答を受信し、 RestTemplateAPIを使用して確認する方法は次のとおりです。

@Autowired
private RestTemplate restTemplate;

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

このテストケースを機能させるには、RestTemplateタイプのBeanを構成クラスに登録する必要があります。

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

受信したプロトコルバッファメッセージを自動的に変換するには、ProtobufHttpMessageConverterタイプの別のBeanも必要です。 このBeanは、サブセクション3.1で定義されているものと同じです。 このチュートリアルでは、クライアントとサーバーが同じアプリケーションコンテキストを共有しているため、ApplicationクラスでRestTemplate Beanを宣言し、ProtobufHttpMessageConverterBeanを再利用できます。

4.3. HttpClientを使用したテスト

HttpClient APIを使用し、プロトコルバッファメッセージを手動で変換する最初のステップは、次の2つの依存関係をMavenPOMファイルに追加することです。

<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>

これらの依存関係の最新バージョンについては、Maven中央リポジトリーのprotobuf-java-formatおよびhttpclientアーティファクトを参照してください。

次に、クライアントを作成し、GETリクエストを実行し、指定されたURLを使用して関連するレスポンスをInputStreamインスタンスに変換しましょう。

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: "john.doe@baeldung.com"
    phone {
        number: "123456"
    }
}
student {
    id: 2
    first_name: "Richard"
    last_name: "Roe"
    email: "richard.roe@baeldung.com"
    phone {
        number: "234567"
        type: LANDLINE
    }
}
student {
    id: 3
    first_name: "Jane"
    last_name: "Doe"
    email: "jane.doe@baeldung.com"
    phone {
        number: "345678"
    }
    phone {
        number: "456789"
        type: LANDLINE
    }
}

5. 結論

このチュートリアルでは、プロトコルバッファを簡単に紹介し、Springの形式を使用したRESTAPIの設定について説明しました。 次に、クライアントサポートとシリアル化-逆シリアル化メカニズムに移行しました。

すべての例とコードスニペットの実装は、GitHubプロジェクトにあります。