1.情景展示
Spring3.0引入了RestTemplate,但是在后来的官方源码中介绍,RestTemplate有可能在未来的版本中被弃用,所谓替代RestTemplate,在Spring5中引入了WebClient作为非阻塞式Reactive Http客户端。
WebClient处理单个HTTP请求的响应时长并不比RestTemplate更快,但是它处理并发的能力更强。
所以响应式非阻塞IO模型的核心意义在于:提高了单位时间内有限资源下的服务请求的并发处理能力,而不是缩短了单个服务请求的响应时长。
如何使用spring组件WebClient发送Http请求?
2.准备工作
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.6.6</version>
<optional>true</optional>
</dependency>
3.解决方案
3.1 GET请求封装
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.18</version>
<optional>true</optional>
</dependency>
import code.marydon.encapsulation.dataType.MapUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 利用spring组件WebClient发送http请求(GET、POST)
* @description: 目前只实现了同步请求,由于没有异步请求实现场景,所以并未封装,网上一搜就有。
* @author: Marydon
* @date: 2023-05-30 16:28
* @version: 1.0
* @email: [email protected]
*/
@Slf4j
public class WebClientUtils {
/**
* WebClient发送GET请求
* @description 同步请求
* @param url 请求地址
* @param paramsMap 请求参数
* @param responseClass 响应数据的数据类型
* @return 响应数据
*/
public static <T> T sendGet(String url, Map<String, String> paramsMap, Class<T> responseClass) {
List<NameValuePair> nameValuePairs = new ArrayList<>(paramsMap.size());
if (!MapUtils.isEmpty(paramsMap)) {
// 存放参数名与参数值
// 遍历取出key和value并将其作为参数的name和value
paramsMap.forEach((k, v) -> nameValuePairs.add(new BasicNameValuePair(k, v)));
}
URI uri;
try {
// 形如:
// http://www.cnblogs.com?Blog=%E5%8D%9A%E5%AE%A2%E5%9B%AD&Name=Marydon
uri = new URIBuilder(url)
// 设置URL的字符集
.setCharset(StandardCharsets.UTF_8)
// 参数会被拼接到URL后面,中文会被自动编码,特殊字符也会被编码
// url?param1=value1¶m2=value2
.addParameters(nameValuePairs)
.build();
} catch (URISyntaxException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
Mono<T> mono = WebClient
// 创建WenClient实例
.create()
// 请求方式
.method(HttpMethod.GET)
// 请求url
.uri(uri)
// 获取响应结果
.retrieve()
// 将结果转换为指定类型
.bodyToMono(responseClass);
// block方法返回最终调用结果,block方法是阻塞的(同步请求,不调用block方法,可以改成异步请求)
T responseData = mono.block();
log.info("请求地址:{}\n响应数据:{}", uri, responseData);
return responseData;
}
}
测试
public static void main(String[] args) {
Map<String, Object> paramsMap = new HashMap<>(2);
paramsMap.put("Name", "Marydon");
paramsMap.put("Blog", "博客园");
sendGet("https://www.cnblogs.com", paramsMap, String.class);
}
结果
从上图我们可以看到:发送的是GET请求,并且是问号传参。
3.2 POST请求封装(FORM表单)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.18</version>
<optional>true</optional>
</dependency>
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* 发送POST请求(form表单)
* @description 同步请求
* @param url
* @param map
* @param responseDataClass
* @return 响应数据
*/
public static <T> T sendPostByForm(String url, Map<String, Object> map, Class<T> responseDataClass) {
// 请求参数必须放到MultiValueMap当中
MultiValueMap<String, Object> paramsMap = new LinkedMultiValueMap<>(map.size());
// 把Map中的数据复制到MultiValueMap当中
map.forEach(paramsMap::add);
// 当传递的请求体对象是一个MultiValueMap对象时,WebClient默认发起的是Form提交,即:application/x-www-form-urlencoded
Mono<T> mono = WebClient
// 创建WenClient实例
.create()
// 请求方式
.method(HttpMethod.POST)
// 请求url
.uri(url)
// 请求参数
.bodyValue(paramsMap)
// 获取响应结果
.retrieve()
// 将结果转换为指定类型
.bodyToMono(responseDataClass);
// block方法返回最终调用结果,block方法是阻塞的
T responseData = mono.block();
log.info("请求地址:{}\n请求参数:{}\n响应数据:{}", url, paramsMap, responseData);
return responseData;
}
测试代码
Map<String, Object> paramsMap = new HashMap<>(2);
paramsMap.put("Name", "Marydon");
paramsMap.put("Blog", "博客园");
sendPostByForm("https://www.cnblogs.com", paramsMap, String.class);
下面这句话证明,我们发送的确确实实是FORM表单请求
测试结果
3.3 POST请求封装(JSON请求)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.18</version>
<optional>true</optional>
</dependency>
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
JSON的本质,还是字符串。
所以,我们在发送请求时,并不是要传递JSON对象,而是传json字符串就可以啦。
/**
* 发送post请求(JSON数据)
* @description 同步请求
* @param url
* @param params 请求入参
* Map或JSON
* @param responseDataClass
* @return 响应数据
*/
public static <T> T sendPostByJson(String url, Object params, Class<T> responseDataClass) {
String paramsJsonStr;
if (params instanceof JSONObject) {
paramsJsonStr = params.toString();
} else {
// Map转JSON
paramsJsonStr = JSON.toJSONString(params);
}
Mono<T> mono = WebClient
// 创建WenClient实例
.builder()
// 设置请求内容类型为:Content-Type=application/json
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build()
// 请求方式
.method(HttpMethod.POST)
// 请求url
.uri(url)
// 请求参数
.bodyValue(paramsJsonStr)
// 获取响应结果
.retrieve()
// 将结果转换为指定类型
.bodyToMono(responseDataClass);
// block方法返回最终调用结果,block方法是阻塞的(同步请求)
T responseData = mono.block();
log.info("请求地址:{}\n请求参数:{}\n响应数据:{}", url, paramsJsonStr, responseData);
return responseData;
}
调用
sendPostByJson("https://www.cnblogs.com", paramsMap, String.class);
从下面这句输出,可以看出:发送的是JSON字符串
结果
写在最后
哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!