webclient使用介绍

时间:2025-03-15 15:01:16

      webclient 和webflux(参考(webflux)) 这一系列,我们可以开好多节课程来讲解

      什么是webclient,在spring5中,出现了reactive 响应式编程思想(参考响应式编程文章),并且为网络编程提供相关响应式编程的支持,如提供webflux(参考另外一个教程),他是spring 提供的异步非阻塞的响应式的网络框架,可以充分利用多cpu并行处理一些功能,虽然不能提高单个请求的响应能力,但是总体可以提高多核的服务器性能,提高系统吞吐量和伸缩性,特别适合于i/o密集型服务,webflux对应的是老式的sevlet的阻塞式服务容器。

      而webclient提供的基于响应式的非阻塞的web请求客户端,对应的是老式的restTemplate这类,相对于老式的restTemplate,他不阻塞代码、异步执行。

     他的执行流程是先创建个()实例,之后调用get(),post()等调用方式,uri()指定路径,retrieve()用来发起请求并获得响应,bodyToMono()用来指定请求结果需要处理为String,并包装为Reactor的Mono对象,

    简单实例如下:

WebClient webClient = ();
Mono<String> mono = ().uri("").retrieve().bodyToMono();
(::println);

以下我们讲解具体的实例

以下加webclient的pom

<dependencies>
		<dependency>
			<groupId></groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId></groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
			<groupId></groupId>
			<artifactId>reactor-spring</artifactId>
			<version>1.0.</version>
		</dependency>
		<dependency>
			<groupId></groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
</dependencies>

一、webclient的使用

1、创建实例

WebClient webClient = ();
// 如果是调用特定服务的API,可以在初始化webclient 时使用,baseUrl
WebClient webClient = ("");

或者用构造者模式构造

	WebClient webClient1 = ()
				.baseUrl("")
				.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
				.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
				.build();

2、get请求

Mono<String> resp = ()
      .method()
      .uri("")
      .cookie("token","xxxx")
      .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .retrieve().bodyToMono();

 

3、post请求

 @Test
    public void testFormParam(){
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        ("name1","value1");
        ("name2","value2");
        Mono<String> resp = ().post()
                .uri("http:///test/demo_form.asp")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body((formData))
                .retrieve().bodyToMono();
        ("result:" + ());
    }

可以使用BodyInserters类提供的各种工厂方法来构造BodyInserter对象并将其传递给body方法。BodyInserters类包含从Object,Publisher,Resource,FormData,MultipartData等创建BodyInserter的方法。

4、post json

static class Book {
    String name;
    String title;
    public String getName() {
      return name;
    }
 
    public void setName(String name) {
       = name;
    }
 
    public String getTitle() {
      return title;
    }
 
    public void setTitle(String title) {
       = title;
    }
  }
 
  @Test
  public void testPostJson(){
    Book book = new Book();
    ("name");
    ("this is title");
    Mono<String> resp = ().post()
        .uri("http://localhost:8080/demo/json")
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .body((book),)
        .retrieve().bodyToMono();
    ("result:{}",());
  }

5、上传文件:

@Test
  public void testUploadFile(){
    HttpHeaders headers = new HttpHeaders();
    (MediaType.IMAGE_PNG);
    HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource(""), headers);
    MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
    ("file", entity);
    Mono<String> resp = ().post()
        .uri("http://localhost:8080/upload")
        .contentType(MediaType.MULTIPART_FORM_DATA)
        .body((parts))
        .retrieve().bodyToMono();
    ("result:{}",());

6、失败处理

 @Test
    public void testFormParam4xx(){
        WebClient webClient = ()
                .baseUrl("")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
                .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
                .build();
         responseSpec = ()
                .uri("/user/repos?sort={sortField}&direction={sortDirection}",
                        "updated", "desc")
                .retrieve();
        Mono<String> mono = responseSpec
                .onStatus(e -> e.is4xxClientError(),resp -> {
                    ("error:{},msg:{}",().value(),().getReasonPhrase());
                    return (new RuntimeException(().value() + " : " + ().getReasonPhrase()));
                })
                .bodyToMono()
                .doOnError(, err -> {
                    ("ERROR status:{},msg:{}",(),());
                    throw new RuntimeException(());
                })
                .onErrorReturn("fallback");
        String result = ();
        (result);
    }
  1. 可以使用onStatus根据status code进行异常适配

  2. 可以使用doOnError异常适配

  3. 可以使用onErrorReturn返回默认值

7、指定url,并且替换api路径

      在应用中使用WebClient时也许你要访问的URL都来自同一个应用,只是对应不同的URL地址,这个时候可以把公用的部分抽出来定义为baseUrl,然后在进行WebClient请求的时候只指定相对于baseUrl的URL部分即可。这样的好处是你的baseUrl需要变更的时候可以只要修改一处即可。下面的代码在创建WebClient时定义了baseUrl为http://localhost:8081,在发起Get请求时指定了URL为/user/1,而实际*问的URL是http://localhost:8081/user/1

String baseUrl = "http://localhost:8081";
WebClient webClient = (baseUrl);
Mono<User> mono = ().uri("user/{id}", 1).retrieve().bodyToMono();

8、retrieve和exchange

     retrieve方法是直接获取响应body,但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。由于响应的得到是异步的,所以都可以调用 block 方法来阻塞当前程序,等待获得响应的结果。

    以下代码是把获取写入的cookie

String baseUrl = "http://localhost:8081";
WebClient webClient = (baseUrl);

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
("username", "u123");
("password", "p123");

Mono<ClientResponse> mono = ().uri("login").syncBody(map).exchange();
ClientResponse response = ();
if (() == ) {
    Mono<Result> resultMono = ();
    (result -> {
        if (()) {
            ResponseCookie sidCookie = ().getFirst("sid");
            Flux<User> userFlux = ().uri("users").cookie((), ()).retrieve().bodyToFlux();
            (::println);
        }
    });
}

9、filter

      WebClient也提供了Filter,对应于接口,其接口方法定义如下。Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next)在进行拦截时可以拦截request,也可以拦截response。

增加基本身份验证:

WebClient webClient = ()
    .baseUrl(GITHUB_API_BASE_URL)
    .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
    .filter(ExchangeFilterFunctions
            .basicAuthentication(username, token))
    .build();

过滤response:

 @Test
    void filter() {
        Map<String, Object> uriVariables = new HashMap<>();
        ("p1", "var1");
        ("p2", 1);
        WebClient webClient = ().baseUrl("")
                .filter(logResposneStatus())
                .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/.v3+json")
                .build();
        Mono<String> resp1 = webClient
                .method()
                .uri("/")
                .cookie("token","xxxx")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .retrieve().bodyToMono();
        String re=  ();
        ("result:" +re);

    }

    private ExchangeFilterFunction logResposneStatus() {
        return (clientResponse -> {
            ("Response Status {}", ());
            return (clientResponse);
        });
    }

使用过滤器记录日志:

WebClient webClient = ()
    .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) -> {
        ("Request: {} {}", (), ());
        ()
                .forEach((name, values) -> (value -> ("{}={}", name, value)));
        return (clientRequest);
    };
}

 

注意:

将response body 转换为对象/集合

  • bodyToMono

    如果返回结果是一个Object,WebClient将接收到响应后把JSON字符串转换为对应的对象。

  • bodyToFlux

    如果响应的结果是一个集合,则不能继续使用bodyToMono(),应该改用bodyToFlux(),然后依次处理每一个元素。

 

二、webclient原理

webclient的整个实现技术基本采用reactor-netty实现,下章我讲述相关细节

参考:https:///article/

               /spring/docs/current/spring-framework-reference/#webflux-client