Spring中WebClient的创建和使用详解
前言
在Spring5中,出现了Reactive响应式编程思想,并且为网络编程提供相关响应式编程的支持,如提供了WebFlux,它是Spring提供的异步非阻塞的响应式的网络框架,相比传统的SpringMVC框架,可以充分利用多CPU并行处理一些功能,虽然不能提高单个请求的响应能力,但是总体可以提高多核的服务器性能,提高系统吞吐量和伸缩性,特别适合于IO密集型服务。
WebClient提供的基于响应式的非阻塞的Web请求客户端,相对于传统的RestTemplate,他不阻塞代码、异步执行。
使用WebClient需要引入下面的依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
WebClient的创建
WebClient可以直接通过new来创建,也可以使用构造者模式来构造。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * WebClient的创建 */ public class WebClientDemo1 { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println); WebClient webClient2 = WebClient.builder() .baseUrl("http://127.0.0.1:8020") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); webClient2.get().uri("/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println); TimeUnit.SECONDS.sleep(5); } }
在应用中使用WebClient时也许你要访问的URL都来自同一个应用,只是对应不同的URI地址,这个时候可以把公用的部分抽出来定义为baseUrl,然后在进行WebClient请求的时候只指定相对于baseUrl的URL部分即可。这样的好处是你的baseUrl需要变更的时候可以只要修改一处即可。
WebClient发送Get请求
先创建个webclient.create()实例,之后调用get()、post()等调用方式,uri()指定路径,retrieve()用来发起请求并获得响应,bodyToFlux(Order.class)用来将请求结果需要处理为Order数组,并包装为Reactor的Flux对象。
如果返回结果是一个JSON字符串,可以使用bodyToMono(),将接收到的JSON字符串转换为对应的对象。
如果返回结果是一个JSON数组,可以使用bodyToFlux(),将接收到的JSON数组转换为对应的对象集合,然后依次处理每一个元素。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * WebClient发送Get请求 */ public class WebClientGetDemo { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToFlux(Order.class).subscribe(System.out::println);; // 休眠一会,否则WebClient中的线程池还没执行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
WebClient发送Post请求
可以使用BodyInserters类提供的各种工厂方法来构造BodyInserter对象并将其传递给body方法。BodyInserters类包含从Object,Publisher,Resource,FormData,MultipartData等创建BodyInserter的方法。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; /** * WebClient发送Post请求 */ public class WebClientPostDemo { public static void main(String[] args) { WebClient webClient = WebClient.create(); Order order = new Order(); order.setId(1L); order.setUserId(666L); order.setGoodName("Iphone 13"); order.setPrice(9999); Mono<Long> mono = webClient.post().uri("http://127.0.0.1:8020/order/saveOrder") .body(BodyInserters.fromValue(order)) // .body(Mono.just(order), Order.class) .retrieve() .bodyToMono(Long.class); // 阻塞等待获取结果 System.out.println(mono.block()); } }
WebClient对失败的处理
package com.morris.user.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; /** * WebClient对失败的处理 */ @Slf4j public class WebClientDealFailDemo { public static void main(String[] args) { WebClient webClient = WebClient.create(); WebClient.ResponseSpec responseSpec = webClient.get().uri("http://127.0.0.1:8020/order/error") .retrieve(); Mono<String> mono = responseSpec .onStatus(HttpStatus::is4xxClientError, resp -> { log.error("error4xx:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase()); return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase())); }) .bodyToMono(String.class) .doOnError(WebClientResponseException.class, err -> { log.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString()); throw new RuntimeException(err.getMessage()); }) .onErrorReturn("fallback"); // 阻塞等待获取结果 System.out.println(mono.block()); } }
可以使用onStatus根据响应的status code进行适配,可以使用doOnError对异常进行适配,可以使用onErrorReturn返回默认值。
exchange()
retrieve()方法是直接获取响应body,但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。由于响应的得到是异步的,所以都可以调用block()方法来阻塞当前程序,等待获得响应的结果。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.web.reactive.function.client.WebClient; import java.util.concurrent.TimeUnit; /** * WebClient使用Exchange发送请求 */ public class WebClientExchangeDemo { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一会,否则WebClient中的线程池还没执行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
filter
WebClient也提供了Filter,对应于org.springframework.web.reactive.function.client.ExchangeFilterFunction接口,可以拦截request,也可以拦截response。
package com.morris.user.demo; import com.morris.user.entity.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.concurrent.TimeUnit; /** * WebClient使用filter拦截器 */ @Slf4j public class WebClientFilterDemo { private static ExchangeFilterFunction logResponseStatus() { return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { log.info("Response Status {}", clientResponse.statusCode()); return Mono.just(clientResponse); }); } public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.builder().filter(logResponseStatus()).build(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一会,否则WebClient中的线程池还没执行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
Attributes
可以使用attribute在多个filter之间传递参数。
package com.morris.user.demo; import com.morris.user.entity.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * WebClient使用attribute传递参数 */ @Slf4j public class WebClientAttributesDemo { private static ExchangeFilterFunction filterRequest() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { Optional<Object> myAttribute = clientRequest.attribute("myAttribute"); System.out.println(myAttribute.get()); return Mono.just(clientRequest); }); } public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.builder().filter(filterRequest()).build(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .attribute("myAttribute", "myAttribute") .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一会,否则WebClient中的线程池还没执行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
到此这篇关于Spring中WebClient的创建和使用详解的文章就介绍到这了,更多相关WebClient的创建和使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Mybatis-Plus处理Mysql Json类型字段的详细教程
这篇文章主要给大家介绍了关于Mybatis-Plus处理Mysql Json类型字段的详细教程,Mybatis-Plus可以很方便地处理JSON字段,在实体类中可以使用@JSONField注解来标记JSON字段,同时在mapper.xml中使用json函数来操作JSON字段,需要的朋友可以参考下2024-01-01logback的FileAppender文件追加模式和冲突检测解读
这篇文章主要为大家介绍了logback的FileAppender文件追加模式和冲突检测解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-10-10SpringBoot多controller添加URL前缀的实现方法
这篇文章主要介绍了SpringBoot多controller添加URL前缀的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2023-02-02spring-boot-starter-parent的作用详解
这篇文章主要介绍了spring-boot-starter-parent的作用详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-08-08
最新评论