【问题标题】:SpringWebFlux method on request payload no being invoked未调用请求有效负载上的 Spring WebFlux 方法
【发布时间】:2021-12-31 02:00:01
【问题描述】:

我有一个带有几个端点的 Web 服务,其中一个是 PUT 端点,它接收 XML 并向 Redis 服务器添加一些信息。

我的端点被映射到一个配置类中:

@Configuration 
public class RouterConfig {
    @Autowired
    private YAMLConfig yamlConfig;

    @Bean
    public RouterFunction<ServerResponse> getRoutes(final ServiceHandler serviceHandler) {
        return RouterFunctions.route()
            .GET(yamlConfig.getEnvPrefix() + STATUS_ENDPOINT, serviceHandler::getStatus)
            .GET(yamlConfig.getEnvPrefix() + STOCK_CAP_API + PRODUCT_CAPPING_ENDPOINT, serviceHandler::getStockCapForProducts)
            .PUT(yamlConfig.getEnvPrefix() + STOCK_CAP_API + ADD_PRODUCT_CAP_FOR_CUSTOMER, RequestPredicates.contentType(MediaType.APPLICATION_XML), serviceHandler::AddStockCap)
            .build();
    }
}

PUT 端点的处理程序方法如下所示:

@NonNull
public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
    log.info("Adding new stock caps");
    serverRequest.bodyToMono(CustomerCap.class)
        .flatMap(stockCapService::addCustomerStockCap)
        .doOnEach(result -> System.out.println("Cap added - " + result));
    return ServerResponse.ok().build();
}

bodyToMono 被映射到一个表示 XML 有效负载的类,如下所示,并且该类中的属性 Customer 也以相同的方式映射到一个类。

@XmlRootElement(name="customerCap")
@XmlAccessorType(XmlAccessType.FIELD)
public class CustomerCap {
    @XmlElement
    private List<Customer> customer;

    public List<Customer> getCustomer() {
        return customer;
    }

    public void setCustomer(List<Customer> customer) {
        this.customer = customer;
    }
}

addCustomerStockCap 的实现是

@Override
public Mono<Boolean> addCustomerStockCap(CustomerCap customerCap) {
    log.info("Starting adding process");
    customerCap.getCustomer().forEach(customer -> {
        final var customerUID = customer.getCustomerNumber();
        customer.getCap().forEach(cap -> {
            redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), CAP_LIMIT, String.valueOf(cap.getCustomerCapLimit()));
            redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_FROM, String.valueOf(cap.getCapValidFrom()));
            redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_TO, String.valueOf(cap.getCapValidTo()));
            setExpirationOnHash(customerUID, cap);
        });
    });
    return Mono.just(Boolean.TRUE);
}

处理端点的方法被正确调用,我可以在日志上看到“Addin new stock caps”消息,但是addCustomerStockCap从未被调用,没有抛出异常或错误,该方法根本不执行。

我做的另一个测试是创建一个控制器而不是一个功能端点。控制器方法最终是这样的

@PutMapping(STOCK_CAP_API + ADD_PRODUCT_CAP_FOR_CUSTOMER)
@ResponseStatus(HttpStatus.OK)
public void AddStockCap(@RequestBody CustomerCap customerCap) {
    log.info("Adding new stock caps");;
    stockCapService.addCustomerStockCap(customerCap);
}

当我这样做时,我在 XML 解析中遇到了一些错误,我只修复了将注释 @NoArgsConstructor 添加到 CustomerCap 和其他元素类。在此修复后,控制器/端点按预期工作。

问题仍然是我需要让它在功能端点上工作,我们有几个应用程序并且我们使用功能端点作为我们的标准,所以像这样只留下这个端点是没有意义的。

【问题讨论】:

    标签: spring-boot spring-webflux


    【解决方案1】:

    问题是您的 Mono 从未被订阅,因此不会发出任何内容,因此永远不会调用 stockCapService::addCustomerStockCap。以下应该有效:

    @NonNull
    public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
        log.info("Adding new stock caps");
        serverRequest.bodyToMono(CustomerCap.class)
            .flatMap(stockCapService::addCustomerStockCap)
            .doOnEach(result -> System.out.println("Cap added - " + result))
            .subscribe();
        return ServerResponse.ok().build();
    }
    

    或者

    @NonNull
    public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
        log.info("Adding new stock caps");
        return serverRequest.bodyToMono(CustomerCap.class)
            .flatMap(stockCapService::addCustomerStockCap)
            .doOnEach(result -> System.out.println("Cap added - " + result))
            .flatMap(ServerResponse.ok().build());
    }
    

    2021 年 11 月 21 日更新

    试试下面的。你真的不需要addCustomerStockCap 来返回Mono&lt;Boolean&gt;

    @Override
    public Boolean addCustomerStockCap(CustomerCap customerCap) {
        log.info("Starting adding process");
        customerCap.getCustomer().forEach(customer -> {
            final var customerUID = customer.getCustomerNumber();
            customer.getCap().forEach(cap -> {
                redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), CAP_LIMIT, String.valueOf(cap.getCustomerCapLimit()));
                redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_FROM, String.valueOf(cap.getCapValidFrom()));
                redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_TO, String.valueOf(cap.getCapValidTo()));
                setExpirationOnHash(customerUID, cap);
            });
        });
        return Boolean.TRUE;
    }
    

    现在你可以稍微简化你的AddStockCapflatMap 变成map):

    @NonNull
    public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
        log.info("Adding new stock caps");
        return serverRequest.bodyToMono(CustomerCap.class)
            .map(stockCapService::addCustomerStockCap)
            .flatMap(ServerResponse.ok().build());
    }
    

    【讨论】:

    • 也不行。实际上,我尝试了几种不同的方法,当我尝试这个时,我收到错误 415,应用程序无法接受 XML,为了修复它我将 @XMLElement 和其他相关注释添加到修复错误的 CustomerCap.class,但仍然, addcustomerStockCap 未执行。我的感觉是身体到 CustomerCap 的映射有问题
    • addcustomerStockCap 是否返回任何内容?您可以将其实现添加到问题中吗?谢谢!
    • 我添加了实现,现在我让它只返回一个布尔值,谢谢。
    • 由于它在“常规”控制器上工作,它应该按照我的建议工作。您是否尝试过这两种选择?
    • 是的,但结果相同,没有任何反应
    【解决方案2】:

    对我来说确保调用该方法的唯一方法是将其结果添加为响应正文。

    有问题的方法最终是这样的:

    @Override
    public Boolean addCustomerStockCap(CustomerCap customerCap) {
      log.info("Starting adding process");
      customerCap.getCustomer().forEach(customer -> {
          final var customerUID = customer.getCustomerNumber();
          customer.getCap().forEach(cap -> {
              redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), CAP_LIMIT, String.valueOf(cap.getCustomerCapLimit()));
              redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_FROM, String.valueOf(cap.getCapValidFrom()));
              redisCommands.hset(String.join(SEP, STOCK_CAP, customerUID, cap.getMaterialNumber()), VALID_TO, String.valueOf(cap.getCapValidTo()));
              setExpirationOnHash(customerUID, cap);
          });
      });
      return Boolean.TRUE;
    }
    

    还有这样的处理程序

    @NonNull
    public Mono<ServerResponse> AddStockCap(ServerRequest serverRequest) {
        log.info("Adding new stock caps");
        return ServerResponse.ok().body(serverRequest.bodyToFlux(CustomerCap.class).map(stockCapService::addCustomerStockCap), Boolean.class);
    }
    

    不理想,因为此端点的目标是只返回没有正文的 200 代码,但这是一种解决方法,直到找到另一种调用它的方法。

    同样在 spring 文档中,对于 XML 和 JSON 有效负载,我们应该使用 Flux 而不是 Mono

    以下示例将主体提取到 Flux(或 Kotlin 中的 Flow),其中 Person 对象从某种序列化形式(例如 JSON 或 XML)解码:

    https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-fn-handler-functions

    【讨论】:

      猜你喜欢
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 2017-09-10
      • 1970-01-01
      • 1970-01-01
      • 2020-04-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多