对于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所属的地区。

    只要方法封装的好,调用都不是问题!

    就是这么简单,你学废了吗?感觉有用的话,给笔者点个赞吧 !