对于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所属的地区。
只要方法封装的好,调用都不是问题!
就是这么简单,你学废了吗?感觉有用的话,给笔者点个赞吧 !