在Spring5中,出现了Reactive响应式编程思想,并且为网络编程提供相关响应式编程的支持,如提供了WebFlux,它是Spring提供的异步非阻塞的响应式的网络框架,相比传统的SpringMVC框架,可以充分利用多CPU并行处理一些功能,虽然不能提高单个请求的响应能力,但是总体可以提高多核的服务器性能,提高系统吞吐量和伸缩性,特别适合于IO密集型服务。
WebClient提供的基于响应式的非阻塞的Web请求客户端,相对于传统的RestTemplate,他不阻塞代码、异步执行。
使用WebClient需要引入下面的依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
WebClient的创建
WebClient可以直接通过new来创建,也可以使用构造者模式来构造。
java
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数组转换为对应的对象集合,然后依次处理每一个元素。
java
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的方法。
java
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对失败的处理
java
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()方法来阻塞当前程序,等待获得响应的结果。
java
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。
java
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之间传递参数。
java
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);
}
}