对于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 WebClientSpring-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,打印结果如下:

 

java spring 异步导出文件 spring webclient异步编程_java spring 异步导出文件

可以看出,同步和异步的差别。同步是按顺序执行,异步会单独创建一个线程去执行任务,不会阻塞主线程的执行。需要注意的是,无论是使用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,打印结果如下:

java spring 异步导出文件 spring webclient异步编程_请求参数_02

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

 

 

 

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

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