HTTP客户端之Spring WebClient
本文共 11,386 字,预计阅读时间 38 分钟
对于HTTP客户端,其实有很多种,而SpringBoot也提供了一种方式叫Spring WebClient
。它是在Spring 5中引入的异步、反应式HTTP客户端,用于取代较旧的RestTemplate
,以便在使用Spring Boot
框架构建的应用程序中进行REST API调用,它支持同步、异步和流式处理。
1.导入依赖
这里使用SpringBoot项目进行演示
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
Spring WebClient
在Spring-boot-starter-webFlux
包中,Spring WebFlux是Spring5的一部分,用于为Web应用程序中的反应式编程提供支持。
2.封装工具类
分别封装了同步和异步的请求
package com.zxh.test.util; import com.fasterxml.jackson.databind.JsonNode; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLException; import java.util.Map; import java.util.function.Consumer; public class WebHttpApi { /** * 同步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @return 默认返回jsonNode类型的数据 */ public static JsonNode get(String requestPath, MultiValueMap<String, String> requestParams) { return syncGet(requestPath, requestParams, null, JsonNode.class); } /** * 同步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @return 默认返回jsonNode类型的数据 */ public static JsonNode get(String requestPath, MultiValueMap<String, String> requestParams, Map<String, String> headers) { return syncGet(requestPath, requestParams, headers, JsonNode.class); } /** * 同步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param clazz 返回体类型 * @return 自定义返回体的类型 */ public static <T> T get(String requestPath, MultiValueMap<String, String> requestParams, Map<String, String> headers, Class<T> clazz) { return syncGet(requestPath, requestParams, headers, clazz); } /** * 异步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param callBack 方法回调 * @return */ public static void get(String requestPath, MultiValueMap<String, String> requestParams, Consumer<JsonNode> callBack) { Mono<JsonNode> monoResponse = sendGet(requestPath, requestParams, null, JsonNode.class); monoResponse.subscribe(callBack); } /** * 异步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param callBack 方法回调 * @return */ public static void get(String requestPath, MultiValueMap<String, String> requestParams, Map<String, String> headers, Consumer<JsonNode> callBack) { Mono<JsonNode> monoResponse = sendGet(requestPath, requestParams, headers, JsonNode.class); monoResponse.subscribe(callBack); } /** * 同步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @return 默认返回jsonNode类型的数据 */ public static JsonNode post(String requestPath, LinkedMultiValueMap<String, Object> requestParams) { return syncPost(requestPath, requestParams, null, JsonNode.class); } /** * 同步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @return 默认返回jsonNode类型的数据 */ public static JsonNode post(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Map<String, String> headers) { return syncPost(requestPath, requestParams, headers, JsonNode.class); } /** * 同步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param clazz 返回体类型 * @return 自定义返回体的类型 */ public static <T> T post(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Map<String, String> headers, Class<T> clazz) { return syncPost(requestPath, requestParams, headers, clazz); } /** * 异步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param callBack 方法回调 * @return */ public static void post(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Consumer<JsonNode> callBack) { Mono<JsonNode> monoResponse = sendPost(requestPath, requestParams, null, JsonNode.class); monoResponse.subscribe(callBack); } /** * 异步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param callBack 方法回调 * @return */ public static void post(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Map<String, String> headers, Consumer<JsonNode> callBack) { Mono<JsonNode> monoResponse = sendPost(requestPath, requestParams, headers, JsonNode.class); monoResponse.subscribe(callBack); } /** * 同步get请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param clazz 返回来类型 * @param <T> * @return */ private static <T> T syncGet(String requestPath, MultiValueMap<String, String> requestParams, Map<String, String> headers, Class<T> clazz) { Mono<T> monoResponse = sendGet(requestPath, requestParams, headers, clazz); //如果需要则可设置超时时间,单位是秒 //return monoResponse.block(Duration.ofSeconds(timeout)); return monoResponse.block(); } /** * 同步post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param clazz 返回来类型 * @param <T> * @return */ private static <T> T syncPost(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Map<String, String> headers, Class<T> clazz) { Mono<T> monoResponse = sendPost(requestPath, requestParams, headers, clazz); //如果需要则可设置超时时间,单位是秒 //return monoResponse.block(Duration.ofSeconds(timeout)); return monoResponse.block(); } /** * 发送get请求 * * @param requestPath * @param requestParams * @param headers * @param clazz 返回体类型 * @return */ private static <T> Mono<T> sendGet(String requestPath, MultiValueMap<String, String> requestParams, Map<String, String> headers, Class<T> clazz) { String url = composeGetRequestPath(requestPath, requestParams); WebClient.RequestHeadersSpec<?> requestBodySpec = createIgnoreSslWebClient().get().uri(url); if (headers != null) { headers.forEach(requestBodySpec::header); } return requestBodySpec.retrieve().bodyToMono(clazz); } /** * 发送post请求 * * @param requestPath 请求路径 * @param requestParams 请求参数 * @param headers 请求头 * @param clazz 返回体类型 * @return */ private static <T> Mono<T> sendPost(String requestPath, LinkedMultiValueMap<String, Object> requestParams, Map<String, String> headers, Class<T> clazz) { WebClient.RequestBodySpec requestBodySpec = createIgnoreSslWebClient().post().uri(requestPath); if (headers != null) { headers.forEach(requestBodySpec::header); } return requestBodySpec.body(BodyInserters.fromMultipartData(requestParams)).retrieve().bodyToMono(clazz); } /** * 根据请求参数封装请求url * * @param requestPath * @param requestParams * @return */ private static String composeGetRequestPath(String requestPath, MultiValueMap<String, String> requestParams) { return requestParams == null ? requestPath : UriComponentsBuilder.fromHttpUrl(requestPath).queryParams(requestParams).toUriString(); } /** * 创建web客户端,免除ssl协议验证 * * @return */ public static WebClient createIgnoreSslWebClient() { try { SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext)); return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build(); } catch (SSLException sslException) { throw new RuntimeException(sslException); } } }
其实上述方法中关键的方法就是sendGet()
和sendPost()
方法,而方法内部又调用了createIgnoreSslWebClient()
来创建客户端。因此主要的步骤如下:
- 创建客户端并免除ssl验证;
- 调用client的get()或post()方法,并调用uri()方法设置请求API地址和请求头等信息;
- 调用链中的retrieve()方法用于进行API调用,依次来发送请求;
- 通过bodyToMono()方法获取响应体,该响应体通过bodyToMono()方法转换为Mono对象(同步请求到这里已结束);
- 使用subscribe()方法以非阻塞方式订阅bodyToMono()方法转换返回的Mono对象(异步请求特有)。
另外,在get请求时,其将url和请求的参数map进行了格式化,设置为符合要求的url去请求,而无需手动拼接参数。
3.新建controller类进行测试
3.1测试get方法
private String baseUrl = "https://autumnfish.cn/api"; private String url1 = baseUrl + "/joke/list"; private String url2 = baseUrl + "/user/reg"; @GetMapping("/test") public void test() { MultiValueMap<String, String> listParams = new LinkedMultiValueMap<>(); listParams.add("num", "3"); //同步请求 System.out.println("------开始同步请求--------"); JsonNode jsonNode = WebHttpApi.get(url1, listParams); System.out.println("响应的结果"); System.out.println(jsonNode); //将结果转为字符串,格式化 System.out.println(jsonNode.toPrettyString()); //获取结果中的data数据 System.out.println(jsonNode.get("data")); System.out.println("------完成同步请求--------"); //异步请求 System.out.println("\n*******开始异步请求*******"); WebHttpApi.get(url1, listParams, data -> { //对返回的结果进行处理,异步的。这里模拟打印 System.out.println(data); }); System.out.println("*******完成异步请求*******"); }
浏览器访问http://localhost:8080/api/test/test,打印结果如下:
可以看出,同步和异步的差别。同步是按顺序执行,异步会单独创建一个线程去执行任务,不会阻塞主线程的执行。需要注意的是,无论是使用main方法或测试类的方式,都无法测试异步的执行,故这里才用controller接口的方式来测试。
3.2测试post方法
//注册用户 @Test public void test2() { LinkedMultiValueMap<String, Object> regParams = new LinkedMultiValueMap<>(); regParams.add("username", "张无忌123"); //同步请求 JsonNode jsonNode2 = WebHttpApi.post(url2, regParams); System.out.println(jsonNode2.toPrettyString()); //异步请求 regParams.add("username", "张无忌456"); WebHttpApi.post(url2, regParams, data -> { //对返回的结果进行处理,异步的。这里模拟打印 System.out.println(data); }); }
浏览器访问http://localhost:8080/api/test/test2,打印结果如下:
3.3采用自定义返回体类型
示例:根据ip获取所属地区
public static void main(String[] args) { MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("ip", "183.95.251.19"); params.add("json", "true"); String resp = WebHttpApi.get("http://whois.pconline.com.cn/ipJson.jsp", params, null, String.class); JSONObject data = JSON.parseObject(resp); System.out.println(String.format("%s%s", data.getString("pro"), data.getString("city")));//湖北省武汉市 }
由于上述接口返回的数据是String类型,故指定了返回体的类型,从而对结果进行处理,获取IP所属的地区。
只要方法封装的好,调用都不是问题!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2022-01-03 SpringBoot下载Excel文件无法打开