WebClient 简介及常用操作。
简介 WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。
pom依赖 1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-webflux</artifactId > </dependency > <dependency > <groupId > org.projectreactor</groupId > <artifactId > reactor-spring</artifactId > <version > 1.0.1.RELEASE</version > </dependency >
请求流程
创建实例
使用create()
1 2 3 4 5 WebClient webClient = WebClient.create(); WebClient webClient = WebClient.create("https://api.github.com" );
使用WebClient builder
1 2 3 4 5 6 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();
发起请求 GET请求示例 1 2 3 4 5 6 7 8 public Flux<GithubRepo> listGithubRepositories (String username, String token) { return webClient.get() .uri("/user/repos" ) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }
在uri中使用参数 1 2 3 4 5 6 7 8 9 public Flux<GithubRepo> listGithubRepositories (String username, String token) { return webClient.get() .uri("/user/repos?sort={sortField}&direction={sortDirection}" , "updated" , "desc" ) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }
使用URIBuilder 1 2 3 4 5 6 7 8 9 10 11 public Flux<GithubRepo> listGithubRepositories (String username, String token) { return webClient.get() .uri(uriBuilder -> uriBuilder.path("/user/repos" ) .queryParam("sort" , "updated" ) .queryParam("direction" , "desc" ) .build()) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }
POST请求 body方法参数为Mono/Flux
1 2 3 4 5 6 7 8 9 10 public Mono<GithubRepo> createGithubRepository (String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos" ) .body(Mono.just(createRepoRequest), RepoRequest.class) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }
使用syncBody
快捷方式
1 2 3 4 5 6 7 8 9 10 public Mono<GithubRepo> createGithubRepository (String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos" ) .syncBody(createRepoRequest) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }
可以使用BodyInserters类提供的各种工厂方法来构造BodyInserter对象并将其传递给body方法。BodyInserters类包含从Object,Publisher,Resource,FormData,MultipartData等创建BodyInserter的方法。
1 2 3 4 5 6 7 8 9 10 public Mono<GithubRepo> createGithubRepository (String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos" ) .body(BodyInserters.fromObject(createRepoRequest)) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }
处理返回结果 retrieve方法是获取响应body的最简单方法。但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。
1 2 3 4 5 6 7 8 public Flux<GithubRepo> listGithubRepositories (String username, String token) { return webClient.get() .uri("/user/repos" ) .header("Authorization" , "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .exchange() .flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class)); }
将response body 转换为对象/集合
过滤器 WebClient支持使用ExchangeFilterFunction
进行请求过滤。可以使用过滤器功能以任何方式拦截和修改请求。 例如,可以使用过滤器函数为每个请求添加Authorization标头,或记录每个请求的详细信息。
ExchangeFilterFunction有两个参数:
ClientRequest
过滤器链中的下一个ExchangeFilterFunction
增加基本身份验证 1 2 3 4 5 6 WebClient webClient = WebClient.builder() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE) .filter(ExchangeFilterFunctions .basicAuthentication(username, token)) .build();
使用过滤器记录日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 WebClient webClient = WebClient.builder() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE) .filter(ExchangeFilterFunctions .basicAuthentication(username, token)) .filter(logRequest()) .build(); private ExchangeFilterFunction logRequest () { return (clientRequest, next) -> { logger.info("Request: {} {}" , clientRequest.method(), clientRequest.url()); clientRequest.headers() .forEach((name, values) -> values.forEach(value -> logger.info("{}={}" , name, value))); return next.exchange(clientRequest); }; }
使用工厂方法来创建过滤器
ofRequestProcessor
ofResponseProcessor
1 2 3 4 5 6 7 8 private ExchangeFilterFunction logRequest () { ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { logger.info("Request: {} {}" , clientRequest.method(), clientRequest.url()); clientRequest.headers() .forEach((name, values) -> values.forEach(value -> logger.info("{}={}" , name, value))); return Mono.just(clientRequest); }); }
使用过滤器拦截response
1 2 3 4 5 6 private ExchangeFilterFunction logResposneStatus () { return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { logger.info("Response Status {}" , clientResponse.statusCode()); return Mono.just(clientResponse); }); }
处理WebClient异常 只要收到状态代码为4xx或5xx的响应,WebClient中的retrieve方法就会抛出WebClientResponseException。
1 2 3 4 5 6 7 8 9 10 11 12 13 public Flux<GithubRepo> listGithubRepositories () { return webClient.get() .uri("/user/repos?sort={sortField}&direction={sortDirection}" , "updated" , "desc" ) .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new MyCustomClientException()) ) .onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new MyCustomServerException()) ) .bodyToFlux(GithubRepo.class); }
注意 ,与retrieve方法不同,exchange方法在4xx或5xx响应的情况下不会抛出异常。 需要自己检查状态代码并按照希望的方式处理它们。
可以在控制器中使用@ExceptionHandler
来处理WebClientResponseException
。
1 2 3 4 5 @ExceptionHandler (WebClientResponseException.class)public ResponseEntity<String> handleWebClientResponseException (WebClientResponseException ex) { logger.error("Error from WebClient - Status {}, Body {}" , ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex); return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString()); }