如何在 Webflux 的另一个异步方法中进行异步调用?

时间:2021-01-08 17:19:48

标签: java reactive-programming spring-webflux project-reactor

问题的解释有点长。请花一点时间帮忙!

我有两个 http 调用,它们将提供以下数据。

第一个 http 请求调用将返回 <Mono<List<Chips>>

[
  {
    "id": 1,
    "name": "redlays"
  },
  {
    "id": 2,
    "name": "yellowlays"
  },
  {
    "id": 3,
    "name": "kurkure"
  }
]

Chips 模型是

@Data
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Chips {
    private int id;
    private String name;
}

第二个 http 请求调用将根据 Mono<ChipsDetails>

返回 Id
{
    "id": 1,
    "color": "red",
    "grams": "50"
}

ChipsDetails 模型如下,

@Data
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ChipsDetails {
    private int id;
    private String color;
    private String grams;
}

我已经使用 Webflux 完成了实现。这里我使用了三个模型,分别是 ChipsChipsDetailsChipsFullDetails

模型 Chips 将具有两个属性 idname 然后模型 ChipsDetails 将具有三个属性 idcolor 和 {{1 }} 而模型 grams 将具有 ChipsFullDetailsChips 属性的组合,它们是 ChipsDetailsidnamecolor

grams

此处 @RestController @RequestMapping("/chips") public class ChipsController { @Autowired ChipsService chipsService; @GetMapping public Mono<List<ChipsFullDetails>> getAllChips() { return chipsService.getChips() .map(f -> { List<ChipsFullDetails> chipsFullDetails = new ArrayList<>(); f.forEach(a -> { ChipsFullDetails chipsFullDetail = new ChipsFullDetails(); chipsFullDetail.setId(a.getId()); chipsFullDetail.setName(a.getName()); chipsService.getChipsDetails(a.getId()) .subscribe(b -> { chipsFullDetail.setColor(b.getColor()); chipsFullDetail.setGrams(b.getGrams()); }); chipsFullDetails.add(chipsFullDetail); }); return chipsFullDetails; } ); } } 将返回 chipsService.getChips() 这是第一次调用,Mono<List<Chips>> 将返回 chipsService.getChipsDetails(a.getId()) 这是第二次 http 请求调用。

执行的结果将是 Mono<ChipsDetails>

ChipsFullDetails

问题是 @Data @Setter @Getter @NoArgsConstructor @AllArgsConstructor public class ChipsFullDetails { private int id; private String name; private String color; private String grams; } ChipsFullDetailscolor 属性返回 null,即使它是在内部订阅的,我们也可以从第二个 http 调用中获取这些属性。

如何以异步方式实现第二次 Http 调用即 grams 取决于第一个 http 调用(chipsService.getChipsDetails(a.getId()))的结果?

这是否可以在不阻塞两个调用的情况下实现?

2 个答案:

答案 0 :(得分:4)

我会先将初始 Mono<List<Chips>> 转换为 Flux<Chips>,以便您可以在每个元素上 flatMap,例如类似的东西:

public Mono<List<ChipsFullDetails>> getAllChips() {
    return chipsService
            .getChips()
            // Mono<List> to Flux:
            .flatMapIterable(Function.identity())
            // flat map on each element:
            .flatMap(this::buildChipsFullDetails)
            // Flux back to Mono<List>:
            .collectList();
}

private Mono<ChipsFullDetails> buildChipsFullDetails(Chips chips) {
    return chipsService
            .getChipsDetails(chips.getId())
            // once you get both chips and details, aggregate:
            .map(details -> buildChipsFullDetails(chips, details));
}

private ChipsFullDetails buildChipsFullDetails(Chips chips, ChipsDetails details) {
    // straightforward synchronous code:
    ChipsFullDetails chipsFullDetail = new ChipsFullDetails();
    chipsFullDetail.setId(chips.getId());
    chipsFullDetail.setName(chips.getName());
    chipsFullDetail.setColor(details.getColor());
    chipsFullDetail.setGrams(details.getGrams());
    return chipsFullDetail;
}

答案 1 :(得分:0)

我基本上不同意使用 Flux 的想法,但我承认我也有。

我想说的是,如果您想获取芯片列表的详细信息,那么您应该创建一个端点来执行此操作。那么这将是一个单一的调用。

对于你原来的问题,有一种方法可以不用 Flux,但它读起来有点搞笑:

ParameterizedTypeReference<List<Chip>> chipList = new ParameterizedTypeReference<List<Chip>>() {};

public Mono<List<ChipDetails>> getChipDetails() {
    return webClient.get().uri("chips").retrieve().bodyToMono(chipList).flatMap(chips -> {
        return Mono.zip(chips.stream().map(chip -> webClient.get().uri("chipDetails?id="+chip.getId()).retrieve().bodyToMono(ChipDetails.class)).collect(Collectors.toList()), details -> {
            List<ChipDetails> chipDetails = new ArrayList<>();
            for (Object o : details) {
                chipDetails.add((ChipDetails) o);
            }
            return chipDetails;
        });
    });
}

这使用 Mono.zip 从列表中的每个 Chip 条目中创建一种批处理请求,同时执行它们。 Flux 最终可能会或多或少地做同样的事情,但并非如此。

如果你只是制作你需要的端点,那么:

ParameterizedTypeReference<List<ChipDetails>> detailsList = new ParameterizedTypeReference<List<ChipDetails>>() {};

public Mono<List<ChipDetails>> getChipDetailsReal() {
    return webClient.post().uri("chipDetails").body(webClient.get().uri("chips").retrieve().bodyToMono(chipList), chipList).retrieve().bodyToMono(detailsList);
}

这种方法避免了对同一端点的重复调用,并且正在做您想做的事情。

当您真正指的是 Flux 时,我不喜欢使用 ListFlux 是一种具有背压和复杂功能的流媒体,而 List 只是一个 List。