webclient 和webflux(参考(webflux)) 这一系列,我们可以开好多节课程来讲解
什么是webclient,在spring5中,出现了reactive 响应式编程思想(参考响应式编程文章),并且为网络编程提供相关响应式编程的支持,如提供webflux(参考另外一个教程),他是spring 提供的异步非阻塞的响应式的网络框架,可以充分利用多cpu并行处理一些功能,虽然不能提高单个请求的响应能力,但是总体可以提高多核的服务器性能,提高系统吞吐量和伸缩性,特别适合于i/o密集型服务,webflux对应的是老式的sevlet的阻塞式服务容器。
而webclient提供的基于响应式的非阻塞的web请求客户端,对应的是老式的restTemplate这类,相对于老式的restTemplate,他不阻塞代码、异步执行。
他的执行流程是先创建个()实例,之后调用get(),post()等调用方式,uri()指定路径,retrieve()用来发起请求并获得响应,bodyToMono()
用来指定请求结果需要处理为String,并包装为Reactor的Mono对象,
简单实例如下:
WebClient webClient = ();
Mono<String> mono = ().uri("").retrieve().bodyToMono();
(::println);
以下我们讲解具体的实例
以下加webclient的pom
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>reactor-spring</artifactId>
<version>1.0.</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
一、webclient的使用
1、创建实例
WebClient webClient = ();
// 如果是调用特定服务的API,可以在初始化webclient 时使用,baseUrl
WebClient webClient = ("");
或者用构造者模式构造
WebClient webClient1 = ()
.baseUrl("")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();
2、get请求
Mono<String> resp = ()
.method()
.uri("")
.cookie("token","xxxx")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.retrieve().bodyToMono();
3、post请求
@Test
public void testFormParam(){
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
("name1","value1");
("name2","value2");
Mono<String> resp = ().post()
.uri("http:///test/demo_form.asp")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body((formData))
.retrieve().bodyToMono();
("result:" + ());
}
可以使用BodyInserters类提供的各种工厂方法来构造BodyInserter对象并将其传递给body方法。BodyInserters类包含从Object,Publisher,Resource,FormData,MultipartData等创建BodyInserter的方法。
4、post json
static class Book {
String name;
String title;
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
= title;
}
}
@Test
public void testPostJson(){
Book book = new Book();
("name");
("this is title");
Mono<String> resp = ().post()
.uri("http://localhost:8080/demo/json")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body((book),)
.retrieve().bodyToMono();
("result:{}",());
}
5、上传文件:
@Test
public void testUploadFile(){
HttpHeaders headers = new HttpHeaders();
(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource(""), headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
("file", entity);
Mono<String> resp = ().post()
.uri("http://localhost:8080/upload")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body((parts))
.retrieve().bodyToMono();
("result:{}",());
6、失败处理
@Test
public void testFormParam4xx(){
WebClient webClient = ()
.baseUrl("")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();
responseSpec = ()
.uri("/user/repos?sort={sortField}&direction={sortDirection}",
"updated", "desc")
.retrieve();
Mono<String> mono = responseSpec
.onStatus(e -> e.is4xxClientError(),resp -> {
("error:{},msg:{}",().value(),().getReasonPhrase());
return (new RuntimeException(().value() + " : " + ().getReasonPhrase()));
})
.bodyToMono()
.doOnError(, err -> {
("ERROR status:{},msg:{}",(),());
throw new RuntimeException(());
})
.onErrorReturn("fallback");
String result = ();
(result);
}
-
可以使用onStatus根据status code进行异常适配
-
可以使用doOnError异常适配
-
可以使用onErrorReturn返回默认值
7、指定url,并且替换api路径
在应用中使用WebClient时也许你要访问的URL都来自同一个应用,只是对应不同的URL地址,这个时候可以把公用的部分抽出来定义为baseUrl,然后在进行WebClient请求的时候只指定相对于baseUrl的URL部分即可。这样的好处是你的baseUrl需要变更的时候可以只要修改一处即可。下面的代码在创建WebClient时定义了baseUrl为http://localhost:8081
,在发起Get请求时指定了URL为/user/1
,而实际*问的URL是http://localhost:8081/user/1
。
String baseUrl = "http://localhost:8081";
WebClient webClient = (baseUrl);
Mono<User> mono = ().uri("user/{id}", 1).retrieve().bodyToMono();
8、retrieve和exchange
retrieve方法是直接获取响应body,但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。由于响应的得到是异步的,所以都可以调用 block 方法来阻塞当前程序,等待获得响应的结果。
以下代码是把获取写入的cookie
String baseUrl = "http://localhost:8081";
WebClient webClient = (baseUrl);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
("username", "u123");
("password", "p123");
Mono<ClientResponse> mono = ().uri("login").syncBody(map).exchange();
ClientResponse response = ();
if (() == ) {
Mono<Result> resultMono = ();
(result -> {
if (()) {
ResponseCookie sidCookie = ().getFirst("sid");
Flux<User> userFlux = ().uri("users").cookie((), ()).retrieve().bodyToFlux();
(::println);
}
});
}
9、filter
WebClient也提供了Filter,对应于接口,其接口方法定义如下。Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next)在进行拦截时可以拦截request,也可以拦截response。
增加基本身份验证:
WebClient webClient = ()
.baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.filter(ExchangeFilterFunctions
.basicAuthentication(username, token))
.build();
过滤response:
@Test
void filter() {
Map<String, Object> uriVariables = new HashMap<>();
("p1", "var1");
("p2", 1);
WebClient webClient = ().baseUrl("")
.filter(logResposneStatus())
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
.build();
Mono<String> resp1 = webClient
.method()
.uri("/")
.cookie("token","xxxx")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.retrieve().bodyToMono();
String re= ();
("result:" +re);
}
private ExchangeFilterFunction logResposneStatus() {
return (clientResponse -> {
("Response Status {}", ());
return (clientResponse);
});
}
使用过滤器记录日志:
WebClient webClient = ()
.baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.filter(ExchangeFilterFunctions
.basicAuthentication(username, token))
.filter(logRequest())
.build();
private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
("Request: {} {}", (), ());
()
.forEach((name, values) -> (value -> ("{}={}", name, value)));
return (clientRequest);
};
}
注意:
将response body 转换为对象/集合
-
bodyToMono
如果返回结果是一个Object,WebClient将接收到响应后把JSON字符串转换为对应的对象。
-
bodyToFlux
如果响应的结果是一个集合,则不能继续使用bodyToMono(),应该改用bodyToFlux(),然后依次处理每一个元素。
二、webclient原理
webclient的整个实现技术基本采用reactor-netty实现,下章我讲述相关细节
参考:https:///article/
/spring/docs/current/spring-framework-reference/#webflux-client