Spring5.0 — WebClient(响应式web客户端)

一、介绍

1.1、RestTemplate 

同步阻塞代码,http 请求返回响应才继续执行。

1.2、WebClient 

1.基于 Reactor 和 Netty。
2.响应式 web 客户端。异步执行不阻塞代码,少量的线程数处理高并发的 Http 请求。
3.集成 Spring WebFlux 框架,可与其他 Spring 组件无缝协作。
4.可通过自定义 ExchangeFilterFunction 对请求和响应进行拦截和处理。

二、使用 

2.1、引入依赖 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2.2、基础属性介绍

2.2.1、基础配置

HTTP 底库
// 选择 HTTP 底库; 默认底层用Netty,切换Jetty。
WebClient
         .builder()
         .clientConnector(new JettyClientHttpConnector())
		 .build();


全局的请求配置
// 设置基础的全局的web请求配置,如cookie、header、baseUrl。
WebClient
		.builder()
		.defaultCookie("kl","kl")
		.defaultUriVariables(ImmutableMap.of("name","kl"))
		.defaultHeader("header","kl")
		.defaultHeaders(httpHeaders -> {
			httpHeaders.add("header1","kl");
			httpHeaders.add("header2","kl");
		})
		.defaultCookies(cookie ->{
			cookie.add("cookie1","kl");
			cookie.add("cookie2","kl");
		})
		.baseUrl("http://www.kailing.pub")
		.build();


Filter
// Filter 过滤器,统一修改拦截请求。
WebClient
	.builder()
	.baseUrl("http://www.kailing.pub")
	.filter((request, next) -> {
		ClientRequest filtered = ClientRequest.from(request)
				.header("foo", "bar")
				.build();
		return next.exchange(filtered);
	})
	.filters(filters ->{
		filters.add(ExchangeFilterFunctions.basicAuthentication("username","password"));
		filters.add(ExchangeFilterFunctions.limitResponseSize(800));
	})
	.build().get()
	.uri("/article/index/arcid/{id}.html", 254)
	.retrieve()
	.bodyToMono(String.class)
	.subscribe(System.err::println);


Netty 库配置 
// 配置动态连接池 ConnectionProvider provider = ConnectionProvider.elastic("elastic pool"); 配置固定大小连接池,如最大连接数、连接获取超时、空闲连接死亡时间等
ConnectionProvider provider = ConnectionProvider.fixed("fixed", 45, 4000, Duration.ofSeconds(6));
HttpClient httpClient = HttpClient.create(provider)
		.secure(sslContextSpec -> {
			SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().trustManager(new File("E://server.truststore"));
			sslContextSpec.sslContext(sslContextBuilder);
		}).tcpConfiguration(tcpClient -> {
			// 指定Netty的 select 和 work 线程数量
			LoopResources loop = LoopResources.create("kl-event-loop", 1, 4, true);
			return tcpClient.doOnConnected(connection -> {
				// 读写超时设置
				connection
				.addHandlerLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
				.addHandlerLast(new WriteTimeoutHandler(10));
			})
				// 连接超时设置
			   .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
			   .option(ChannelOption.TCP_NODELAY, true)
			   .runOn(loop);
		});
WebClient.builder()
		.clientConnector(new ReactorClientHttpConnector(httpClient))
		.build();

2.2.2、WebClient.builder() 的选项

uriBuilderFactory:自定义UriBuilderFactory用作基本URL(BaseUrl)。
defaultHeader:每个请求的标题。
defaultCookie:针对每个请求的Cookie。
defaultRequest:Consumer自定义每个请求。
filter:针对每个请求的客户端过滤器。
exchangeStrategies:HTTP消息读取器/写入器定制。
clientConnector:HTTP客户端库设置。

2.2.3、请求类型、与返回结果

.block()
   阻塞当前程序等待结果
.retrieve()
   直接获取响应body
.exchange()
   可访问整个ClientResponse

2.3、get 请求

// 简单传参。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
WebClient client = WebClient.create("http://www.kailing.pub");
Mono<String> result = client.get()
		.uri("/article/arcid/{id}", 256)
		.acceptCharset(StandardCharsets.UTF_8)
		.accept(MediaType.TEXT_HTML)
		.retrieve()     // 同步
		.bodyToMono(String.class);
result.subscribe(System.err::println);


// 复杂传参 — MultiValueMap。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("name", "kl");
params.add("age", "19");
// 定义 url 参数 
Map<String, Object> uriVariables = new HashMap<>(); 
uriVariables.put("id", 200);
String uri = UriComponentsBuilder.fromUriString("/article/arcid/{id}")
		.queryParams(params)
		.uriVariables(uriVariables)
		.toUriString();
Mono<String> result = client.get()
		.uri(uri)
		.acceptCharset(StandardCharsets.UTF_8)
		.accept(MediaType.TEXT_HTML)
		.retrieve()
		.bodyToMono(String.class);
result.subscribe(System.err::println);


// 复杂传参 — UriBuilder。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
Mono<String> resp = WebClient.create()
			.get()
			.uri(uriBuilder -> uriBuilder
					.scheme("http")
					.host("www.baidu.com")
					.path("/s")
					.queryParam("wd", "北京天气")
					.queryParam("other", "test")
					.build())
			.retrieve()
			.bodyToMono(String.class);

2.4、post 请求

// 表单 默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
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);
	LOGGER.info("result:{}",resp.block());

// FormInserter  表单:参数、文件。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
WebClient client = WebClient.create("http://www.kailing.pub");
FormInserter formInserter = fromMultipartData("name","kl")
		.with("age",19)
		.with("map",ImmutableMap.of("xx","xx"))
		.with("file",new File("C://xxx.doc"));
Mono<String> result = client.post()
		.uri("/article/index/arcid/{id}.html", 256)
		.contentType(MediaType.APPLICATION_JSON)
		.body(formInserter)
		//.bodyValue(ImmutableMap.of("name","kl"))
		.retrieve()
		.bodyToMono(String.class);
result.subscribe(System.err::println);


// json — 实体类。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
User user = new User();
user.setName("aaa");
user.setTitle("AAAAAA");
Mono<String> resp = WebClient.create()
    .post()
	.uri("http://localhost:8080/demo/json")
	.contentType(MediaType.APPLICATION_JSON_UTF8)
	.body(Mono.just(user),User.class)
	.retrieve().bodyToMono(String.class);
System.out.println("---resp.block(): "+resp.block());


// json — raw。默认异步,通过 Mono 的 subscribe 订阅响应值。block() 阻塞当前线程获取返回值。
Mono<String> resp = WebClient.create()
		.post()
		.uri("http://localhost:8080/demo/json")
		.contentType(MediaType.APPLICATION_JSON_UTF8)
		.body(BodyInserters.fromObject("{\n" +
				"  \"title\" : \"this is title\",\n" +
				"  \"author\" : \"this is author\"\n" +
				"}"))
		.retrieve().bodyToMono(String.class);
System.out.println("---resp.block(): "+resp.block());


// 二进制上传文件
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource("parallel.png"), headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("file", entity);
Mono<String> resp = WebClient.create()
		.post()
		.uri("http://localhost:8080/upload")
		.contentType(MediaType.MULTIPART_FORM_DATA)
		.body(BodyInserters.fromMultipartData(parts))
		.retrieve().bodyToMono(String.class);
System.out.println("---resp.block(): "+resp.block());

2.5、WebSocketClient  使用 Socket

WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->session.receive().doOnNext(System.out::println).then());

三、封装工具类 

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * 
 */
public class WebClientUtils {
    	
    private WebClient webClient;
	
    public WebClientUtils(String baseUrl) {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
				.filter(logRequest())
                .build();
    }
	
    public <T> Mono<T> get(String uri, Class<T> responseType) {
        return webClient.get()
                .uri(uri)
                .retrieve()
                .bodyToMono(responseType);
    }
	
    public <T> Mono<T> post(String uri, Object request, Class<T> responseType) {
        return webClient.post()
                .uri(uri)
                .body(BodyInserters.fromValue(request))
                .retrieve()
                .bodyToMono(responseType);
    }
	
    public <T> Mono<T> put(String uri, Object request, Class<T> responseType) {
        return webClient.put()
                .uri(uri)
                .body(BodyInserters.fromValue(request))
                .retrieve()
                .bodyToMono(responseType);
    }
	
    public <T> Mono<T> delete(String uri, Class<T> responseType) {
        return webClient.delete()
                .uri(uri)
                .retrieve()
                .bodyToMono(responseType);
    }
	
	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);
		};
	}

}


public class Test001 {
    public static void main(String[] args) {
        WebClientUtils webClientUtils = new WebClientUtils("https://api.example.com");
		
        // 发起 GET 请求
        webClientUtils.get("/users/1", User.class).subscribe(user -> System.out.println("GET response: " + user));
		
        // 发起 POST 请求
        User newUser = new User("John", "Doe");
        webClientUtils.post("/users", newUser, User.class).subscribe(user -> System.out.println("POST response: " + user));
		
        // 发起 PUT 请求
        User updatedUser = new User("Jane", "Doe");
        webClientUtils.put("/users/1", updatedUser, User.class).subscribe(user -> System.out.println("PUT response: " + user));
		
        // 发起 DELETE 请求
        webClientUtils.delete("/users/1", Void.class).subscribe(response -> System.out.println("DELETE response: " + response));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/329827.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言基础考研向】05 scanf读取标准输入超详解

文章目录 一.scanf函数的原理 样例问题原因解决方法 二.多种数据类型混合输入 错误样例正确样例 一.scanf函数的原理 C语言未提供输入/输出关键字&#xff0c;其输入和输出是通过标准函数库来实现的。C语言通过scanf函数读取键盘输入&#xff0c;键盘输入又被称为标准输入。…

JavaScript 类型判断及类型转换规则

文章目录 JavaScript 类型及其判断使用 typeof 判断类型使用 instanceof 判断类型使用 constructor 和 Object.prototype.toString 判断类型JavaScript 类型及其转换JavaScript 函数参数传递cannot read property of undefined 问题解决方案分析一道网红题目JavaScript 类型判断…

thinkphp 可执行文件think

think 是一个可执行文件&#xff0c;位置&#xff1a;网站根目录 内容&#xff1a;1 定义项目路径 2 加载cll框架文件 shell脚本里第一行的&#xff1a;#!/usr/bin/env php 什么意思 这句#!的含义就是&#xff0c;按照环境变量PATH寻找第一个php程序来执行。 #!/usr/bin/php…

大语言模型系列-ELMo

文章目录 前言一、ELMo的网络结构和流程二、ELMo的创新点总结 前言 在前文大语言模型系列-word2vec已经提到word2vec的缺点&#xff1a; 为每个词汇表中每个分词静态生成一个对应的词向量表示&#xff0c;没有考虑到语境&#xff0c;因此无法无法处理多义词 ps&#xff1a;先…

【Spring Boot 3】【Redis】分布式锁

【Spring Boot 3】【Redis】分布式锁 背景介绍开发环境开发步骤及源码工程目录结构总结 背景 软件开发是一门实践性科学&#xff0c;对大多数人来说&#xff0c;学习一种新技术不是一开始就去深究其原理&#xff0c;而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经…

【文档数据库】ES和MongoDB的对比

目录 1.由文档存储牵出的问题 2.什么是MongoDB&#xff1f; 3.ES和MongoDB的对比 1.由文档存储牵出的问题 本文或者说关于mongodb的这个系列文章的源头&#xff1a; 前面我们聊过了分布式链路追踪系统&#xff0c;在基于日志实现的分布式链路追踪的方式seluthzipkin中为了…

VsCode 常见的配置

转载&#xff1a;Visual Studio Code 常见的配置、常用好用插件以及【vsCode 开发相应项目推荐安装的插件】 - 知乎 (zhihu.com) 一、VsCode 常见的配置 1、取消更新 把插件的更新也一起取消了 2、设置编码为utf-8&#xff1a;默认就是了&#xff0c;不用设置了 3、设置常用的…

beego的控制器Controller篇 — 错误处理

1 错误处理 在做 Web 开发的时候&#xff0c;经常需要页面跳转和错误处理&#xff0c;beego 这方面也进行了考虑&#xff0c;通过 Redirect 方法来进行跳转&#xff1a; func (this *AddController) Get() {this.Redirect("/", 302) } 如何中止此次请求并抛出异常…

TDengine 企业级功能:存储引擎对多表低频场景优化工作分享

在去年 8 月份发布的 3.1.0.0 版本中&#xff0c;TDengine 进行了一系列重要的企业级功能更新&#xff0c;其中包括对多表低频场景写入性能的大幅优化。这一优化工作为有此需求的用户提供了更大的便捷性和易用性。在本文中&#xff0c;TDengine 的资深研发将对此次优化工作进行…

C语言--带哨兵位的双向循环链表的创建及使用详解

C语言--带哨兵位的双向循环链表的创建及使用详解 1. 双向循环链表定义1.1 定义1.2 优点&#xff1a;1.3 物理结构 2. 双向链表的创建2.1 文件创建2.2 节点创建 3. 链表操作3.1 初始化3.2 显示3.3 尾插3.4 头插3.5 尾删3.6 头删3.7 查找3.8 指定位置前插入3.9 指定位置删除3.10 …

【Qt之模型视图】2. 模型类及QModelIndex模型索引、自定义模型

1. 模型类 在模型/视图体系结构中&#xff0c;模型提供了一个标准接口&#xff0c;视图和委托使用该接口访问数据。在Qt中&#xff0c;标准接口是由QAbstractItemModel类定义的。无论数据项如何存储在任何底层数据结构中&#xff0c;QAbstractItemModel的所有子类都会以层次结…

GPT APP的开发步骤

开发一个GPT&#xff08;Generative Pre-trained Transformer&#xff09; Store&#xff08;存储&#xff09;涉及到使用预训练的语言模型&#xff08;例如GPT-3&#xff09;来生成和管理内容。以下是一般的步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&…

云计算入门——Linux 命令行入门

云计算入门——Linux 命令行入门 前些天发现了一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站。 介绍 如今&#xff0c;我们许多人都熟悉计算机&#xff08;台式机和笔记本电…

vscode 中配置 python 虚拟环境

vscode 中配置 python 虚拟环境 Start 在编写代码的过程中&#xff0c;我们经常会用到一些第三方依赖&#xff0c;帮助我们快速完成功能。在 Python 中&#xff0c;默认情况都是统一安装在全局环境中&#xff0c;但是这样伴随着电脑项目越来越多&#xff0c;不同项目对依赖的…

XTuner 大模型单卡低成本微调实战

1 概述 1.1 XTuner 一个大语言模型微调工具箱。由 MMRazor 和 MMDeploy 联合开发。 1.2 支持的开源LLM (2023.11.01) InternLM ✅Llama&#xff0c;Llama2ChatGLM2&#xff0c;ChatGLM3QwenBaichuan&#xff0c;Baichuan2…Zephyr 1.3 特色 &#x1f913; 傻瓜化&#xf…

pytest学习和使用-pytest如何进行分布式测试?(pytest-xdist)

1 什么是分布式测试&#xff1f; 在进行本文之前&#xff0c;先了解些基础知识&#xff0c;什么是分布式测试&#xff1f;分布式测试&#xff1a;是指通过局域网和Internet&#xff0c;把分布于不同地点、独立完成特定功能的测试计算机连接起来&#xff0c;以达到测试资源共享…

Ubuntu系统环境搭建(十三)——使用docker-compose安装redis

ubuntu环境搭建专栏&#x1f517;点击跳转 Ubuntu系统环境搭建&#xff08;十三&#xff09;——使用docker-compose安装redis 文章目录 Ubuntu系统环境搭建&#xff08;十三&#xff09;——使用docker-compose安装redis1.搭建文件夹2.docker-compose.yaml配置文件3.redis.co…

银河麒麟操作系统 v10 中离线安装 Docker

银河麒麟操作系统 v10 中离线安装 Docker 1. 查看系统版本2. 查看 Linux 内核版本&#xff08;3.10以上&#xff09;3. 查看 iptabls 版本&#xff08;1.4以上&#xff09;4. 判断处理器架构5. 离线下载 Docker 安装包6. 移动解压出来的二进制文件到 /usr/bin 目录中7. 配置 Do…

Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析

目录 1.配置访问控制鉴权流程的2种方式 2.Shiro内置过滤器 3.权限控制流程解析 3.1 权限&#xff08;Permissions&#xff09;配置和初始化流程 3.1.1 通过filterChainDefinitionMap配置url权限 3.1.1.1 应用层配置方式 3.1.1.2 配置解析过程 3.1.1.2.1 FilterChainMan…

css3 纯代码案例

css3 纯代码案例 前言渐变之美1.1 纯CSS3实现的渐变背景1.2 使用多重颜色和方向打造丰富渐变效果1.3 渐变色停留动画的巧妙运用 纯CSS图形绘制2.1 使用border属性制作三角形、梯形等形状伪类箭头图标2.2 利用transform创建旋转、缩放的图形 浮动的阴影敲代码css准备reset 样式复…