WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。

增加pom引用

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

简单例子

下面的代码是一个简单的WebClient请求示例。可以通过WebClient.create()创建一个WebClient的实例,之后可以通过get()、post()等选择调用方式,uri()指定需要请求的路径,retrieve()用来发起请求并获得响应,bodyToMono(String.class)用来指定请求结果需要处理为String,并包装为Reactor的Mono对象。

        WebClient webClient = WebClient.create();Mono<String> mono = webClient.get().uri("https://www.baidu.com").retrieve().bodyToMono(String.class);System.out.println(mono.block());

URL中使用路径变量

URL中也可以使用路径变量,路径变量的值可以通过uri方法的第2个参数指定。下面的代码中就定义了URL中拥有一个路径变量id,然后实际访问时该变量将取值1。

webClient.get().uri("http://localhost:8081/user/{id}", 1);

URL中也可以使用多个路径变量,多个路径变量的赋值将依次使用uri方法的第2个、第3个、第N个参数。下面的代码中就定义了URL中拥有路径变量p1和p2,实际访问的时候将被替换为var1和var2。所以实际访问的URL是http://localhost:8081/user/var1/var2

webClient.get().uri("http://localhost:8081/user/{p1}/{p2}", "var1", "var2");

使用的路径变量也可以通过Map进行赋值。面的代码中就定义了URL中拥有路径变量p1和p2,实际访问的时候会从uriVariables中获取值进行替换。所以实际访问的URL是http://localhost:8081/user/var1/1

Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("p1", "var1");
uriVariables.put("p2", 1);
webClient.get().uri("http://localhost:8081/user/{p1}/{p2}", uriVariables);

使用uriBuilder传递参数

String baseUrl = "http://192.1681.5.9:8989";
WebClient webClient = WebClient.create(baseUrl);
WebClient.RequestBodyUriSpec request = webClient.method(HttpMethod.POST);
request.uri(uriBuilder -> uriBuilder.scheme("http").host("192.168.5.9").path("/mxtest4").port(8989).path("/mxtest4").queryParam("name1", "啊").queryParam("name2", "是").build());

指定baseUrl

在应用中使用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 = WebClient.create(baseUrl);
Mono<User> mono = webClient.get().uri("user/{id}", 1).retrieve().bodyToMono(User.class);

Form提交

当传递的请求体对象是一个MultiValueMap对象时,WebClient默认发起的是Form提交。下面的代码中就通过Form提交模拟了用户进行登录操作,给Form表单传递了参数username,值为u123,传递了参数password,值为p123。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "u123");
map.add("password", "p123");Mono<String> mono = webClient.post().uri("/login").syncBody(map).retrieve().bodyToMono(String.class);

请求JSON

假设现在拥有一个新增User的接口,按照接口定义客户端应该传递一个JSON对象,格式如下:

{"name":"张三","username":"zhangsan"
}

客户端可以建立一个满足需要的JSON格式的对象,然后直接把该对象作为请求体,WebClient会帮我们自动把它转换为JSON对象。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);User user = new User();
user.setName("张三");
user.setUsername("zhangsan");Mono<Void> mono = webClient.post().uri("/user/add").syncBody(user).retrieve().bodyToMono(Void.class);
mono.block();

如果没有建立对应的对象,直接包装为一个Map对象也是可以的,比如下面这样。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);Map<String, Object> user = new HashMap<>();
user.put("name", "张三");
user.put("username", "zhangsan");Mono<Void> mono = webClient.post().uri("/user/add").syncBody(user).retrieve().bodyToMono(Void.class);
mono.block();

直接传递一个JSON字符串也是可以的,但是此时需要指定contentType为application/json,也可以加上charset。默认情况下WebClient将根据传递的对象在进行解析处理后自动选择ContentType。直接传递字符串时默认使用的ContentType会是text/plain。其它情况下也可以主动指定ContentType。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);String userJson = "{" + "    \"name\":\"张三\",\r\n" + "    \"username\":\"zhangsan\"\r\n" + "}";Mono<Void> mono = webClient.post().uri("/user/add").contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(userJson).retrieve().bodyToMono(Void.class);
mono.block();

处理WebClient错误

WebClient.ResponseSpec retrieve = request.retrieve();Mono<String> mono = retrieve.onStatus(e -> e.is4xxClientError(), resp -> {System.out.println(resp.statusCode().value() + "," + resp.statusCode().getReasonPhrase());return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));}).bodyToMono(String.class).doOnError(WebClientResponseException.class, err -> {System.out.println(err.getRawStatusCode() + "," + err.getResponseBodyAsString());throw new RuntimeException(err.getMessage());}).onErrorReturn("fallback");System.out.println("result:" + mono.block());

上传和下载文件

上传

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource("parallel.png"), headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("file", entity);
Mono<String> resp = WebClient.create().post().uri("http://localhost:8080/upload").contentType(MediaType.MULTIPART_FORM_DATA).body(BodyInserters.fromMultipartData(parts)).retrieve().bodyToMono(String.class);
LOGGER.info("result:{}",resp.block());

下载图片

Mono<Resource> resp = WebClient.create().get().uri("http://www.toolip.gr/captcha?complexity=99&size=60&length=9").accept(MediaType.IMAGE_PNG).retrieve().bodyToMono(Resource.class);
Resource resource = resp.block();
BufferedImage bufferedImage = ImageIO.read(resource.getInputStream());
ImageIO.write(bufferedImage, "png", new File("captcha.png"));

下载文件

Mono<ClientResponse> resp = WebClient.create().get().uri("http://localhost:8080/file/download").accept(MediaType.APPLICATION_OCTET_STREAM).exchange();
ClientResponse response = resp.block();
String disposition = response.headers().asHttpHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
String fileName = disposition.substring(disposition.indexOf("=")+1);
Resource resource = response.bodyToMono(Resource.class).block();
File out = new File(fileName);
FileUtils.copyInputStreamToFile(resource.getInputStream(),out);
LOGGER.info(out.getAbsolutePath());

异步调用

Flux<String> flux = request.retrieve().bodyToFlux(String.class);
Disposable subscribe = flux.subscribe(tweet -> {//如果jvm结束了,就不能显示了System.out.println(tweet.toString());
});
System.out.println("result:exit");
Thread.sleep(5000);

exchange

前面介绍的示例都是直接获取到了响应的内容,可能你会想获取到响应的头信息、Cookie等。那就可以在通过WebClient请求时把调用retrieve()改为调用exchange(),这样可以访问到代表响应结果的org.springframework.web.reactive.function.client.ClientResponse对象,通过它可以获取响应的状态码、Cookie等。下面的代码先是模拟用户进行了一次表单的登录操作,通过ClientResponse获取到了登录成功后的写入Cookie的sessionId,然后继续请求了用户列表。在请求获取用户列表时传递了存储了sessionId的Cookie。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "u123");
map.add("password", "p123");Mono<ClientResponse> mono = webClient.post().uri("login").syncBody(map).exchange();
ClientResponse response = mono.block();
if (response.statusCode() == HttpStatus.OK) {Mono<Result> resultMono = response.bodyToMono(Result.class);resultMono.subscribe(result -> {if (result.isSuccess()) {ResponseCookie sidCookie = response.cookies().getFirst("sid");Flux<User> userFlux = webClient.get().uri("users").cookie(sidCookie.getName(), sidCookie.getValue()).retrieve().bodyToFlux(User.class);userFlux.subscribe(System.out::println);}});
}

WebClient.Builder

除了可以通过WebClient.create()创建WebClient对象外,还可以通过WebClient.builder()创建一个WebClient.Builder对象,再对Builder对象进行一些配置后调用其build()创建WebClient对象。下面的代码展示了其用法,配置了baseUrl和默认的cookie信息。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.builder().baseUrl(baseUrl).defaultCookie("cookieName", "cookieValue").build();
//使用WebClient构建器,可以自定义选项:包括过滤器、默认标题、cookie、客户端连接器等
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com").defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json").defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient").build()

Builder还可以通过clientConnector()定义需要使用的ClientHttpConnector,默认将使用org.springframework.http.client.reactive.ReactorClientHttpConnector,其底层是基于netty的,如果你使用的是Maven,需要确保你的pom.xml中定义了如下依赖。

<dependency><groupId>io.projectreactor.ipc</groupId><artifactId>reactor-netty</artifactId><version>0.7.8.RELEASE</version>
</dependency>

如果对默认的发送请求和处理响应结果的编解码不满意,还可以通过exchangeStrategies()定义使用的ExchangeStrategies。ExchangeStrategies中定义了用来编解码的对象,其也有对应的build()方法方便我们来创建ExchangeStrategies对象。

WebClient也提供了Filter,对应于org.springframework.web.reactive.function.client.ExchangeFilterFunction接口,其接口方法定义如下。

Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next)

在进行拦截时可以拦截request,也可以拦截response。下面的代码定义的Filter就拦截了request,给每个request都添加了一个名为header1的header,值为value1。它也拦截了response,response中也是添加了一个新的header信息。拦截response时,如果新的ClientResponse对象是通过ClientResponse.from(response)创建的,新的response是不会包含旧的response的body的,如果需要可以通过ClientResponse.Builderbody()指定,其它诸如header、cookie、状态码是会包含的。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.builder().baseUrl(baseUrl).filter((request, next) -> {ClientRequest newRequest = ClientRequest.from(request).header("header1", "value1").build();Mono<ClientResponse> responseMono = next.exchange(newRequest);return Mono.fromCallable(() -> {ClientResponse response = responseMono.block();ClientResponse newResponse = ClientResponse.from(response).header("responseHeader1", "Value1").build();return newResponse;});
}).build();

如果定义的Filter只期望对某个或某些request起作用,可以在Filter内部通过request的相关属性进行拦截,比如cookie信息、header信息、请求的方式或请求的URL等。也可以通过ClientRequest.attribute(attrName)获取某个特定的属性,该属性是在请求时通过attribute("attrName", "attrValue")指定的。这跟在HttpServletRequest中添加的属性的作用范围是类似的。

配置连接池,超时时间等

@Configuration
public class WebClientConfig {@BeanReactorResourceFactory resourceFactory() {ReactorResourceFactory factory = new ReactorResourceFactory();factory.setUseGlobalResources(false);factory.setConnectionProvider(ConnectionProvider.fixed("httpClient", 50, 10));factory.setLoopResources(LoopResources.create("httpClient", 50, true));return factory;}@BeanWebClient webClient() {Function<HttpClient, HttpClient> mapper = client ->client.tcpConfiguration(c ->c.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10).option(TCP_NODELAY, true).doOnConnected(conn -> {conn.addHandlerLast(new ReadTimeoutHandler(10));conn.addHandlerLast(new WriteTimeoutHandler(10));}));ClientHttpConnector connector =new ReactorClientHttpConnector(resourceFactory(), mapper);return WebClient.builder().clientConnector(connector).build();}
}

参数

https://blog.csdn.net/iteye_13139/article/details/82726588

https://segmentfault.com/a/1190000012916413

https://juejin.im/post/5d6c9507e51d4561f777e20b

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

Spring的WebClient基本使用相关推荐

  1. java webclient使用,java – Spring Reactive WebClient

    我有一个反应式休息api(webflux),也使用 spring WebClient类,从其他休息服务请求数据. 简化设计: @PostMapping(value = "/document& ...

  2. 聊聊WebClient的LoadBalance支持

    为什么80%的码农都做不了架构师?>>>    序 本文主要研究一下WebClient的LoadBalance支持 代码实例 配置 @Configuration public cla ...

  3. Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新

    文章目录 简介 Spring基础 Core核心技术 Testing测试 Data Access Web Servlet Web Reactive 总结 简介 是什么让java世界变得更好,程序员变得更 ...

  4. 使用Spring Cloud HystrixCommands的功能Hystrix

    Spring的WebClient提供了一个非阻塞客户端来进行服务调用. Hystrix虽然现在处于维护模式,但已通过防止级联故障,为慢速或故障上游服务的呼叫提供断路器来保护服务对服务的呼叫. 在本文中 ...

  5. 新一代Spring Web框架WebFlux!

    Spring WebFlux 教程:如何构建反应式 Web 应用程序 反应式系统提供了我们在高数据流世界中所需的无与伦比的响应能力和可扩展性.然而,反应式系统需要经过专门培训的工具和开发人员来实现这些 ...

  6. Spring Boot 3.0.0-M1 Reference Documentation(Spring Boot中文参考文档) 9-16

    9. 数据 Spring Boot与多个数据技术集成,包括SQL和NoSQL. 9.1. SQL数据库 Spring Framework提供扩展支持用于与SQL数据工作,从使用JdbcTemplate ...

  7. Spring Boot 3.0.0-M1 Reference Documentation(Spring Boot中文参考文档)-附录A-C

    附录 附录A:常用的应用程序属性 多种属性可以指定到application.properties文件,application.yml文件内,或者作为命令行开关.这个附录提供常用的Spring Boot ...

  8. 夯实Spring系列|总览介绍

    文章目录 系列文章 番外篇 1.文章说明 2.整理目的 3.Spring 核心特性 4.数据存储(Data Access) 5.Web 技术 6.技术整合 7.测试(Testing) 8.Java 版 ...

  9. webclient使用介绍

    webclient 和webflux(参考(webflux)) 这一系列,我们可以开好多节课程来讲解 什么是webclient,在spring5中,出现了reactive 响应式编程思想(参考响应式编 ...

最新文章

  1. Java动态生成类以及动态添加属性 本篇文章来源于 Linux公社网站(www.linuxidc.c
  2. 华为手机双卡有android,华为Mate 40系列手机入网:双卡5G+安卓系统
  3. JavaScript 的性能优化:加载和执行
  4. SpringBoot优点
  5. vant按需引入没样式_传统背景墙早看腻了,不如走一圈石膏线简单好看,8种样式随意选...
  6. JUC锁-CountDownLatch(六)
  7. (1)memcached应用
  8. 有这个OCR程序,不用再买VIP了,Python 调用百度OCR API
  9. leetcode738. 单调递增的数字
  10. 匿名内部类 可以访问外部类_Java——内部类详解
  11. keras embedding层_初识TextCNN及keras实现
  12. 【免费毕设】PHP校园二手信息网站的设计与开发(源代码+论文)
  13. 同一个WiFi,电脑不能访问CSDN网站,手机却可以,换一个网络电脑就可以是什么原因?
  14. 渗透测试常用端口利用总结
  15. 社区车辆信息管理系统
  16. 联想怎么进入linux界面,联想(Lenovo)为何重返Linux桌面?
  17. Android手机写Java代码的软件
  18. 7-2 买电影票 (C语言)
  19. ArcGIS软件CAD数据通过转换建立GIS数据库
  20. 淘宝客订单同步,关联私域授权用户,给用户返分,流程是如何串起来的

热门文章

  1. 使用IDA 进行远程调试
  2. instsrv.exe——来自Windows 2000 Resource Kits的一个小工具
  3. 零字节WSASend,WSARecv
  4. 监听以太网(四) Packet32函数SDK
  5. CreateProcess创建进程
  6. 面试官:说说Java中java.lang.Void和void有什么作用和区别?
  7. 即构科技:解决行业痛点,以MSDN为基础构建全球实时音视频通信云
  8. LiveVideoStack线上交流分享 (九) —— B站的QUIC实践简介
  9. GitHub超实用操作
  10. JVM之盘点家底查看初始默认值和更改值