Spring REST APIのバイナリデータフォーマット

1.概要

JSONとXMLは、REST APIに関しては広く普及しているデータ転送フォーマットですが、利用できる唯一のオプションではありません。

さまざまな程度のシリアライゼーション速度およびシリアライズされたデータサイズを有する他の多くのフォーマットが存在する。

この記事では、バイナリデータフォーマットを使用するためのSpring RESTメカニズムを設定する方法を探ります。

さらに、Googleプロトコルバッファのサポートを追加して、複数のデータ形式をサポートする方法を示します。

2. HttpMessageConverter

HttpMessageConverter インターフェースは基本的に、RESTデータ形式の変換用のSpringの公開APIです。

目的のコンバーターを指定する方法はいくつかあります。ここでは WebMvcConfigurer を実装し、オーバーライドされた configureMessageConverters メソッドで使用したいコンバーターを明示的に提供します。

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

3. Kryo

3.1. Kryoの概要とMaven

Kryoは、テキストベースの形式と比較して、シリアル化とシリアル化解除の速度が速く、転送データサイズが小さいバイナリエンコード形式です。

理論的にはさまざまな種類のシステム間でデータを転送するために使用できますが、主にJavaコンポーネントと連携するように設計されています。

以下のMaven依存関係を持つ必要なKryoライブラリを追加します。

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

kryo の最新バージョンを確認するにはhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com.esotericsoftware%22%20AND%20a%3A%22kryo%22[aここを見て]。

3.2. Kryo in Spring REST

データ転送フォーマットとしてKryoを利用するために、カスタムの HttpMessageConverter を作成し、必要なシリアライゼーションおよびデシリアライゼーションロジックを実装します。また、KryoのカスタムHTTPヘッダーを定義します。

application/x-kryo これがデモンストレーション目的で使用する完全に簡略化された作業例です。

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

ここでは ThreadLocal を使用していることに注意してください。Kryoインスタンスの作成にはコストがかかる可能性があるためです。できるだけこれらを再利用したいのです。

コントローラの方法は簡単です(カスタムのプロトコル固有のデータ型は必要ありません。プレーンの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<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.複数データフォーマットのサポート

多くの場合、同じサービスに対して複数のデータフォーマットをサポートしたいと思うでしょう。クライアントは Accept HTTPヘッダーで目的のデータ形式を指定し、対応するメッセージコンバータがデータをシリアル化するために呼び出されます。

通常は、箱から出して動作するために別のコンバータを登録するだけです。 Springは Accept ヘッダの値とコンバータで宣言されているサポートされているメディアタイプに基づいて適切なコンバータを自動的に選択します。

たとえば、JSONとKryoの両方のサポートを追加するには、 KryoHttpMessageConverter MappingJackson2HttpMessageConverter の両方を登録します。

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

それでは、Google Protocol Bufferをリストに追加したいとしましょう。この例では、次の proto ファイルに基づいて protoc コンパイラで生成されたクラス FooProtos.Foo があるとします。

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はプロトコルバッファをサポートしています。機能させるために必要なのは、サポートされているコンバータのリストに ProtobufHttpMessageConverter を含めることだけです。

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

ただし、 FooProtos.Foo インスタンスを返す別のコントローラーメソッドを定義する必要があります(JSONとKryoはどちらも __Foo __sを処理するため、2つを区別するためにコントローラーに変更は必要ありません)。

どのメソッドが呼び出されるかについてのあいまいさを解決するには2つの方法があります。最初のアプローチはprotobufと他のフォーマットのために異なるURLを使うことです。例えば、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 =“/fooprotos/\ {id}” を使用し、他のフォーマットには value =“/foos/\ {id}” を使用します。

  • 2番目の、そしてより良いアプローチは同じURLを使うことですが、protobufのためのリクエストマッピングで生成されたデータフォーマットを明示的に指定することです:**

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

produces notation属性でメディアタイプを指定することによって、クライアントによって提供された Accept ヘッダーの値に基づいてどのマッピングを使用する必要があるかについての基礎となるSpringメカニズムへのヒントを与えます。 “ foos/\ {id}” URLに対して呼び出されます。

2番目の方法では、すべてのデータ形式について、統一された一貫性のあるREST APIをクライアントに提供できます。

最後に、Spring REST APIでプロトコルバッファをより深く使用することに興味があるなら、リンクを見てください:/spring-rest-api-with-protocol-buffers[参照記事]

5.追加のメッセージコンバータを登録する

configureMessageConverters メソッドをオーバーライドすると、デフォルトのメッセージコンバータがすべて失われることに注意してください。

あなたが提供するものだけが使用されます。

時々これはまさにあなたが望むものですが、多くの場合あなたは新しいコンバータを追加したいだけで、それでもJSONのような標準的なデータフォーマットの世話をしているデフォルトのものを保ちます。これを実現するには、 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.まとめ

このチュートリアルでは、Spring MVCで任意のデータ転送フォーマットを使用することがいかに簡単であるかを見て、例としてKryoを使用してこれを調べました。

また、さまざまなクライアントがさまざまな形式を使用できるように、複数の形式のサポートを追加する方法も示しました。

SpringのREST APIチュートリアルhttps://github.com/eugenp/tutorials/tree/master/spring-rest[にあるこのバイナリデータフォーマットの実装はもちろんGithubにあります]。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。