概述

在这个教程中,我们将探讨在Spring WebFlux应用中遇到DataBufferLimitException的原因,并讨论解决此问题的不同方法。

2. 问题理解

2.1. DataBufferLimitException是什么?

为了防止应用程序内存问题,Spring WebFlux库默认对内存中的数据缓冲设置了限制。这个默认值是262,144字节。如果我们的应用场景需要处理更大的数据量,就可能导致DataBufferLimitException

2.2. 什么是Codec

spring-webspring-core模块通过非阻塞I/O和反应式流后压力支持将字节内容序列化和反序列化为更高层次的对象。编码器(Codecs)提供了Java序列化之外的另一种选择。一个优点是,通常对象不需要实现Serializable接口。

3. 服务器端

3.1. 重现问题

让我们尝试发送一个390KB大小的JSON负载到Spring WebFlux服务器应用,以引发异常。我们可以使用curl命令发送POST请求到服务器:

curl --location --request POST 'http://localhost:8080/1.0/process' \
  --header 'Content-Type: application/json' \
  --data-binary '@/tmp/390KB.json'

正如我们所见,DataBufferLimitException被抛出:

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
  at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:99) ~[spring-core-5.3.23.jar:5.3.23]
  Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
  *__checkpoint ⇢ HTTP POST "/1.0/process" [ExceptionHandlingWebHandler]

3.2. 解决方案

我们可以使用WebFluxConfigurer接口来配置相同的阈值。为此,我们需要添加一个新的配置类,名为WebFluxConfiguration:

@Configuration
public class WebFluxConfiguration implements WebFluxConfigurer {
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(500 * 1024);
    }
}

还需要更新我们的应用程序属性:

spring:
  codec:
    max-in-memory-size: 500KB

4. 客户端侧

4.1. 重现问题

现在让我们看看客户端行为。我们将尝试使用WebFlux的WebClient重现同样的行为。让我们创建一个处理器,它向服务器发送一个390KB的负载:

public Mono<Users> fetch() {
    return webClient
      .post()
      .uri("/1.0/process")
      .body(BodyInserters.fromPublisher(readRequestBody(), Users.class))
      .exchangeToMono(clientResponse -> clientResponse.bodyToMono(Users.class));
}

我们再次看到相同的异常,但这次是因为webClient试图发送超过允许的最大负载:

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
  at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:99) ~[spring-core-5.3.23.jar:5.3.23]
  Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ Body from POST http://localhost:8080/1.0/process [DefaultClientResponse]
    *__checkpoint ⇢ Handler com.baeldung.spring.reactive.springreactiveexceptions.handler.TriggerHandler@428eedd9 [DispatcherHandler]
    *__checkpoint ⇢ HTTP POST "/1.0/trigger" [ExceptionHandlingWebHandler]

4.2. 解决方案

我们也有一种编程方式来配置WebClient以达到这个目标。让我们创建一个配置的WebClient

@Bean("progWebClient")
    public WebClient getProgSelfWebClient() {
        return WebClient
          .builder()
          .baseUrl(host)
          .exchangeStrategies(ExchangeStrategies
      .builder()
      .codecs(codecs -> codecs
            .defaultCodecs()
            .maxInMemorySize(500 * 1024))
        .build())
          .build();
}

同样,我们需要更新应用程序属性:

spring:
  codec:
    max-in-memory-size: 500KB

这样,我们的应用就应该能够发送超过500KB的负载了。值得注意的是,这个配置会影响到整个应用,包括所有的WebClient和服务器本身。

因此,如果我们只想为特定的WebClient配置这个限制,那么这可能不是一个理想的解决方案。此外,这种方法有一个局限性,即创建WebClient的Builder必须由Spring自动注入,如下所示:

@Bean("webClient")
  public WebClient getSelfWebClient(WebClient.Builder builder) {
  return builder.baseUrl(host).build();
}

5. 总结

在这篇文章中,我们了解了DataBufferLimitException是什么,并学习了如何在服务器和客户端两侧修复它。我们针对属性配置和编程方式提供了两种方法。希望这个异常不再困扰你。

如往常一样,完整的代码可以在GitHub上找到。