精华内容
下载资源
问答
  • webclient使用
    2022-02-13 15:09:00

    现在,越来越多的项目都开始使用反应式编程以及异步处理请求了。在 Spring 5中,引入了反应式 WebClient实现作为 WebFlux 框架的一部分。今天,我们就来学习下如何使用 WebClient反应式地请求 REST API。

    定义 REST API

    首先,我们先定义一些 REST API(假设我们数据库里保存了一系列的事件,这些事件有id、属性、分类及标签等):

    • /events - 获取所有的事件
    • /events/[id] - 通过 id 获取事件
    • /events/[id]/atrributes/[attributeId] - 通过属性id获取某个事件的属性
    • /events?name=[name]&startDate=[startDate] - 根据给定条件获取事件
    • /events?tag[]=[tag1]&tag[]=[tag2] - 根据标签获取事件
    • /events?category=[category1]&category=[category2] - 根据分类获取事件

    以上,我们定义了一些不同的 URI。等下,我们就来看下如何使用 WebClient 来构建和发送每种类型的 URI

    需要注意的是,通过标签和分类查找事件都包含了数组查询参数,但是它们的语法不同。因为在 URI 中数组的表示没有严格定义,这主要取决于服务端的实现。在这里,我们两种方式都覆盖一下。

    WebClient 设置

    首先,我们需要创建一个WebClient实例。在这篇文章中,我们使用 mocked 的对象来验证是否请求了一个有效的 URI

    我们先定义一个 client 以及相关的 mocked 对象。

    this.exchangeFunction = mock(ExchangeFunction.class);
    ClientResponse mockResponse = mock(ClientResponse.class);
    when(this.exchangeFunction.exchange(this.argumentCaptor.capture())).thenReturn(Mono.just(mockResponse));
    this.webClient = WebClient
    		.builder()
    		.baseUrl("https://example.com/api")
    		.exchangeFunction(exchangeFunction)
    		.build();
    

    在上面的定义中,我们设置了一个参数 baseUrl,webClient 会将这个参数的值添加到它发送的所有请求之前。

    最后,要验证特定的 URI 是否已经传递给底层的 ExchangeFunction 实例,我们需要使用以下方法:

    private void verifyCalledUrl(String relativeUrl) {
    	ClientRequest request = this.argumentCaptor.getValue();
    	Assert.assertEquals(String.format("%s%s", BASE_URL, relativeUrl, request.url().toString());
    	Mockito.verify(this.exchangeFunction).exchange(request);
    	verifyNoMoreInteractions(this.exchangeFunction);
    }
    

    WebClientBuilder 类具有将 UriBuilder 实例作为参数提供的 uri() 方法。通常,API 调用通过以下方式进行:

    this.webClient.get()
    		.uri(uriBuilder -> uriBuilder
    		// ... builder a URI
    		.build())
    	.retrieve());
    

    后面,我们将广泛使用 UriBuilder 来构建 URI。需要注意的是,我们可以使用任何其它方式构建 URI,然后将生成的 URI 作为字符串传递进去。

    URI 路径构成

    URI 路径构成由一系列的由斜杠(/)分隔的路径段组成。首先,我们先看下最简单的没有任何可变段的/events简单案例。

    this.webClient.get()
    	.uri("/events")
    	.retrieve();
    verifyCalledUrl("/events");
    

    在这个例子中,我们仅传递了一个String类型的数据作为参数。

    接着,我们使用 /events/{id} 访问点并构建相应的 URI:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events/{id}")
    		.build(2))
    	.retrieve();
    verifyCalledUrl("/events/2");
    

    从上面的代码中,我们可以看到实际的 {id} 值 2 被传递给了 biild() 方法。

    同样我们可以为 /events/{id}/attributes/{attributeId} 访问点创建一个包含多个路径段的 URI:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events/{id}/atrributes/{attributeId}")
    		.build(3, 12))
    	.retrieve();
    verifyCalledUrl("/events/3/attributes/13");
    

    在最终的 URI 长度没有超出限制的情况下,一个 URI 可以有很多路径段。我们只需要确保传递给 build() 方法的实际字段值的顺序需要正确。

    URI 查询参数

    通常情况下,一个查询参数是一个简单的键值对,比如 name=dengkaiting。我们来看下如何构建这样的 URI。

    单值参数

    我们从单值参数开始,采用 /events?name=[name]&startDate=[startDate]访问点。要设置查询参数,我们需要调用 UriBuilder 接口的 queryParam()方法:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events")
    		.queryParam("name", "InitFailed")
    		.queryParam("startDate", "13/02/2021")
    		.build())
    	.retrieve();
    verifyCalledUrl("/events?name=InitFailed&startDate=13/02/2021")
    

    我们添加了两个查询参数并给他们设置了实际值。此外,我们也可以使用占位符而不立即设置实际值:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events")
    		.queryParam("name", "{name}")
    		.queryParam("startDate", "{startDate}")
    		.build("InitFailed", "13/02/2021"))
    	.retrieve();
    verifyCalledUrl("/events?name=InitFailed&startTime=13%2F02%2F2021")
    

    当我们需要在调用链中进一步传递参数时,后面这种方式就比较方便了。上面两个代码片段里有一个重要的区别:
    我们可以看到对于预期的 URI,它们两个的编码方式是不同的。在后面这个例子中,斜线(/)被转义了。一般来说,RFC3986不需要在查询中对斜杠进行编码。但是,某些服务端应用可能需要这种转换。我们在后面会介绍下如果更改此行为。

    数组参数

    有时,我们可能需要传递一个值数组。其实,在查询字符串中传递数组并没有严格的限制。对于数组的表示通常取决于底层框架。下面,我们介绍最广泛使用的格式。

    我们以 /events?tag[]=[tag1]&tag[]=[tag2]访问点开始:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events")
    		.queryParam("tag[]", "node", "service")
    		.build())
    	.retrieve();
    verifyCalledUrl("/events?tag%5B%5D=node&tag%5B%5D=service")
    

    最终的 URL 包含多个标记参数,后面紧跟编码后的方括号。queryParam()方法接受可变参数作为值,因此我们不需要多次调用该方法。

    同样地,我们还可以省略方括号,只传递具有相同键但值不同的多个查询参数 - /events?category=[category1]&category=[category2]

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events")
    		.queryParams("category", "info", "warn")
    		.build())
    	.retrieve();
    verifyCalledUrl("/events?category=info&category=warn")
    

    还有一种更广泛使用的对数组进行编码的方法是传递逗号分隔的值。我们把前面的示例转为逗号分隔值:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder
    		.path("/events")
    		.queryParam("category", String.join(",", "info", "warn"))
    		.build())
    	.retrieve();
    verifyCalledUrl("/events?category=info,warn");
    

    我们只是使用 String 类的 join() 方法创建了一个逗号分隔的字符串。当然,我们可以使用应用期望的任何其它分隔符。

    编码模式

    我们在上面提到了 URL 的编码。

    如果默认行为不符合我们的要求,我们可以更改它。我们在创建WebClient实例的时候,可以提供一个 UriBuilderFactory 的实现来更改默认的编码模式。这里,我们使用 DefaultBuilderFactory类。要设置编码,需要调用 setEncodingMode() 方法。可以使用的模式有:

    • TEMPLATE_AND_VALUES:对 URI 模板进行预编码,扩展时对 URI 变量进行严格编码
    • VALUES_ONLY: 不对 URL 模板进行编码,而是将 URI 变量展开成模板后严格编码
    • URI_COMPONENTS:扩展 URI 变量后编码 URI 的组件值
    • NONE:不会应用任何编码

    默认值是 TEMPLATE_AND_VALUES。我们把模式设置成 URI_COMPONENTS

    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
    factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
    this.webClient = WebClient
    	.builder()
    	.uriBuilderFactory(factory)
    	.baseUrl(BASE_URL)
    	.exchangeFunction(exchangeFunction)
    	.build();
    

    最终,下面的断言可以成功:

    this.webClient.get()
    	.uri(uriBuilder -> uriBuilder)
    		.path("/events")
    		.queryParam("name", "InitFailed")
    		.queryParam("startDate", "13/02/2021")
    		.build()
    	.retrieve();
    verifyCalledUrl("/events?name=InitFailed&startDate=13/02/2021");
    

    当然,我们也可以提供一个完全自定义的 URIBuilderFactory 实现来手动处理 URI 创建。

    结论

    在这篇文章中,我们了解了如何使用 WebClientDefaultUriBuilder 构建不同类型的 URI。在此过程中,我们介绍了各种类型和格式的查询参数。最后,我们更改了 URL 构建器的默认编码模式。


    标题Spring WebClient 使用简介
    作者末日没有进行曲
    链接link
    时间:2021-02-12
    声明:本博客所有文章均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

    更多相关内容
  • 主要给大家介绍了关于Spring5中WebClient使用方法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring5具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • 本篇文章主要介绍了spring 5 webclient使用指南详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Spring WebClient使用

    2021-03-09 03:39:39
    如下为一个使用了线程池,SSL,出现指定异常后充实,超时配置的demoSSL配置packagecom.demo.client;importjava.io.FileInputStream;importjava.io.InputStream;importjava.security.KeyStore;importjava.security....

    如下为一个使用了线程池,SSL,出现指定异常后充实,超时配置的demo

    SSL配置

    packagecom.demo.client;importjava.io.FileInputStream;importjava.io.InputStream;importjava.security.KeyStore;importjava.security.PublicKey;importjava.security.cert.CertificateFactory;importjava.security.cert.X509Certificate;importjava.util.ArrayList;importjava.util.LinkedList;importjava.util.List;importjava.util.function.Consumer;importjavax.net.ssl.KeyManagerFactory;importjavax.net.ssl.SNIMatcher;importjavax.net.ssl.SNIServerName;importjavax.net.ssl.SSLEngine;importjavax.net.ssl.SSLParameters;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importio.netty.handler.ssl.SslContext;importio.netty.handler.ssl.SslContextBuilder;importreactor.netty.tcp.SslProvider.SslContextSpec;public class SslConsumer implements Consumer{private Logger logger = LoggerFactory.getLogger(SslConsumer.class);privateString keyStorePath;private String password = "123456";private boolean trustServer = true;//测试,默认为true

    private String serverCaPath = "";publicSslConsumer(String keyStorePath) {this.keyStorePath =keyStorePath;

    }

    @Overridepublic voidaccept(SslContextSpec t) {try{

    t.sslContext(createSslContext(loadKeyStore(keyStorePath))).handlerConfigurator(handler->{

    SSLEngine engine=handler.engine();

    List matchers = new LinkedList();

    SNIMatcher matcher= new SNIMatcher(0) {

    @Overridepublic booleanmatches(SNIServerName serverName) {//返回true,不验证主机名

    return true;

    }

    };

    matchers.add(matcher);

    SSLParameters params= newSSLParameters();

    params.setSNIMatchers(matchers);

    engine.setSSLParameters(params);

    });

    }catch(Exception e) {

    }

    }private SslContext createSslContext(KeyStore keyStore) throwsException {

    SslContextBuilder builder=SslContextBuilder.forClient();

    KeyManagerFactory keyMgrFactory= KeyManagerFactory.getInstance("SunX509");

    keyMgrFactory.init(keyStore, password.toCharArray());

    builder.keyManager(keyMgrFactory);

    SSLX509TrustMgr trustMgr= null;if(trustServer) {

    trustMgr= newSSLX509TrustMgr();

    }else{

    trustMgr= newSSLX509TrustMgr(getSeverPublicKey(serverCaPath));

    }

    builder.trustManager(trustMgr);

    List ciper = new ArrayList();

    ciper.add("TLS_RSA_WITH_AES_128_GCM_SHA256");

    ciper.add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");

    ciper.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");

    builder.ciphers(ciper);returnbuilder.build();

    }privateKeyStore loadKeyStore(String keyStorePath) {

    KeyStore keyStore= null;

    InputStream in= null;try{

    in= newFileInputStream(keyStorePath);

    keyStore=KeyStore.getInstance(KeyStore.getDefaultType());

    keyStore.load(in, password.toCharArray());

    }catch(Exception e) {

    logger.error("", e);

    }returnkeyStore;

    }privatePublicKey getSeverPublicKey(String serverCaPath) {

    PublicKey key= null;

    InputStream in= null;try{

    in= newFileInputStream(serverCaPath);

    X509Certificate cert= (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);

    key=cert.getPublicKey();

    }catch(Exception e) {

    logger.error("", e);

    }returnkey;

    }

    }

    验证服务器整数实现类

    目前没有做任何处理,有需要验证服务器证书时,可以在对应的重写方法中进行处理

    packagecom.demo.client;importjava.security.PublicKey;importjava.security.cert.CertificateException;importjava.security.cert.X509Certificate;importjavax.net.ssl.X509TrustManager;public class SSLX509TrustMgr implementsX509TrustManager {privatePublicKey serverPublicKey;publicSSLX509TrustMgr() {

    }publicSSLX509TrustMgr(PublicKey serverPublicKey) {this.serverPublicKey =serverPublicKey;

    }

    @Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType) throwsCertificateException {//TODO Auto-generated method stub

    }

    @Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType) throwsCertificateException {//TODO Auto-generated method stub

    }

    @OverridepublicX509Certificate[] getAcceptedIssuers() {//TODO Auto-generated method stub

    return null;

    }

    }

    WebClient工具类

    packagecom.demo.utils;importjava.net.ConnectException;importjava.net.NoRouteToHostException;importjava.time.Duration;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.http.MediaType;importorg.springframework.http.client.reactive.ReactorClientHttpConnector;importorg.springframework.web.reactive.function.client.WebClient;importcom.demo.entity.SslConsumer;importio.netty.channel.ChannelOption;importio.netty.channel.ConnectTimeoutException;importio.netty.handler.timeout.ReadTimeoutHandler;importio.netty.handler.timeout.WriteTimeoutHandler;importreactor.core.publisher.Mono;importreactor.netty.http.client.HttpClient;importreactor.netty.resources.ConnectionProvider;importreactor.netty.resources.LoopResources;importreactor.netty.tcp.TcpClient;importreactor.retry.Backoff;importreactor.retry.Retry;public classWebClientUtil {private static Logger logger = LoggerFactory.getLogger(WebClientUtil.class);private static Map webClientMap = new ConcurrentHashMap();public staticWebClient createWebClient(String keyStorePath) {

    ConnectionProvider provider= ConnectionProvider.builder("wc-").maxConnections(30)

    .maxIdleTime(Duration.ofSeconds(30)).maxLifeTime(Duration.ofSeconds(30))

    .pendingAcquireTimeout(Duration.ofSeconds(30)).build();

    LoopResources loop= LoopResources.create("loop-", 20, 20, true);

    TcpClient tcpClient= TcpClient.create(provider).secure(newSslConsumer(keyStorePath)).runOn(loop)

    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,2000).doOnConnected(conn ->conn

    .addHandlerLast(new ReadTimeoutHandler(30)).addHandlerLast(new WriteTimeoutHandler(20)));return WebClient.builder().clientConnector(newReactorClientHttpConnector(HttpClient.from(tcpClient))).build();//如下方式可能会一直循环生成loop线程,直到程序僵死//HttpClient httpClient = HttpClient.create(provider).secure(new SslConsumer(keyStorePath))//.tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)//.doOnConnected(con -> con.addHandlerLast(new ReadTimeoutHandler(20))//.addHandlerLast(new WriteTimeoutHandler(10)))//.runOn(LoopResources.create("loop-", 20, 20, true)));//

    //return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();

    }public static MonodoPost(String url, String data, String keyStorePath) {

    WebClient webClient=webClientMap.get(url);if (webClient == null) {

    webClient=createWebClient(keyStorePath);

    WebClient putIfAbsent=webClientMap.putIfAbsent(url, webClient);if (putIfAbsent != null) {

    webClient=putIfAbsent;

    }

    }

    Retry> retry = Retry.anyOf(ConnectTimeoutException.class, NoRouteToHostException.class).retryMax(3)

    .backoff(Backoff.fixed(Duration.ofMillis(100)));

    Mono mono =webClient.post().uri(url).contentType(MediaType.APPLICATION_JSON).bodyValue(data).retrieve()

    .bodyToMono(String.class).timeout(Duration.ofSeconds(30)).doOnError(ConnectException.class, e ->{

    logger.error("", e);

    }).doOnError(NoRouteToHostException.class, e ->{

    logger.error("", e);

    }).retryWhen(retry);returnmono;

    }

    }

    1、demo使用的springboot版本为2.3.3

    2、HttpClient的create方法与secure方法不能分开,否则secure方法可能不生效

    3、 ConnectionProvider中有很多方法已经过时,比如fixed,elastic,不建议使用,在过时的方法上,源码中也给出了对应替换使用的方法,比如fixed可以使用create方法替代,elastic方法可以用builder方法替代,但create方法直接创建的就为ConnectionProvider,重载的方法可以设置maxConnections,但不能设置连接最大空闲时间,连接最大生命周期等,builder方法可以对线程更精细的管理,故本例使用的builder方法和build来创建ConnectionProvider,如果使用默认的provider,线程数默认500,且maxIdleTime和maxLifeTime为-1

    a462c9847918d49dd619099592db5fee.png

    4、LoopResources为处理响应消息的线程数,2.3.3版本最小值为4

    8103bee2dbc7335605ec36484f3b4fe0.png

    73583c115d6cf14ec9d80a9ba4321f1c.png

    5、ReactorClientHttpConnector存在一个重构方法

    733ca33691cc6267dcb16013d9468247.png

    在该方法中,factory可以设置ConnectionProvider和LoopResources,mapper可以设置HttpClient,但在使用时可能是使用方式不正确,一直都是要么线程池配置上了,但SSL不可用,或者是SSL可用,但线程池不可用,后在ReactorClientHttpConnector两个参数的构造方法中看到initHttpClient方法,该方法中,使用了runOn配置LoopResources,故本例中也是用了runOn来配置。

    测试:

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    @Overridepublic void run(String... args) throwsException {int count = 6;for (int i = 0; i < count; i++) {

    logger.info("start: {}", i);

    JsonObject obj= newJsonObject();

    obj.addProperty("name", "name"+i);

    obj.addProperty("age", i);

    Mono mono =WebClientUtil.doPost(url, obj.toString(), keyStorePath);

    mono.subscribe(newMainConsumer(i));

    logger.info("end: {}", i);

    }

    }class MainConsumer implements Consumer{private inti;public MainConsumer(inti) {this.i =i;

    }

    @Overridepublic voidaccept(String t) {

    logger.info("receive: {}, loop: {}", t, i);

    }

    }

    View Code

    结果:

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    [INFO] main 14:44:30 WebclientdemoApplication:61(logStarted) Started WebclientdemoApplication in 3.453 seconds (JVM running for 4.137)

    [INFO] main 14:44:30 MainBusiStart:29(run) start: 0

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 0

    [INFO] main 14:44:31 MainBusiStart:29(run) start: 1

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 1

    [INFO] main 14:44:31 MainBusiStart:29(run) start: 2

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 2

    [INFO] main 14:44:31 MainBusiStart:29(run) start: 3

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 3

    [INFO] main 14:44:31 MainBusiStart:29(run) start: 4

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 4

    [INFO] main 14:44:31 MainBusiStart:29(run) start: 5

    [INFO] main 14:44:31 MainBusiStart:35(run) end: 5

    [INFO] loop--nio-4 14:44:31 MainBusiStart:48(accept) receive: {"name":"name3","age":3.0}, loop: 3

    [INFO] loop--nio-5 14:44:31 MainBusiStart:48(accept) receive: {"name":"name4","age":4.0}, loop: 4

    [INFO] loop--nio-1 14:44:31 MainBusiStart:48(accept) receive: {"name":"name0","age":0.0}, loop: 0

    [INFO] loop--nio-2 14:44:31 MainBusiStart:48(accept) receive: {"name":"name1","age":1.0}, loop: 1

    [INFO] loop--nio-3 14:44:31 MainBusiStart:48(accept) receive: {"name":"name2","age":2.0}, loop: 2

    [INFO] loop--nio-6 14:44:31 MainBusiStart:48(accept) receive: {"name":"name5","age":5.0}, loop: 5

    View Code

    6、本例中,使用了一个reactor-extra进行重试处理

    io.projectreactor.addons

    reactor-extra

    但这种重试的方法虽然比较简单好用,但已经被标记为过时

    0ea02ae8ea88a5e234e4501f7dd3ee9b.png

    应该使用传递一个reactor.util.retry.Retry类型参数的方法

    7、如果使用springclloud,则需要考虑springboot和springcloud的兼容问题,以及当前程序的springboot和springcloud版本与其他需要远程调用的版本的兼容

    展开全文
  • WebClient 使用学习记录

    2021-06-02 15:37:15
    文章目录前言一、相关技术背景介绍二、IO相关知识点三、 WebClient使用3.1 引入依赖3.2 创建实例3.3 Get, Mono是WebClint返回的结果类型3.4 POST 普通形式3.5 POST 业务需求,参数通过读取文件获取,文件内容是...

    前言

    ​ 最近接手了一个新的需求,需要使用到网络请求相关,经过思考决定使用比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使用介绍

    展开全文
  • Spring的WebClient使用

    2020-12-29 19:51:42
    WebClient使用 引言 Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。...

    WebClient使用

    引言

    Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。本文主要介绍WebClient的使用,包括通过WebClient请求接口及实现接口的文件上传下载。

    1.引入依赖

    在pom.xml中引入WebClient所需的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectreactor</groupId>
        <artifactId>reactor-spring</artifactId>
        <version>1.0.1.RELEASE</version>
    </dependency>
    

    2.一个简单的请求示例

    请求百度首页示例

    // 1.创建WebClient实例
    WebClient webClient = WebClient
                    .builder()
                    .baseUrl("https://www.baidu.com")
                    .build();
    
    // 2.通过通过retrieve()请求并获得响应
    Mono<String> bodyToMono = webClient
                    .get()
                    .retrieve()
                    // 将请求结果处理为String类型
                    .bodyToMono(String.class);
    
    log.info("请求返回的数据内容:{}", bodyToMono.block());
    

    3.WebClient配置

    在创建WebClient时可对一些变量进行默认设置

    WebClient client3 = WebClient
      .builder()
        .baseUrl("http://localhost:8080")
        .defaultCookie("cookieKey", "cookieValue")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
        .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
      .build();
    

    也可在发起请求时填入cookie、header等参数

    webClient
      .get()
      .uri("https://www.baidu.com")
      .cookie("cookieKey", "cookieValue")
      .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .retrieve();
    

    4.在请求中传参

    在日常使用中传递参数的方法

    WebClient webClient = WebClient.create();
    Mono<String> bodyToMono = webClient.post()
      .uri(uriBuilder ->
           uriBuilder
           .scheme("https")
           .host("localhost:8080")
           .path("/api/test")
           .queryParam("id", "123")
           .queryParam("invalid", "invalid123")
           .build()
          )
      // requestbody,也可以使用MultiValueMap<String, String> map = new LinkedMultiValueMap<>(),传入参数,发起form提交。或直接使用HashMap传参
      .syncBody(new Student())
      .retrieve()
      .bodyToMono(String.class);
    

    5.设置连接超时时间

    设置请求超时时间

    TcpClient tcpClient = TcpClient
                  .create()
                  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                  .doOnConnected(connection -> {
    	connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
    	connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS));
                    });
    
    WebClient client = WebClient.builder()
                    .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
                    .build();
    

    6.文件上传

    上传文件代码

    HttpHeaders headers = new HttpHeaders();
    headers.add("content-type", "application/x-www-form-urlencoded");
    HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource("a.txt"), headers);
    MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
    parts.add("file", entity);
    Mono<String> bodyToMono = WebClient.create().post()
            .uri("http://localhost:8080/upload")
            .contentType(MediaType.MULTIPART_FORM_DATA)
            .body(BodyInserters.fromMultipartData(parts))
            .retrieve().bodyToMono(String.class);
    

    7.文件下载

    通过将.retrieve()方法换成.exchange(),可以获取到响应的头信息、Cookie等信息,与restTemplate相似。

    Mono<ClientResponse> responseMono = WebClient.create().get()
                    .uri("http://localhost:8080/file/download")
                    .accept(MediaType.APPLICATION_OCTET_STREAM)
                    .exchange(); 
    ClientResponse response = responseMono.block();
    // 可从headers中获取filename等信息 
    ClientResponse.Headers headers = response.headers();
    Resource resource = response.bodyToMono(Resource.class).block();
    // 获取文件流
    InputStream inputStream = resource.getInputStream();
    

    8.WebTestClient

    The WebTestClient is the main entry point for testing WebFlux server endpoints. It has a very similar API to the WebClient, and it delegates most of the work to an internal WebClient instance focusing mainly on providing a test context. The DefaultWebTestClient class is a
    single interface implementation.

    主要意思是WebTestClient用于测试WebFlux服务器端点,他和WebClient相似,且将大部分工作委托给了内部的WebClient,主要用于测试上下文,此部分后期在单独整理。

    展开全文
  • webclient使用介绍

    万次阅读 2019-12-14 23:49:16
    webclient 和webflux(参考(webflux)) 这一系列,我们可以开好多节课程来讲解 什么是webclient,在spring5中,出现了reactive 响应式编程思想(参考响应式编程文章),并且为网络编程提供相关响应式编程的支持,...
  • 在 Spring 5 之前,如果我们想要调用其他系统提供的 HTTP 服务,通常可以使用 ...​ 作为替代,Spring 官方已在 Spring 5 中引入了 WebClient 作为非阻塞式 Reactive HTTP 客户端。下面通过样例演示如何使用 WebClie
  • 主要介绍了C#中在WebClient使用post发送数据实现方法,需要的朋友可以参考下
  • 四、POST 请求1,发送一个 JSON 格式数据(使用 json 字符串)(1)下面代码使用 post方式发送一个 json格式的字符串,并将结果打印出来(以字符串的形式)。@RestControllerpublic class HelloController {// 创建 ...
  • Spring WebFlux (5): WebClient使用

    千次阅读 2020-08-13 13:20:45
    可以使用WebFlux的WebClient类很方便的对网络请求进行代理处理操作,我想Spring Cloud Gateway中主要用的就是WebClient进行操作,这里简单介绍一下WebClient的用法,想要深度学习的话,看一下Spring Cloud Gateway...
  • spring 5 webclient使用指南

    千次阅读 2019-06-13 10:39:33
    WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor... 下面是链接是讲解webclient使用指南,推荐一下,感兴趣的可以学习一下。...
  • java相关:spring5 webclient使用指南详解发布于 2020-3-23|复制链接本篇文章主要介绍了spring 5 webclient使用指南详解,小妖觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小妖过来看看吧之前写了一篇...
  • rt-thread中webclient使用

    2021-12-17 09:11:25
    rt_thread中webclient 使用记录
  • SpringBoot - 网络请求客户端WebClient使用详解(异常处理) 请求异常处理 1,默认异常 (1)当我们使用 WebClient 发送请求时, 如果接口返回的不是 200 状态(而是 4xx、5xx 这样的异常状态),则会抛出 ...
  • Webflux函数式编程模型以及WebClient使用 (1)在使用函数式编程模型操作的时候,需要自己初始化服务器 (2)基于函数式编程模型,有两个核心接口:RouterFunction(实现路由功能,请求转发给对应handler)和Handler...
  • WEBCLIENT使用方法

    2013-11-06 17:04:11
    WEBCLIENT使用方法
  • spring 5 webclient使用指南 请求携带header 携带cookie @Test public void testWithCookie(){ Mono&lt;String&gt; resp = WebClient.create() .method(HttpMethod.GET) .uri("...
  • Spring5的WebClient使用详解

    千次阅读 2020-04-09 16:34:46
    1 前言 以前文章介绍了: RestTemplate:https://blog.csdn.net/zzhongcy/article/details/104674808 AsyncRestTemplate:... 这里介绍一下另外一个新兴的http客户端:WebClient 2 ...
  • springboot webflux webclient 使用说明

    千次阅读 2020-06-23 10:26:26
    springbootwebfluxwebclient使用说明
  • 文章目录前言在SpringBoot中使用1、...本文主要说一下 WebClient的基本使用。 主要原因及场景如下: 在一个类似监听机器的项目中,需要发送警告到某群聊。 这种警告可能会堆积,需要异步发送出去。 官方文档如下: ...
  • spring 5 webclient使用指南-转

    千次阅读 2019-06-21 20:11:56
    spring 5 webclient使用指南 序 之前写了一篇restTemplate使用实例,由于spring 5全面引入reactive,同时也有了restTemplate的reactive版webclient,本文就来对应展示下webclient的基本使用。 请求携带header ...
  • springbootwebfluxwebclient使用示例
  • Symbian S60中使用http协议访问服务器资源的例子
  • WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,...
  • WebClient使用示例

    千次阅读 2018-11-02 17:08:12
     从Web站点检索数据,使用OpenRead()方法返回一个Stream引用。ReadLine()方法从数据流中以文本的形式获取数据。  下例从百度网页上读取数据,然后逐行显示在屏幕上。 static void Main(string[] args) { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,331
精华内容 12,532
关键字:

webclient使用