WebClient 使用学习记录

前言

​ 最近接手了一个新的需求,需要使用到网络请求相关,经过思考决定使用比restTemplate更新的WebClient。

​ WebClient是Spring 5.0推出的用来替代restTemplate的非阻塞式Reactive响应式编程思想,并且为网络编程提供相关响应式编程的支持.

一、相关技术背景介绍

​ 相比传统阻塞IO模式,每次连接都需要独立的线程,导致并发量变大后,会产生大量线程,占用很大一部分的系统资源。

​ 为了解决Spring MVC等框架在高并发场景下的性能瓶颈,推出了Spring WebFlux,Spring WebFlux是基于响应式流的,因此可以用来建立异步的、非阻塞的、事件驱动的服务。它采用Reactor作为首选的响应式流的实现库。

​ 由于响应式编程的特性,Spring WebFlux和Reactor底层需要支持异步的运行环境,比如Netty(非阻塞IO通信框架)。Netty 是一个广泛使用的 Java 网络编程框架,前面说过,推出WebFlux是为了提高并发的,为什么选择Netty,是因为Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),NIO的并发性能有很大的提高。

二、IO相关知识点

  • BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善

  • NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

  • AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

    上面我们知道了,BIO每次请求都要一次线程,NIO一个线程能处理多个请求,这是为什么呢?

原因就是那个多路复用器,当一个连接建立之后,他有两个步骤要做,

  1. 第一步是接收完客户端发过来的全部数据
  2. 第二步是服务端处理完请求业务之后返回response给客户端。

NIO和BIO的区别主要是在第一步。
在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。
而NIO中,当一个Socket建立好之后,线程并不会阻塞去接受这个Socket,而是将这个请求交给多路复用器,多路复用器会不断的去遍历所有的请求连接,一旦有一个请求连接建立完成,他会通知线程,然后线程处理完数据再返回给客户端——这个过程是不阻塞的,这样就能让一个线程处理更多的请求了。

除了BIO和NIO,还有其他几种IO模型

  1. BIO,同步阻塞IO,阻塞整个步骤,如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。
  2. NIO,同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景,比如聊天软件。
  3. 多路复用IO,他的两个步骤处理是分开的,也就是说,一个连接可能他的数据接收是线程a完成的,数据处理是线程b完成的,他比BIO能处理更多请求。
  4. 信号驱动IO,这种IO模型主要用在嵌入式开发,不参与讨论。
  5. 异步IO,他的数据请求和数据处理都是异步的,数据请求一次返回一次,适用于长连接的业务场景。

其中,Java的NIO对应linux的multiplexing io,也就是多路复用IO。

三、 WebClient的使用

3.1 引入依赖
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
	</dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <!--            <version>2.8.2</version>-->
    </dependency>

3.2 创建实例
WebClient webClient = WebClient.create();
// 如果是调用特定服务的API,可以在初始化webclient 时使用,baseUrl
WebClient webClient = WebClient.create("https://api.github.com");

或者使用构造器

	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();
3.3 Get, Mono是WebClint返回的结果类型
Mono<String> resp = WebClient.create()
      .method(HttpMethod.GET)
      .uri("http://www.baidu.com")
      .cookie("token","xxxx")
      .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .retrieve().bodyToMono(String.class);
3.4 POST 普通形式
 @Test
    public void testFormParam(){
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        formData.add("name1","value1");
        formData.add("name2","value2");
        Mono<String> resp = WebClient.create().post()
                .uri("http://www.w3school.com.cn/test/demo_form.asp")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData(formData))
                .retrieve().bodyToMono(String.class);
        System.out.print("result:" + resp.block());
    }
3.5 POST 业务需求,参数通过读取文件获取,文件内容是JSON格式

其实重要的是动态生成MultiValueMap的,其他一样

  // 读取request.body文件的数据
        byte[] bytes;
        try {
            try(InputStream inputStream = new FileInputStream(requestParameter.getBodyFile())) {
                bytes = IOUtils.toByteArray(inputStream);
            }
        } catch (IOException e) {
            log.error(message);
            return Mono.error(new ServiceRequestException(message, e));
        }
        // 字节数组转字符串
        String strBody = new String(bytes, "UTF-8");
        // 字符串转Map
        Gson gson = new Gson();
        Map<String, Object> requestParams = new HashMap<String, Object>();
        requestParams = gson.fromJson(strBody, requestParams.getClass());

        // 动态构建参数
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        requestParams.forEach((key, value) -> {
            if (value instanceof String) {
                map.add(key.toString(), value.toString());
            } else {
                map.add(key.toString(), JsonUtil.serializeObject(value));
            }
        });
        String url = requestParameter.getUrl();
        String method = requestParameter.getMethod().toString();

        Mono<ResponseResult> mono = null;
        if (method.equalsIgnoreCase("post")){
          mono = webClient.post().uri(url).bodyValue(map).exchange().flatMap(response -> {
            return  responsePackager.pack(requestParameter.getId(), requestParameter.getServiceName(), response,requestParamsStr);
        });
          System.out.println("customResult[POST]:" + mono.block());
        }
        ResponseResult block = mono.block();
        System.out.println(block.toString());

​ 在body使用MultiValueMap类型的参数,如果没设置,默认就是表单的形式发送。

3.6 retrieve和exchange的区别

​ retrieve方法是直接获取响应body,但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。

四、参考材料

一、 Netty入门教程——认识Netty

二、webclient使用介绍

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值