1. 概要

JSONとXMLは、REST APIに関しては広く普及しているデータ転送形式ですが、使用できるオプションはこれらだけではありません。

さまざまな程度のシリアル化速度とシリアル化されたデータサイズを持つ他の多くの形式が存在します。

この記事では、バイナリデータ形式を使用するように aSpringRESTメカニズムを構成する方法について説明します。これをKryoで説明します。

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

2. HttpMessageConverter

HttpMessageConverter インターフェースは、基本的にSpringのRESTデータ形式の変換用のパブリックAPIです。

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

@Configuration
@EnableWebMvc
@ComponentScan({ "com.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 の最新バージョンを確認するには、こちらをご覧ください

3.2. SpringRESTのKryo

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インスタンスの作成にコストがかかる可能性があるためです。また、これらを可能な限り再利用したいと考えています。

コントローラの方法は単純です(カスタムプロトコル固有のデータ型は必要ないことに注意してください。プレーンな 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<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の両方のサポートを追加するには、KryoHttpMessageConverterMappingJackson2HttpMessageConverterの両方を登録します。

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

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

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には、ProtocolBufferのサポートが組み込まれています。 それを機能させるために必要なのは、サポートされているコンバーターのリストに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 を処理するため、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 アノテーション属性でメディアタイプを指定することにより、によって提供される Accept ヘッダーの値に基づいて、どのマッピングを使用する必要があるかについて、基盤となるSpringメカニズムにヒントを与えることに注意してください。クライアントであるため、“ foos / {id}”URLに対してどのメソッドを呼び出す必要があるかについてのあいまいさはありません。

2番目のアプローチでは、すべてのデータ形式のクライアントに統一された一貫性のあるRESTAPIを提供できます。

最後に、Spring REST APIでプロトコルバッファを使用することに興味がある場合は、リファレンス記事を参照してください。

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

configureMessageConverters メソッドをオーバーライドすると、デフォルトのメッセージコンバーターがすべて失われることに注意することが非常に重要です。 ご提供いただいたもののみを使用します。

これがまさにあなたが望むものである場合もありますが、多くの場合、JSONなどの標準データ形式をすでに処理しているデフォルトのコンバーターを維持しながら、新しいコンバーターを追加したいだけです。 これを実現するには、extendMessageConvertersメソッドをオーバーライドします。

@Configuration
@EnableWebMvc
@ComponentScan({ "com.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チュートリアルでのこのバイナリデータ形式の実装は、もちろんGithubにあります。 これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。