WebClient
Spring 有两个web客户端的实现,一个是RestTemplate另一个是spring5的响应代替WebClient。
WebClient是一个以Reactive方式处理HTTP请求的非阻塞客户端。
基础用法
创建WebClient
1 2
| WebClient.create(); WebClient.builder();
|
请求方法
1 2 3 4 5 6 7
| WebClient webClient = WebClient.create(); webClient.get(); webClient.post(); webClient.delete(); webClient.put(); webClient.patch(); webClient.options();
|
获取结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
Mono<Object> entityMono = webClient.get() .uri("/persons/1") .accept(MediaType.APPLICATION_JSON) .exchangeToMono(response -> { if (response.statusCode().equals(HttpStatus.OK)) { return response.bodyToMono(Object.class); } else if (response.statusCode().is4xxClientError()) { return response.bodyToMono(Object.class); } else { return Mono.error((Supplier<? extends Throwable>) response.createException()); } });
Flux<Object> entityFlux = webClient.get() .uri("/persons") .accept(MediaType.APPLICATION_JSON) .exchangeToFlux(response -> { if (response.statusCode().equals(HttpStatus.OK)) { return response.bodyToFlux(Object.class); } else if (response.statusCode().is4xxClientError()) { return response.bodyToMono(Object.class).flux(); } else { return Flux.error((Supplier<? extends Throwable>) response.createException()); } });
|
异常处理
1 2
| .doOnError(t -> log.error("Error: ", t)) .doFinally(s -> log.info("Finally "))
|
请求体
1 2 3 4 5
| RequestHeadersSpec<?> headersSpec = bodySpec.bodyValue("data");
RequestHeadersSpec<?> headersSpec = bodySpec.body(Mono.just(new Foo("name")), Foo.class);
|
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class AppApplication {
public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(AppApplication.class, args); } @Bean public WebClient register() { return WebClient.create(); } }
@RestController @RequestMapping("/") @Slf4j public class AppController {
@Autowired private WebClient webClient;
@PostMapping("/list") public Flux<UserVo> list() { List<UserVo> userList = new ArrayList<>(); userList.add(new UserVo("1", "张三")); userList.add(new UserVo("2", "王五")); return Flux.fromIterable(userList); }
@GetMapping("/{id}") public Mono<UserVo> info(@PathVariable(value = "id") String id) { return Mono.just(new UserVo(id, "某某")); }
@GetMapping("/ip/info") public Mono<String> ip() { String url = "https://myip.ipip.net/"; Mono<String> body = webClient.get() .uri(url) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .acceptCharset(StandardCharsets.UTF_8) .exchangeToMono(response -> { if (response.statusCode().equals(HttpStatus.OK)) { return response.bodyToMono(String.class); } else { return response.createException().flatMap(Mono::error); } }) .doOnError(t -> log.error("Error: ", t)) .doFinally(s -> log.error("Finally ")) .subscribeOn(Schedulers.single()); return body; }
}
@Data @AllArgsConstructor @NoArgsConstructor public class UserVo {
private String uid;
private String name; }
|
接口Mock测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @BootstrapWith(WebFluxTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) @OverrideAutoConfiguration(enabled = false) @TypeExcludeFilters(WebFluxTypeExcludeFilter.class) @AutoConfigureCache @AutoConfigureJson @AutoConfigureWebFlux @AutoConfigureWebTestClient @ImportAutoConfiguration public @interface WebFluxTest {
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @Slf4j @ExtendWith(SpringExtension.class) @ActiveProfiles("dev") @WebFluxTest public class AppApplicationMockTest {
@Autowired private WebTestClient webTestClient;
@MockBean private AppController appController;
@Test @Disabled public void get() { UserVo user = new UserVo("1", "张三"); Mockito.when(appController.info("1")).thenReturn(Mono.just(user)); EntityExchangeResult<UserVo> result = webTestClient.get() .uri("/1") .exchange() .expectStatus().isOk() .expectBody(UserVo.class) .returnResult(); log.info("{}", result); }
@Test @Disabled public void list() { List<UserVo> userList = new ArrayList<>(); userList.add(new UserVo("1", "张三")); userList.add(new UserVo("2", "王武")); Mockito.when(appController.list()).thenReturn(Flux.fromIterable(userList)); EntityExchangeResult<List<UserVo>> result = webTestClient.post() .uri("/list") .exchange() .expectStatus().isOk() .expectBodyList(UserVo.class) .returnResult(); log.info("{}", result); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| [main] > GET /1 > WebTestClient-Request-Id: [1]
No content
< 200 OK OK < Content-Type: [application/json] < Content-Length: [27]
{"uid":"1","name":"张三"}
[main] > POST /list > WebTestClient-Request-Id: [1]
No content
< 200 OK OK < Content-Type: [application/json]
[{"uid":"1","name":"张三"},{"uid":"2","name":"王武"}]
|
接口测试
- 添加@AutoConfigureWebTestClient 启用WebTestClient。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = AppApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("dev") @AutoConfigureWebTestClient public class AppApplicationTest {
@Autowired private WebTestClient webTestClient;
@Test @Disabled public void get() { EntityExchangeResult<UserVo> result = webTestClient.get() .uri("/1") .exchange() .expectStatus().isOk() .expectBody(UserVo.class) .returnResult(); log.info("{}", result); }
@Test @Disabled public void ip() { EntityExchangeResult<String> result = webTestClient.get() .uri("/ip/info") .exchange() .expectStatus().isOk() .expectBody(String.class) .returnResult(); log.info("{}", result); }
@Test @Disabled public void list() { EntityExchangeResult<List<UserVo>> result = webTestClient.post() .uri("/list") .exchange() .expectStatus().isOk() .expectBodyList(UserVo.class) .returnResult(); log.info("{}", result); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| [main] > GET /1 > WebTestClient-Request-Id: [1]
No content
< 200 OK OK < Content-Type: [application/json] < Content-Length: [27]
{"uid":"1","name":"某某"}
[reactor-http-nio-3] Finally [main] > GET /ip/info > WebTestClient-Request-Id: [1]
No content
< 200 OK OK < Content-Type: [text/plain;charset=UTF-8] < Content-Length: [67]
当前 IP:000.000.00.0 来自于:中国 浙江 杭州 电信
[main] > POST /list > WebTestClient-Request-Id: [1]
No content
< 200 OK OK < Content-Type: [application/json]
[{"uid":"1","name":"张三"},{"uid":"2","name":"王五"}]
|
END