0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

spring の WebClient で JSON のデコードに fastjson2 を使う

Posted at

環境

  • java 11
  • spring-boot 2.7
  • springframework 5.3

動機

あるサービスの開発中、通信先から返却された JSON レスポンスのデコードがボトルネックになっているという話を聞きました。
軽く調べると fastjson が速いらしいというベンチマークが。
fastjson の GitHub リポジトリに行くと、 fastjson2 が出たよ!もっと高速だよ!と書いてあったので、fastjson2 を導入してみました。

この記事は、その換装記録です。

結論を先に書くと、期待したほど速くならなかった ので採用は見送りました。
が、似たようなことをしたいという人がいるかもしれないのでログとして残しておきます。

換装方法

Spring 5.3 系の WebClient では JSON のデコードに jackson が使われています。
この Decoder 実装を上書きしてやることで fastjson2 で処理できるようになります。

Decoder 実装

FastJsonDecoder.java
  class FastJsonDecoder implements Decoder<Object> {
    private static final StringDecoder STRING_DECODER =
        StringDecoder.textPlainOnly(Arrays.asList(",", "\n"), false);

    private static final ResolvableType STRING_TYPE = ResolvableType.forClass(String.class);

    private static final List<MimeType> SUPPORTED_MIME_TYPES =
        ImmutableList.of(MediaType.APPLICATION_JSON);

    @Override
    public boolean canDecode(ResolvableType elementType, MimeType mimeType) {
      return SUPPORTED_MIME_TYPES.stream().anyMatch(m -> m.equalsTypeAndSubtype(mimeType));
    }

    @Override
    public Flux<Object> decode(
        Publisher<DataBuffer> inputStream,
        ResolvableType elementType,
        MimeType mimeType,
        Map<String, Object> hints) {
      Charset charset =
          Optional.ofNullable(mimeType).map(MimeType::getCharset).orElse(StandardCharsets.UTF_8);
      MimeType textMimeType = new MimeType(MimeTypeUtils.TEXT_PLAIN, charset);
      return STRING_DECODER
          .decode(inputStream, STRING_TYPE, textMimeType, null)
          .handle(
              (next, sink) -> {
                try {
                  // ここで fastjson2 の JSON クラスを使う
                  sink.next(JSON.parseObject(next, elementType.getType()));
                } catch (JSONException e) {
                  sink.error(e);
                }
              });
    }

    @Override
    public Mono<Object> decodeToMono(
        Publisher<DataBuffer> inputStream,
        ResolvableType elementType,
        MimeType mimeType,
        Map<String, Object> hints) {
      Charset charset =
          Optional.ofNullable(mimeType).map(MimeType::getCharset).orElse(StandardCharsets.UTF_8);
      MimeType textMimeType = new MimeType(MimeTypeUtils.TEXT_PLAIN, charset);
      return STRING_DECODER
          .decodeToMono(inputStream, STRING_TYPE, textMimeType, null)
          .handle(
              (next, sink) -> {
                try {
                  // ここで fastjson2 の JSON クラスを使う
                  sink.next(JSON.parseObject(next, elementType.getType()));
                } catch (JSONException e) {
                  sink.error(e);
                }
              });
    }

    @Override
    public List<MimeType> getDecodableMimeTypes() {
      return SUPPORTED_MIME_TYPES;
    }
  }
}

設定

WebClientConfig.java
@Configuration
public class WebClientConfig {
  @Bean
  public WebClient webClient() {
    return WebClient.builder()
      // Decoder を上書く
      .codecs(configurer -> configurer.defaultCodecs().jackson2JsonDecoder(new FastJsonDecoder()))
      .build();
  }
}

あとがき

JSON で巨大なデータをやり取りするとデコードのコストが無視できなくてヤバいですね。
昔、通信フォーマットとしての XML でも似たような問題があって、SAX だの Pull Parser だのいろいろ試しましたが、JSON にもパーサの種類っていろいろあるんでしょうか?

根本的に解決するには、MessagePack とか Protocol Buffer とか、別のフォーマットを採用した方がいいのかも知れません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?