Spring-web-Flux之webClient

3,101 阅读2分钟

简介

WebClient是Spring5中新引入的非阻塞、反应式HttpClient框架。

与传统的RestTemplate相比的话,WebClient具有以下优势:

  • 非阻塞 I/O。
  • 反应流背压。
  • 具有高并发性,硬件资源消耗更少。
  • 流畅的API设计。
  • 同步和异步交互。
  • 流式传输支持

使用

1.创建webclient

 WebClient.builder()
                .clientConnector(new JettyClientHttpConnector())
                .baseUrl(baseUrl)
                .filter(logRequest())
                .exchangeStrategies(ExchangeStrategies.builder()
                        .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs()
                                .maxInMemorySize(16 * 1024 * 1024))
                        .build())
                .build();

clientConnector :客户端连接实现,默认使用Netty,同时也内置Jetty的实现,当然也可以自定义实现clientConnector接口定义新的底层库。

public interface ClientHttpConnector {

	/**
	 * Connect to the origin server using the given {@code HttpMethod} and
	 * {@code URI} and apply the given {@code requestCallback} when the HTTP
	 * request of the underlying API can be initialized and written to.
	 * @param method the HTTP request method
	 * @param uri the HTTP request URI
	 * @param requestCallback a function that prepares and writes to the request,
	 * returning a publisher that signals when it's done writing.
	 * Implementations can return a {@code Mono<Void>} by calling
	 * {@link ClientHttpRequest#writeWith} or {@link ClientHttpRequest#setComplete}.
	 * @return publisher for the {@link ClientHttpResponse}
	 */
	Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
			Function<? super ClientHttpRequest, Mono<Void>> requestCallback);

}

baseUrl : 配置一个用于请求的基础url,通常用于配置域名。

filter : 配置过滤器,可以配置多个,常用的有请求前和请求后,作为权限验证及日志记录等。

//用于请求前,将请求方法及url打印出来
private ExchangeFilterFunction logRequest() {
        return (clientRequest, next) -> {
            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers()
                    .forEach((name, values) -> values.forEach(value -> log.info("Request: {}={}", name, value)));
            return next.exchange(clientRequest);
        };
    }

exchangeStrategies:配置需要使用的exchange,这里主要是重新配置在解析响应结果时可用的内存大小,因为默认大小只有32k,随便一个请求的返回值就超了,所以这里需要重新设置一下大小。当然这个方法还可以设置其他配置,详情可以查文档

2.使用

GET方法

webClient.get()
                .uri(url, (Object[]) param)
                .accept(mediaType)
                .exchangeToMono(response -> {
                    if (response.statusCode().equals(HttpStatus.OK)) {
                        return response.bodyToMono(tClass).doOnNext(item -> {
                            log.info("Get Response :{}", JSONObject.toJSONString(item));
                        });
                    } else {
                        return Mono.error(new ServerException(ResponseCode.API_ERROR));
                    }
                });

uri 中有两个参数,第一个是请求的地址,第二个是请求参数,这种的写法参数是路径变量,类似rest风格,当然也有其他的写法。

1.
webClient.get().uri(uriBuilder -> uriBuilder.path("/user/{id}/{name}")
                            .build(id, name));
2.                           
Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("id", "id1");
uriVariables.put("name", "name1");                        
webClient.get().uri("/user/{id}/{name}", uriVariables)
                                                 

accept 中是设置请求的contentType,内置MediaType类中有定义好的常量直接引用接口

public static final MediaType APPLICATION_JSON;

public static final String APPLICATION_JSON_VALUE = "application/json";

exchangeToMono 中就是对响应结果的处理,可以在里面编写自己的业务逻辑。

POST方法

//一个jsonPost请求
 webClient.post()
                .uri(expand)
                .contentType(MediaType.APPLICATION_JSON)
                .body(Mono.just(ParamsUtil.ObjectToMap(body)), Map.class)
                .exchangeToMono(clientResponse -> {
                    MediaType mediaType = clientResponse.headers().contentType().orElse(MediaType.APPLICATION_JSON);
                    log.info("PostJson resp headers :{},statusCode:{}", mediaType, clientResponse.statusCode());
                    if (clientResponse.statusCode().equals(HttpStatus.OK)) {
                        return clientResponse.bodyToMono(String.class).doOnNext(item -> {
                            log.debug("PostJson Response :{}", JSONObject.toJSONString(item));
                        }).flatMap(str -> Mono.just(JSONObject.parseObject(str, tClass)));
                    } else {
                        return clientResponse.bodyToMono(String.class).doOnNext(item -> {
                            log.info("PostJson Response :{}", JSONObject.toJSONString(item));
                        }).flatMap(item -> Mono.error(new ServerException(ResponseCode.API_ERROR)));
                    }
                });

post方法跟get方法大同小异,注意区别在于传参,post方法中需要将参数放入body中,用mono包装,这里支持任意数据类型可以试java对象,也可以是map