基于OkHttpClient的工具类

写在前面:封装主要是针对响应的结果,以及增加支持失败重试的功能。
废话不多说,代码如下:
先上使用:

/* ------ get请求 ------- */
Map<String, Object> param = new HashMap<>();
//1.最简单的使用
String result = OkHttp3Simple.DEFAULT.getSilently("https://www.baidu.com");
//2.有入参
String result = OkHttp3Simple.DEFAULT.getSilently("https://www.baidu.com", param);
//3.自动类型转换
ABC result = OkHttp3Simple.DEFAULT.getSilently("https://www.baidu.com", param, ABC.class);
//4. 需要自定义异常处理时
InputStream is = OkHttp3Simple.DEFAULT.inputStream(RequestType. GET, "https://www.baidu.com", param, exception -> { /*异常处理*/ });
//5. 自动类型转换 + 异常处理
Test result = OkHttp3Simple.DEFAULT.get("https://www.baidu.com", param,  Test.class, exception -> { /*异常处理*/});
//6.更多方法重载,看后面源码

/* ------ post请求 ------- */
Map<String, Object> param = new HashMap<>();
//1.带参数请求
String result = OkHttp3Simple.DEFAULT.postSilently("https://www.baidu.com", param);
//2. 类型转换 + 异常处理 
Test result = OkHttp3Simple.DEFAULT.post("https://www.baidu.com", param,  Test.class, exception -> { /*异常处理*/});
//3.更多方法重载,见源码

/* ------- 扩展用法 ------ */
OkHttp3Simple okHttp3Simple = OkHttp3Simple.newBuilder()
                // 设置一些OkHttp的初始参数
                .init(builder -> {
                    // 设置读超时
                    builder.readTimeout(60, TimeUnit.MILLISECONDS);
                    //设置写超时参数,等等
                })
                 // 设置重试参数
                .retryable(3, 2000L, OkHttp3Simple.RetryWhen.Timeout)
                 //类型转换器,默认使用GSon的类型转换,可以自定义替换掉
                .convert((responseBody, o) -> {
                    return "";
                })
                .build();

/* ------- 重试机制 -------*/
//重试的话,需要先构造一个支持重试的okHttp3Simple对象
OkHttp3Simple okHttp3Simple = OkHttp3Simple.newBuilder()
                .init(builder -> {
                    // 对httpClient初始,根据需要调用                    
                })
                .retryable(3, 2000L, OkHttp3Simple.RetryWhen.Timeout)
                .build();
//此时的对象支持,当请求超时的时候,重试三次,每次间隔2s
okHttp3Simple.get("https://www.baidu.com");
//如果想要根据返回值来决定是否重试,需要自己在返回值处理的时候,抛出异常来触发重试机制(同时上方的RetryWhen应该选在Exception类型)。比如
okHttp3Simple.get("https://www.baidu.com", param, response -> {
            JSONObject ss = JSONObject.parseObject(response.body().string());
            if(条件){
                // 返回值判断,并抛出异常。
                throw new RuntimeException();
            }            
            return 返回值;
        }, null);

/* ---------- 如果 https 开头的地址报错,可以使用SSL内置的对象 ----------- */
OkHttp3Simple.SSL.get("https://www.baidu.com");

/* ---------- 如果枚举的重试类型,可以只用修改RetryWhen枚举类即可 -------*/

实现如下:

package com.iqiyi.kepler.utils;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @Description: 对 OkHttpClient 的简单封装,主要是返回值的处理,以及增加重试机制
 * @Author: Li Yalei - Robin
 * @Date: 2021/1/27 19:10
 */
@Slf4j
public class OkHttp3Simple implements Serializable {

    public final static OkHttp3Simple DEFAULT = new OkHttp3Simple(new OkHttpClient());

    public final static OkHttp3Simple SSL = OkHttp3Simple.newBuilder().init(builder -> {
        SSLSocketClient client = new SSLSocketClient();
        builder.sslSocketFactory(client.getSSLSocketFactory(), client.getX509TrustManager());
        builder.hostnameVerifier(client.getHostnameVerifier());
    }).build();

    private OkHttpClient httpClient;

    /**
     * 默认的类型转换器, 默认情况下,使用 gson 的转换器
     */
    private BiFunction<ResponseBody, Class<?>, Object> defaultTypeConvert =
            (responseBody, clazz) -> {
                try {
                    return new Gson().fromJson(responseBody.string(), clazz);
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            };

    private OkHttp3Simple(OkHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public void setDefaultTypeConvert(BiFunction<ResponseBody, Class<?>, Object> defaultTypeConvert) {
        this.defaultTypeConvert = defaultTypeConvert;
    }

    /**
     * get 请求;
     * 注意:Silently意为:忽略异常,成功了就返回值,失败时或者异常时返回 null
     *
     * @param url 地址
     * @return
     */
    public String getSilently(String url) {
        return getSilently(url, String.class);
    }

    /**
     * get 请求;
     * 注意:Silently意为:忽略异常,失败时或者异常时返回 null
     *
     * @param url   地址
     * @param param 请求参数
     * @return
     */
    public String getSilently(String url, Map<String, Object> param) {
        return getSilently(url, param, String.class);
    }

    /**
     * get 请求;
     * 注意:Silently意为:忽略异常,失败时或者异常时返回 null
     *
     * @param url   地址
     * @param clazz 返回类型
     * @return
     */
    public <T> T getSilently(String url, Class<T> clazz) {
        return getSilently(url, null, clazz);
    }

    /**
     * get 请求;
     * 注意:Silently意为:忽略异常,失败时或者异常时返回 null
     *
     * @param url   地址
     * @param param 请求参数
     * @param clazz 返回类型
     * @return
     */
    public <T> T getSilently(String url, Map<String, Object> param, Class<T> clazz) {
        return get(url, param, clazz, null);
    }

    /**
     * get or post 请求
     * 返回结果为输入流,注意关闭流
     *
     * @param requestType      post or get
     * @param url              地址
     * @param param            入参
     * @param exceptionHandler
     * @return
     */
    public InputStream inputStream(RequestType requestType, String url, Map<String, Object> param, Consumer<Exception> exceptionHandler) {
        switch (requestType) {
            case GET:
                return get(url, param, response -> (response == null || !response.isSuccessful()) ? null : response.body().byteStream(), exceptionHandler);
            case POST:
                return post(url, param, response -> (response == null || !response.isSuccessful()) ? null : response.body().byteStream(), exceptionHandler);
            default:
                throw new IllegalArgumentException("不支持的请求类型" + requestType);
        }
    }

    public InputStream inputStreamSilently(RequestType requestType, String url, Map<String, Object> param) {
        return inputStream(requestType, url, param, null);
    }

    public String get(String url) {
        return get(url, null, String.class, exception -> {
            throw new RuntimeException(exception);
        });
    }

    public Response getResponse(String url, Map<String, Object> param) {
        return get(url, param, Response.class);
    }

    public String get(String url, Consumer<Exception> exceptionHandler) {
        return get(url, null, String.class, exceptionHandler);
    }

    public String get(String url, Map<String, Object> param, Consumer<Exception> exceptionHandler) {
        return get(url, param, String.class, exceptionHandler);
    }

    /**
     * @param url              请求地址
     * @param param            入参
     * @param response         响应处理器,二元处理,输入response, 输出 T
     * @param exceptionHandler 异常时的处理
     * @param <T>
     * @return
     */
    public <T> T get(String url, Map<String, Object> param, Function<Response, T> response, Consumer<Exception> exceptionHandler) {
        return response.apply(get(url, param, Response.class, exceptionHandler));
    }

    public <T> T get(String url, Map<String, Object> param, Class<T> clazz) {
        return get(url, param, clazz, exception -> {
            throw new RuntimeException(exception);
        });
    }

    /**
     * 发送post请求,
     * 注意:Silently意为:忽略异常,失败时或者异常时返回 null
     *
     * @param url
     * @param param
     * @return
     */
    public String postSilently(String url, Map<String, Object> param) {
        return post(url, param, String.class, null);
    }

    public String post(String url, Map<String, Object> param) {
        return post(url, param, String.class, exception -> {
            throw new RuntimeException(exception);
        });
    }

    public String post(String url, Map<String, Object> param, Consumer<Exception> exceptionHandler) {
        return post(url, param, String.class, exceptionHandler);
    }

    /**
     * @param url              请求地址
     * @param param            入参
     * @param response         响应处理器,二元处理,输入response, 输出 T
     * @param exceptionHandler 异常处理
     * @param <T>
     * @return
     */
    public <T> T post(String url, Map<String, Object> param, Function<Response, T> response, Consumer<Exception> exceptionHandler) {
        return response.apply(post(url, param, Response.class, exceptionHandler));
    }

    private void doNothing(Object input) {
    }

    /**
     * get 请求
     *
     * @param url
     * @param param
     * @param clazz
     * @param exceptionHandler
     * @param <T>
     * @return
     */
    public <T> T get(String url, Map<String, Object> param, Class<T> clazz, Consumer<Exception> exceptionHandler) {

        if (param == null) {
            param = new HashMap<>();
        }

        List<String> params = new ArrayList<>();
        for (Map.Entry<String, Object> entry : param.entrySet()) {
            params.add(entry.getKey() + "=" + entry.getValue());
        }
        String paramStr = StringUtils.join(params, "&");
        url = url.trim();
        if (!url.endsWith("?") && url.lastIndexOf("?") == -1) {
            url += "?";
        }
        if (!param.isEmpty()) {
            url += url.endsWith("?") ? paramStr : "&" + paramStr;
        }

        Request request = new Request.Builder().url(url).get().build();
        return requestInternal(param, () -> request, clazz, exceptionHandler);
    }

    public <T> T post(String url, Map<String, Object> param, Class<T> clazz, Consumer<Exception> exceptionHandler) {
        return post(url, param, clazz, this::doNothing, exceptionHandler);
    }

    /**
     * post请求
     *
     * @param url              地址
     * @param param            参数
     * @param clazz            返回类型
     * @param requestPreSet    request参数预设
     * @param exceptionHandler 异常时的处理
     * @param <T>
     * @return
     */
    public <T> T post(String url, Map<String, Object> param, Class<T> clazz, Consumer<Request.Builder> requestPreSet, Consumer<Exception> exceptionHandler) {
        return requestInternal(param,
                () -> {
                    Request.Builder requestBuilder = new Request.Builder().url(url)
                            .post(RequestBody.create(MediaType.parse("application/json"), new Gson().toJson(param)));
                    requestPreSet.accept(requestBuilder);
                    return requestBuilder.build();
                }
                , clazz, exceptionHandler);
    }

    private <T> T requestInternal(Map<String, Object> param, Supplier<Request> requestSupplier, Class<T> clazz,
                                  Consumer<Exception> exceptionHandler) {
        if (param == null) {
            log.error("param cannot be null");
            return null;
        }
        Request request = requestSupplier.get();
        try {
            Response response = httpClient.newCall(request).execute();
            if (Response.class.isAssignableFrom(clazz)) {
                return (T) response;
            }
            try {
                ResponseBody body = response.body();
                if (String.class == clazz) {
                    return ((T) body.string());
                }
                return (T) defaultTypeConvert.apply(body, clazz);
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        } catch (Exception e) {
            log.error("http post request error", e);
            if (exceptionHandler != null) {
                exceptionHandler.accept(e);
            }
            return null;
        }
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static class Builder {

        private RetryInterceptor retryInterceptor;

        private BiFunction typeConvert;

        private OkHttpClient.Builder builder;

        Builder() {
            this.builder = new OkHttpClient().newBuilder();
        }

        public Builder retryable(int retryMaxTimes, long retryInterval, RetryWhen when) {
            Objects.requireNonNull(when, "retry when required");
            this.retryInterceptor = new RetryInterceptor(retryMaxTimes, retryInterval, when);
            this.builder.addInterceptor(this.retryInterceptor);
            return this;
        }

        public <T, R> Builder convert(BiFunction<ResponseBody, T, R> convert) {
            this.typeConvert = convert;
            return this;
        }

        public Builder init(Consumer<OkHttpClient.Builder> builderConsumer) {
            builderConsumer.accept(this.builder);
            return this;
        }

        public OkHttp3Simple build() {
            OkHttp3Simple okHttp3Simple = new OkHttp3Simple(builder.build());
            if (this.typeConvert != null) {
                okHttp3Simple.setDefaultTypeConvert(this.typeConvert);
            }
            return okHttp3Simple;
        }
    }

    public enum RequestType {
        GET, POST;
    }

    /**
     * 重试的条件
     */
    public enum RetryWhen {
        /* 超时重试 */
        Timeout,
        /* 只要发生异常就重试 */
        Exception;

        public boolean shouldRetry(Throwable throwable) {
            if (throwable == null) {
                return false;
            }
            if (throwable instanceof SocketTimeoutException ||
                    java.lang.Exception.class.isAssignableFrom(throwable.getClass())) {
                return true;
            }
            return false;
        }
    }

    /**
     * 仅当发生异常时,重试才会生效
     */
    static class RetryInterceptor implements Interceptor {

        //最大重试次数
        private int retryMaxTimes;
        //重试的间隔
        private long retryInterval;

        private RetryWhen retryWhen;

        /**
         * @param retryMaxTimes
         * @param retryInterval
         * @param when
         */
        public RetryInterceptor(int retryMaxTimes, long retryInterval, RetryWhen when) {
            this.retryMaxTimes = retryMaxTimes;
            this.retryInterval = retryInterval;
            this.retryWhen = when;
        }

        public long getRetryInterval() {
            return retryInterval;
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = null;

            int retryNum = 0;
            //失败时重试才会生效
            while ((response == null || !response.isSuccessful()) &&
                    retryNum < retryMaxTimes) {
                try {
                    response = chain.proceed(request);
                } catch (Throwable throwable) {
                    log.error("请求发生异常,param=" + request.toString(), throwable);
                    boolean retry = false;
                    if (retryWhen != null && retryWhen.shouldRetry(throwable)) {
                        retry = true;
                    }
                    if (!retry || retryNum == retryMaxTimes) {
                        //不需要重试, 或者重试达到最大次数
                        throw throwable;
                    }
                    log.warn("请求失败,开始重试{}, 最大重试次数{}", retryNum + 1, retryMaxTimes);
                    final long nextInterval = getRetryInterval();
                    try {
                        log.info("Wait for {}", nextInterval);
                        Thread.sleep(nextInterval);
                    } catch (final InterruptedException e) {
                        log.error("等待重试时,发生中断异常", e);
                        throw new RuntimeException(e);
                    }
                    retryNum++;
                }
            }
            return response;
        }

    }

    public static class SSLSocketClient {

        //获取这个SSLSocketFactory
        public SSLSocketFactory getSSLSocketFactory() {
            try {
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, getTrustManager(), new SecureRandom());
                return sslContext.getSocketFactory();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        //获取TrustManager
        private TrustManager[] getTrustManager() {
            return new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public void checkServerTrusted(X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public X509Certificate[] getAcceptedIssuers() {
                            return new X509Certificate[]{};
                        }
                    }
            };
        }

        //获取HostnameVerifier
        public HostnameVerifier getHostnameVerifier() {
            return (s, sslSession) -> true;
        }

        public X509TrustManager getX509TrustManager() {
            X509TrustManager trustManager = null;
            try {
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init((KeyStore) null);
                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
                }
                trustManager = (X509TrustManager) trustManagers[0];
            } catch (Exception e) {
                e.printStackTrace();
            }

            return trustManager;
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349