终于进入springboot了,老框架也不用维护了,开心
这里记录下 WebClient 的学习
-
介绍
WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。 -
进入正题,先是封装好的单例工具,本篇文章主要介绍我的踩坑经历,使用示例可以看最后的参考链接
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
util
public enum WebClientUtils {
INSTANCE;
public WebClient.Builder webClientBuilder =
WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.defaultHeader("App-Locale", "chs")
.defaultHeader(
HttpHeaders.USER_AGENT,
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
/**
* 如果get的参数有数组请不要用这个方法;post只支持body json的这种
* 这里为了方便这样封装 ,headers是头,其实可以在post后面写的,但是不想用MultiValueMap再转,
* errorHandle是遇到错误的处理
* JsonUtils中的方法可以根据使用的JSON工具替换掉,如JSON,Mapper或者Gson中对应的方法
*/
public <T, R> Mono<T> http(Map<String, String> headers,
String url, R request, Class<T> cls, boolean isGet,
Function<ClientResponse, Mono<? extends Throwable>> errorHandle) {
if (!ObjectUtils.isEmpty(headers)) {
headers.entrySet().stream().forEach(entry -> {
webClientBuilder.defaultHeader(entry.getKey(), entry.getValue());
});
}
Mono<T> result = null;
WebClient.ResponseSpec responseSpec = null;
if (isGet) {
Map<String, String> params = null;
String uri = url;
if (!ObjectUtils.isEmpty(request)) {
try {
String requestStr = JsonUtils.beanToJson(request);
params = JsonUtils.jsonToMap(requestStr);
} catch (Exception e) {
throw new ServiceException(JSON_MAPPER_ERROR);
}
Map<String, String> finalParams = params;
uri = url + UriComponentsBuilder.newInstance()
.queryParams(
finalParams.entrySet().stream().filter(e -> !StringUtils.isEmpty(e.getValue()))
.collect(Collectors.toMap(Entry::getKey,
e -> Collections.singletonList(e.getValue()), (t1, t2) -> t2,
LinkedMultiValueMap::new)))
.build().toString();
}
responseSpec = webClientBuilder.build().get().uri(uri).retrieve();
} else {
responseSpec =
webClientBuilder.build()
.post()
.uri(url)
.body(Mono.just(request), new ParameterizedTypeReference<R>() {
})
.retrieve();
}
result =
responseSpec
.onStatus(
status -> status.value() != HttpStatus.OK.value(),
ObjectUtils.isEmpty(errorHandle) ? (rs ->
rs.bodyToMono(String.class)
.map(body -> new ServiceException(ERROR_INFO.getCode(), body))
) : errorHandle)
.bodyToMono(cls);
return result;
}
/**
* 表单
*/
public <T> Mono<T> httpWithForm(
String url, MultiValueMap request, Class<T> cls, Function<ClientResponse, Mono<? extends Throwable>> errorHandle) {
Mono<T> result = null;
WebClient.ResponseSpec responseSpec =
webClientBuilder.build()
.post()
.uri(url)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromMultipartData(request))
.retrieve();
result =
responseSpec
.onStatus(
status -> status.value() != HttpStatus.OK.value(),
ObjectUtils.isEmpty(errorHandle) ? (rs ->
rs.bodyToMono(String.class)
.map(body -> new ServiceException(ERROR_INFO.getCode(), body))
) : errorHandle)
.bodyToMono(cls);
}
- 测试
3.1 最简单的调用 block方法
private final static String SINA_URL = "http://hq.sinajs.cn";
Mono<String> resultMono = WebClientUtils.INSTANCE.http(SINA_URL
, RequestModel.builder().list("USDCNY").build()
, String.class, true);
String result = resultMono//不需要处理,可以直接resultMono.block();
.retry(3)//重试次数
.doOnRequest(num -> {//请求时处理
System.out.println("reuqest:" + num);
}).doOnError(d -> {//错误时处理
System.out.println("error");
}).doOnNext(d -> {//成功时处理
System.out.println("success");
}).block();
System.out.println(result);
3.2 最简单的方法曝露,注意swagger官方还不支持(不过民间还是有对策的~)
@PostMapping("/test")
Mono<String> test() {
return resultMono;
}
3.3 subscribe
result.subscribe(e -> {
log.info("-------- test");
});
//哦,你会发现怎么log没有出来,那是因为subscribe还没跑,jvm就关了。。。这时候需要:
TimeUnit.SECONDS.sleep(5);//到底睡多久,能不能动态,不好意思,我没找到答案,目前我是用CountDownLatch,但是报错的话,有问题,不知道怎么处理(和全局异常捕获流程上有冲突),放弃
3.4 多个不同返回值
同时发出多个请求,返回值Mono中泛型不一样的话:这个我是这么用的(官方的我也没找到,只能说瞎琢磨了)
Consumer c1 = e -> {log.info("-------- c1");};
Consumer c2 = e -> {log.info("-------- c2");};
Flux.merge(resultMono1,resultMono2).toStream().parallel().forEach(r -> {
if(r instanceof String){
c1.accept(r)
}else{
}
});
3.5 取消请求
Disposable disposable1 = result.subscribe(e -> {
log.info("-------- test");
});
disposable1.dispose();