【webclient】WebClient的创建、使用,自定义过滤器

在Spring5中,出现了Reactive响应式编程思想,并且为网络编程提供相关响应式编程的支持,如提供了WebFlux,它是Spring提供的异步非阻塞的响应式的网络框架,相比传统的SpringMVC框架,可以充分利用多CPU并行处理一些功能,虽然不能提高单个请求的响应能力,但是总体可以提高多核的服务器性能,提高系统吞吐量和伸缩性,特别适合于IO密集型服务。

WebClient提供的基于响应式的非阻塞的Web请求客户端,相对于传统的RestTemplate,他不阻塞代码、异步执行。

使用WebClient需要引入下面的依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

WebClient的创建

WebClient可以直接通过new来创建,也可以使用构造者模式来构造。

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * WebClient的创建
 */
public class WebClientDemo1 {

    public static void main(String[] args) throws InterruptedException {
        WebClient webClient = WebClient.create();
        webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve()
                .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println);

        WebClient webClient2 = WebClient.builder()
                .baseUrl("http://127.0.0.1:8020")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
        webClient2.get().uri("/order/findOrderByUserId?userId={userId}", 1).retrieve()
                .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println);
        TimeUnit.SECONDS.sleep(5);
    }
}

在应用中使用WebClient时也许你要访问的URL都来自同一个应用,只是对应不同的URI地址,这个时候可以把公用的部分抽出来定义为baseUrl,然后在进行WebClient请求的时候只指定相对于baseUrl的URL部分即可。这样的好处是你的baseUrl需要变更的时候可以只要修改一处即可。

WebClient发送Get请求

先创建个webclient.create()实例,之后调用get()、post()等调用方式,uri()指定路径,retrieve()用来发起请求并获得响应,bodyToFlux(Order.class)用来将请求结果需要处理为Order数组,并包装为Reactor的Flux对象。

如果返回结果是一个JSON字符串,可以使用bodyToMono(),将接收到的JSON字符串转换为对应的对象。

如果返回结果是一个JSON数组,可以使用bodyToFlux(),将接收到的JSON数组转换为对应的对象集合,然后依次处理每一个元素。

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * WebClient发送Get请求
 */
public class WebClientGetDemo {

    public static void main(String[] args) throws InterruptedException {
        WebClient webClient = WebClient.create();
        webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve()
                .bodyToFlux(Order.class).subscribe(System.out::println);;

        // 休眠一会,否则WebClient中的线程池还没执行,看不到效果
        TimeUnit.SECONDS.sleep(5);
    }
}

WebClient发送Post请求

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

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * WebClient发送Post请求
 */
public class WebClientPostDemo {

    public static void main(String[] args) {
        WebClient webClient = WebClient.create();
        Order order = new Order();
        order.setId(1L);
        order.setUserId(666L);
        order.setGoodName("Iphone 13");
        order.setPrice(9999);
        Mono<Long> mono = webClient.post().uri("http://127.0.0.1:8020/order/saveOrder")
                .body(BodyInserters.fromValue(order))
                // .body(Mono.just(order), Order.class)
                .retrieve()
                .bodyToMono(Long.class);

        // 阻塞等待获取结果
        System.out.println(mono.block());
    }
}

WebClient对失败的处理

java 复制代码
package com.morris.user.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;

/**
 * WebClient对失败的处理
 */
@Slf4j
public class WebClientDealFailDemo {

    public static void main(String[] args) {
        WebClient webClient = WebClient.create();
        WebClient.ResponseSpec responseSpec = webClient.get().uri("http://127.0.0.1:8020/order/error")
                .retrieve();

        Mono<String> mono = responseSpec
                .onStatus(HttpStatus::is4xxClientError, resp -> {
                    log.error("error4xx:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase());
                    return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));
                })
                .bodyToMono(String.class)
                .doOnError(WebClientResponseException.class, err -> {
                    log.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString());
                    throw new RuntimeException(err.getMessage());
                })
                .onErrorReturn("fallback");

        // 阻塞等待获取结果
        System.out.println(mono.block());
    }
}

可以使用onStatus根据响应的status code进行适配,可以使用doOnError对异常进行适配,可以使用onErrorReturn返回默认值。

exchange()

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

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.concurrent.TimeUnit;

/**
 * WebClient使用Exchange发送请求
 */
public class WebClientExchangeDemo {

    public static void main(String[] args) throws InterruptedException {
        WebClient webClient = WebClient.create();
        webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1)
                .exchange()
                .subscribe(r -> {
                    System.out.println(r.headers());
                    r.bodyToFlux(Order.class).subscribe(System.out::println);
                });

        // 休眠一会,否则WebClient中的线程池还没执行,看不到效果
        TimeUnit.SECONDS.sleep(5);
    }
}

filter

WebClient也提供了Filter,对应于org.springframework.web.reactive.function.client.ExchangeFilterFunction接口,可以拦截request,也可以拦截response。

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.concurrent.TimeUnit;

/**
 * WebClient使用filter拦截器
 */
@Slf4j
public class WebClientFilterDemo {

    private static ExchangeFilterFunction logResponseStatus() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            log.info("Response Status {}", clientResponse.statusCode());
            return Mono.just(clientResponse);
        });
    }

    public static void main(String[] args) throws InterruptedException {
        WebClient webClient = WebClient.builder().filter(logResponseStatus()).build();
        webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1)
                .exchange()
                .subscribe(r -> {
                    System.out.println(r.headers());
                    r.bodyToFlux(Order.class).subscribe(System.out::println);
                });

        // 休眠一会,否则WebClient中的线程池还没执行,看不到效果
        TimeUnit.SECONDS.sleep(5);
    }
}

Attributes

可以使用attribute在多个filter之间传递参数。

java 复制代码
package com.morris.user.demo;

import com.morris.user.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * WebClient使用attribute传递参数
 */
@Slf4j
public class WebClientAttributesDemo {

    private static ExchangeFilterFunction filterRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            Optional<Object> myAttribute = clientRequest.attribute("myAttribute");
            System.out.println(myAttribute.get());
            return Mono.just(clientRequest);
        });
    }

    public static void main(String[] args) throws InterruptedException {
        WebClient webClient = WebClient.builder().filter(filterRequest()).build();
        webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1)
                .attribute("myAttribute", "myAttribute")
                .exchange()
                .subscribe(r -> {
                    System.out.println(r.headers());
                    r.bodyToFlux(Order.class).subscribe(System.out::println);
                });

        // 休眠一会,否则WebClient中的线程池还没执行,看不到效果
        TimeUnit.SECONDS.sleep(5);
    }
}
相关推荐
haciii8 分钟前
Spring Boot启动源码深度分析 —— 新手也能看懂的原理剖析
spring boot
泉城老铁6 小时前
Spring Boot对接抖音获取H5直播链接详细指南
spring boot·后端·架构
后端小张1 天前
基于飞算AI的图书管理系统设计与实现
spring boot
考虑考虑2 天前
Jpa使用union all
java·spring boot·后端
阿杆2 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C3 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*3 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*3 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu3 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
计算机毕业设计木哥3 天前
计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
java·vue.js·spring boot·mysql·spark·毕业设计·课程设计