11.4.1 通过 GET 方式获取资源
作为使用 WebClient 的样例,假设你需要通过 Taco Cloud API 根据 ID 获取 Ingredient 对象。如果使用 RestTemplate,那么你可能会使用 getForObject() 方法。但是,借助 WebClient 的话,你可以构建请求、获取响应并抽取一个会发布 Ingredient 对象的 Mono:
Mono<Ingredient> ingredient = WebClient.create()
.get()
.uri("http://localhost:8080/ingredients/{id}", ingredientId)
.retrieve()
.bodyToMono(Ingredient.class);
ingredient.subscribe(i -> { ... })
​ 在这里,你使用 create() 创建了一个新的 WebClient 实例。然后,你可以使用 get()uri() 定义对 http://localhost:8080/ingredients/{id} 的 GET 请求,其中 {id} 占位符将会被 ingredientId 的值所替换。接着,retrieve() 会执行请求。最后,我们调用 bodyToMono()将响应体的载荷抽取到 Mono<Ingredient> 中,就可以继续使用 Mono 的额外操作了。
​ 为了对 bodyToMono() 返回 Mono 进行额外的操作,需要注意的很重要的一点是要在请求发送之前对其进行订阅。发送请求获取值的集合是非常容易的。例如,如下的代码片段将获取所有 Ingredient:
Flux<Ingredient> ingredients = WebClient.create()
.get()
.uri("http://localhost:8080/ingredients")
.retrieve()
.bodyToFlux(Ingredient.class);
ingredients.subscribe(i -> { ... })
​ 大部分而言,获取多个条目与获取单个条目是相同的。最大的差异在于我们不再是使用 bodyToMono() 将响应体抽取为 Mono,而是使用 bodyToFlux() 将其抽取为一个 Flux。与 bodyToMono() 类似,bodyToFlux() 返回的 Flux 还没有被订阅。在数据流过之前,你可以对 Flux 添加一些额外的操作(过滤、映射等)。因此,非常重要的一点就是要订阅结果所形成的 Flux,否则请求将始终不会发送。

使用基础 URI 发送请求

​ 你可能会发现在很多请求中都会使用一个通用的基础 URI。这样的话,创建 WebClient bean 的时候设置一个基础 URI 并将其注入到所需的地方是非常有用的。这样的 bean 可以按照如下的方式来声明:
@Bean
public WebClient webClient() {
return WebClient.create("http://localhost:8080");
}
​ 然后,在想要使用基础 URI 的任意地方,你都可以将 WebClient bean 注入进来并按照如下的方式来使用:
@Autowired
WebClient webClient;
public Mono<Ingredient> getIngredientById(String ingredientId) {
Mono<Ingredient> ingredient = webClient
.get()
.uri("/ingredients/{id}", ingredientId)
.retrieve()
.bodyToMono(Ingredient.class);
ingredient.subscribe(i -> { ... })
}
​ 因为 WebClient 已经创建好了,所以你可以通过 get() 方法直接使用它。对于 URI 来说,你只需要调用 uri() 指定相对于基础 URI 的相对路径即可。

对长时间运行的请求进行超时处理

​ 你需要考虑的一件事情就是,网络并不是始终可靠的,或者并不像你预期的那么快,远程服务器在处理请求时有可能会非常缓慢。理想情况下,对远程服务的请求会在一个合理的时间内返回。无法正常返回的话,客户端要是能够避免陷入长时间等待响应的窘境就好了。为了避免客户端请求被缓慢的网络或服务阻塞,你可以使用 Flux 或 Mono 的 timeout() 方法,为等待数据发布的过程设置一个时长限制。作为样例,你可以考虑一下如何为获取配料数据使用 timeout() 方法:
Flux<Ingredient> ingredients = WebClient.create()
.get()
.uri("http://localhost:8080/ingredients")
.retrieve()
.bodyToFlux(Ingredient.class);
ingredients.timeout(Duration.ofSeconds(1))
.subscribe(
i -> { ... },
e -> {
// handle timeout error
})
​ 可以看到,在订阅 Flux 之前,调用了 timeout() 方法,将持续时间设置成了 1 秒。如果请求能够在 1 秒之内返回,就不会有任何问题。但是,如果请求花费的时间超过 1s,则会超时,然后会调用 subscribe() 的第二个参数进行错误处理。