環境
- java 11
- spring-boot 2.7
- springframework 5.3
動機
あるサービスの開発中、通信先から返却された JSON レスポンスのデコードがボトルネックになっているという話を聞きました。
軽く調べると fastjson が速いらしいというベンチマークが。
fastjson の GitHub リポジトリに行くと、 fastjson2 が出たよ!もっと高速だよ!と書いてあったので、fastjson2 を導入してみました。
この記事は、その換装記録です。
結論を先に書くと、期待したほど速くならなかった ので採用は見送りました。
が、似たようなことをしたいという人がいるかもしれないのでログとして残しておきます。
換装方法
Spring 5.3 系の WebClient では JSON のデコードに jackson が使われています。
この Decoder 実装を上書きしてやることで fastjson2 で処理できるようになります。
Decoder 実装
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;
}
}
}
設定
@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 とか、別のフォーマットを採用した方がいいのかも知れません。