从 Spring 5 开始,Spring 中全面引入了 Reactive 响应式编程。而 WebClient 则是 Spring WebFlux 模块提供的一个非阻塞的基于响应式编程的进行 Http 请求的客户端工具。
由于 WebClient 的请求模式属于异步非阻塞,能够以少量固定的线程处理高并发的 HTTP 请求。因此,从 Spring 5 开始,HTTP 服务之间的通信我们就可以考虑使用 WebClient 来取代之前的 RestTemplate。
下面记录 WebClient 的基本使用方法,并记录如何通过 WebClient 发送 GET 和 POST 请求。
1.添加依赖
要使用 WebClient,首先需要在 Maven 项目中添加 Spring WebFlux 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
对于 Gradle 项目:
implementation 'org.springframework.boot:spring-boot-starter-webflux'
2.创建 WebClient 实例
可以通过多种方式创建 WebClient 实例:
方式一:全局 WebClient Bean(推荐)
在 Spring 项目中,通常会通过定义一个 WebClient.Builder Bean,并在需要的地方注入。
@Configuration
public class WebClientConfig {
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
然后在服务中使用:
@Service
public class MyService {
private final WebClient webClient;
public MyService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://example.com").build();
}
public String getData() {
return webClient.get()
.uri("/api/data")
.retrieve()
.bodyToMono(String.class)
.block(); // 这里使用 block() 表示阻塞操作
}
}
方式二:使用静态创建方法
可以直接使用 WebClient.create() 创建客户端实例:
WebClient webClient = WebClient.create("https://example.com");
3.发送请求
3.1 发送 GET 请求
public String getData() {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
String response = webClient.get()
.uri("/posts/1")
.retrieve()
.bodyToMono(String.class)
.block();
return response;
}
- retrieve():用于发出请求并获取响应。
- bodyToMono(String.class):将响应体转换为Mono,Mono 表示一个异步的单值序列。
- block():阻塞当前线程,直到请求完成并返回结果。
3.2 发送 POST 请求
下面是一个发送 POST 请求并传递 JSON 数据的例子:
public String postData() {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
String response = webClient.post()
.uri("/posts")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue("{ \"title\": \"foo\", \"body\": \"bar\", \"userId\": 1 }")
.retrieve()
.bodyToMono(String.class)
.block();
return response;
}
- bodyValue():设置请求体的数据,可以传递字符串、对象、或者其他序列化格式。
- block():同步等待请求完成。
3.3 异步请求
如果不想阻塞线程,可以使用 WebClient 的异步处理功能。例如,使用 subscribe() 来处理异步请求:
public void getDataAsync() {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
webClient.get()
.uri("/posts/1")
.retrieve()
.bodyToMono(String.class)
.subscribe(response -> {
System.out.println("异步响应: " + response);
});
}
这里使用了 subscribe() 方法,它接受一个 Consumer 回调函数,当响应到达时触发这个回调函数。
4. 处理错误响应
通过 WebClient,可以使用 onStatus() 来处理特定的 HTTP 响应状态码:
public String getDataWithErrorHandling() {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
return webClient.get()
.uri("/posts/invalid")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
return response.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new RuntimeException("4xx 错误: " + errorBody)));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
return response.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new RuntimeException("5xx 错误: " + errorBody)));
})
.bodyToMono(String.class)
.block();
}
5. 超时配置
可以通过自定义 WebClient 的 HttpClient 来设置超时时间:
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(5)); // 设置响应超时
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
6. 文件上传
通过 WebClient 实现文件上传,可以使用 multipart/form-data 来传递文件:
import org.springframework.http.MediaType;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
public String uploadFile() {
WebClient webClient = WebClient.create("https://example.com");
Resource file = new FileSystemResource("/path/to/file.txt");
return webClient.post()
.uri("/upload")
.contentType(MediaType.MULTIPART_FORM_DATA)
.bodyValue(file)
.retrieve()
.bodyToMono(String.class)
.block();
}
总结:
- 同步调用: 可以使用 .block() 让 WebClient 的调用同步阻塞,方便传统开发者的过渡。
- 异步调用: 通过 Mono 和Flux 支持异步和响应式的编程模型,非常适合处理大量并发请求。
- 丰富的错误处理: 可以通过 onStatus() 针对不同的状态码进行错误处理。
- 灵活性强: 可以自定义超时、拦截器、连接池等,适应各种复杂场景。