如何自定义SpringWebFlux WebClient的JSON反序列化?

71
我将使用 spring-webflux WebClient(生成版本为20170502.221452-172)来访问一个Web应用程序,该应用程序生成以如下格式的Entry对象流(application/stream+json):
final WebClient producerClient = WebClient.create("http://localhost:8080/");

Flux<Entry> entries = producerClient.get().uri("json-stream")
        .accept(MediaType.APPLICATION_STREAM_JSON)
        .exchange()
        .flatMapMany(clientResponse -> clientResponse.bodyToFlux(Entry.class));

虽然使用标准常用类型(包括Java时间(JSR-310)数据类型,如java.time.Instant)的POJO的反序列化正常工作,但我想知道为了添加任何自定义JSON到Java反序列化(例如自定义Jackson ObjectMapper),我需要做什么。

我找不到WebClient中的任何API或由其构建器和流畅API生成的对象的类来执行此操作。

有人使用过具有自定义反序列化的WebClient吗?

(也许API还没有出现?)

9个回答

76

下面是一个定制ObjectMapper进行JSON (反)序列化的示例。请注意,为了流式处理,使用了不同的编码器/解码器,但其配置原则保持不变。

    ExchangeStrategies strategies = ExchangeStrategies
            .builder()
            .codecs(clientDefaultCodecsConfigurer -> {
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));

            }).build();

    WebClient webClient = WebClient.builder().exchangeStrategies(strategies).build();

7
为什么 Jackson2ObjectMapperBuilderCustomizer 不适用于默认编解码器? - hahn
这对我来说听起来像是一个新问题 - 你能创建一个吗? - Brian Clozel
11
如果您使用Spring提供的预配置WebClient.Builder而不是Webclient.builder(),则可以自动完成objectMapper定制。 - Saisurya Kattamuri
对于像我这样认为两个配置器是相同的人来说,只是提个醒。 不,它们并不相同!一个是“编码器”的一行,另一个是“解码器”的一行。 - undefined

14

根据上面的回复,我最终得到了这段代码:

final ObjectMapper mapper = new ObjectMapper()
    .findAndRegisterModules()
    .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
    .codecs(configurer -> configurer.defaultCodecs()
    .jackson2JsonDecoder(new Jackson2JsonDecoder(mapper)))
    .build();
final WebClient webClient = WebClient.builder()
    .exchangeStrategies(exchangeStrategies)
    .build();

如果你不包含.findAndRegisterModules(),当你想要反序列化像Java 8的时间对象一样的东西时,你会遇到问题。


10

您可以为特定的WebClient进行配置:

@Autowired
public ItunesAlbumServiceImpl(ObjectMapper mapper) {
    ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(clientCodecConfigurer ->
        clientCodecConfigurer.customCodecs().decoder(
                new Jackson2JsonDecoder(mapper,
                        new MimeType("text", "javascript", StandardCharsets.UTF_8)))
    ).build();

    webClient = WebClient.builder()
            .exchangeStrategies(strategies)
            .baseUrl("https://itunes.apple.com")
            .build();
}

同时也可以在“应用程序层”上通过配置CodecCustomizer来实现:

@Bean
public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) {
    return (configurer) -> {
        MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
        CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
        customCodecs.decoder(
                new Jackson2JsonDecoder(mapper, textJavascript));
        customCodecs.encoder(
                new Jackson2JsonEncoder(mapper, textJavascript));
    };
}

这将由 WebClientAutoConfiguration 生效,作为一个 WebClient.Builder bean:

@Autowired
public ItunesAlbumServiceImpl(WebClient.Builder webclientBuilder) {
    webClient = webclientBuilder.baseUrl("https://itunes.apple.com").build();
}

8

全局配置:

@Configuration
public class AppConfig {

    private final ObjectMapper objectMapper;

    @Autowired
    public AppConfig(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        this.webClientBuilder = WebClient.builder()
                .exchangeStrategies(exchangeStrategies());
    }

    private ExchangeStrategies exchangeStrategies() {
        Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(objectMapper);
        Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper);
        return ExchangeStrategies
                .builder()
                .codecs(configurer -> {
                    configurer.defaultCodecs().jackson2JsonEncoder(encoder);
                    configurer.defaultCodecs().jackson2JsonDecoder(decoder);
                }).build();
    }
}

6
自 Spring 5.1.13 版本起,您可以使用专门的 .codec 方法来自定义它们:
WebClient.builder()
    .codecs(configurer -> {
        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
     })
    .build();

谢谢你,我应该更新问题,加入更近期Spring版本的代码... - Martin

5

使用Webflux 5.0.2,取消注册默认设置

val strategies = ExchangeStrategies.builder()
                .codecs { configurer ->
                    configurer.registerDefaults(false)
                    configurer.customCodecs().encoder(Jackson2JsonEncoder(objectMapper, APPLICATION_JSON))
                    configurer.customCodecs().decoder(Jackson2JsonDecoder(objectMapper, APPLICATION_JSON))
                }.build()

这个问题被标记为 Java 而不是 Kotlin - Stefan Haberl

1
如果有人在测试期间对WebTestClient进行自定义,那么可以采用类似的方法。只需要修改原始bean即可。
@Autowired
private WebTestClient webTestClient;

@Test
void test() {
    webTestClient.mutate()
        .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs()
            .jackson2JsonDecoder(new Jackson2JsonDecoder(CUSTOM_OBJECT_MAPPER, MediaType.APPLICATION_JSON)))
        .build()
        .get()
        .uri("/test-endpoint")
        .exchange()
        .expectStatus().isOk();


0

如果你使用Spring Boot,那么你可以使用自动配置的ObjectMapper并省略冗长的编解码器配置。只需像这样定义bean:

@Bean
public WebClient webClient(WebClient.Builder builder) {
    return builder.build();
}

正如我在这里解释的那样,Spring Boot会自动配置WebClient builder。当您手动创建实例时,它会使用内置的默认值,然后您需要使用上述方法之一手动更新编解码器。


0

我找到的最直接、简洁和精确的方法,是在 Baeldung 的 Spring Boot: 自定义 Jackson ObjectMapper 中读到的。要使用 Jackson2ObjectMapperBuilderCustomizer,代码如下:

@Configuration
public class MyAppConfiguration {

  @Bean
  public Jackson2ObjectMapperBuilderCustomizer jacksonJsonCustomizer() {
    return builder -> builder.modulesToInstall(/*TODO specify modules here);
  }

}

我已经验证了这个在Spring Data和WebFlux中可以工作,例如WebClient.bodyToMono(SomeType.class)。它甚至可以与WebClientCustomizer结合使用,如果你恰好在自定义WebClient时使用它,请参考答案Spring WebClient.Builder timeout defaults and overrides for runtimes


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接