Spring Boot项目封装OkHttp,并实现重试调用(超时、返回HTTP错误、返回业务异常)

需求分析

近期在做的项目,依赖了挺多外部服务的接口。请求这些接口,由于一些原因,多方考虑,最后决定使用OkHttp去处理这些接口请求。考虑到这块项目后续的长期迭代,对于OkHttp进行了一些初步的封装。

目标确定

1,只封装POST和GET方式的请求;
2,请求和读取的超时时间可以自定义配置;
3,外部服务的接口稳定性比较一般,所以请求读取超时、请求返回Http错误、或者返回业务异常,要自动重试,并且可以自定义最大重试次数以及重试间隔;

代码实现

项目JDK版本1.8
Spring Boot版本2.6.14
项目使用Maven构建,先引入OkHttp的maven依赖

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.10.0</version>
</dependency>

先创建一个类OkHttpConfiguration用来构建OkHttpClient,超时的默认参数,重试的默认参数也维护在这个类里。提供按照默认参数创建OkHttpClient的方法,同时也支持自定义超时和重试请求接口的参数,也可以选择是否需要重试调用。具体代码如下:

public class OkHttpConfiguration {

    /**
     * 默认超时时间
     */
    static final long DEFAULT_TIME_OUT = 3;

    /**
     * 默认超时时间单位
     */
    static final TimeUnit DEFAULT_TIME_OUT_UNIT = TimeUnit.SECONDS;

    /**
     * 默认最大重试次数
     */
    static final int DEFAULT_MAX_RETRIES = 2;

    /**
     * 默认重试间隔毫秒数
     */
    static final int DEFAULT_RETRY_DELAY_MILLIS = 0;

    /**
     * 创建OkHttpClient
     * <p>
     * 超时时间:默认配置
     * 重试:否
     *
     * @param
     * @return okhttp3.OkHttpClient
     */
    public static OkHttpClient createClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT, DEFAULT_TIME_OUT_UNIT)
                .readTimeout(DEFAULT_TIME_OUT, DEFAULT_TIME_OUT_UNIT)
                .build();
    }

    /**
     * 创建OkHttpClient
     * <p>
     * 超时时间:根据入参自定义
     * 重试:否
     *
     * @param timeOut     超时时间
     * @param timeOutUnit 超时时间单位
     * @return okhttp3.OkHttpClient
     */
    public static OkHttpClient createClient(final Long timeOut, final TimeUnit timeOutUnit) {
        return new OkHttpClient.Builder()
                .connectTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .readTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .build();
    }

    /**
     * 创建OkHttpClient
     * <p>
     * 超时时间:默认配置
     * 重试:是
     * 重试配置:默认超时配置
     *
     * @param
     * @return okhttp3.OkHttpClient
     */
    public static OkHttpClient createClientWithRetry() {
        return new OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT, DEFAULT_TIME_OUT_UNIT)
                .readTimeout(DEFAULT_TIME_OUT, DEFAULT_TIME_OUT_UNIT)
                .addInterceptor(new OkHttpRetryInterceptor(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY_MILLIS))
                .build();
    }

    /**
     * 创建OkHttpClient
     * <p>
     * 超时时间:根据入参自定义
     * 重试:是
     * 重试配置:默认超时配置
     *
     * @param timeOut     超时时间
     * @param timeOutUnit 超时时间单位
     * @return okhttp3.OkHttpClient
     */
    public static OkHttpClient createClientWithRetry(final Long timeOut, final TimeUnit timeOutUnit) {
        return new OkHttpClient.Builder()
                .connectTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .readTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .addInterceptor(new OkHttpRetryInterceptor(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY_MILLIS))
                .build();
    }

    /**
     * 创建OkHttpClient
     * <p>
     * 超时时间:根据入参自定义
     * 重试:是
     * 重试配置:根据入参自定义
     *
     * @param timeOut          超时时间
     * @param timeOutUnit      超时时间单位
     * @param maxRetries       最大重试次数
     * @param retryDelayMillis 重试间隔毫秒数
     * @return okhttp3.OkHttpClient
     */
    public static OkHttpClient createClientWithRetry(final Long timeOut, final TimeUnit timeOutUnit,
                                                     final Integer maxRetries, final Integer retryDelayMillis) {
        return new OkHttpClient.Builder()
                .connectTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .readTimeout(null == timeOut ? DEFAULT_TIME_OUT : timeOut,
                        null == timeOutUnit ? DEFAULT_TIME_OUT_UNIT : timeOutUnit)
                .addInterceptor(new OkHttpRetryInterceptor(null == maxRetries ? DEFAULT_MAX_RETRIES : maxRetries,
                        null == retryDelayMillis ? DEFAULT_RETRY_DELAY_MILLIS : retryDelayMillis))
                .build();
    }

}

OkHttpUtil这个类用来处理HTTP请求,提供GET请求和POST请求的调用。具体代码如下:

@Slf4j
public class OkHttpUtil {

    /**
     * 按照默认策略GET请求
     * <p>
     * 默认策略 >>> 超时时间:3
     * 默认策略 >>> 超时时间单位:TimeUnit.SECONDS
     * 默认策略 >>> 重试最大次数:2
     * 默认策略 >>> 重试间隔毫秒数:0
     *
     * @param url     请求地址
     * @param headMap 请求头
     * @param retry   是否重试
     * @return java.lang.String
     */
    public static String get(final String url, final Map<String, String> headMap, final boolean retry) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient();
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry();
        }

        // 构建请求
        Request request = buildGetRequest(url, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 按照默认策略POST请求
     * <p>
     * 默认策略 >>> 超时时间:3
     * 默认策略 >>> 超时时间单位:TimeUnit.SECONDS
     * 默认策略 >>> 重试最大次数:2
     * 默认策略 >>> 重试间隔毫秒数:0
     *
     * @param url     请求地址
     * @param obj     请求body
     * @param headMap 请求header
     * @param retry   是否重试
     * @return java.lang.String
     */
    public static String postJson(final String url, final Object obj, final Map<String, String> headMap,
                                  final boolean retry) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient();
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry();
        }

        // 构建请求
        Request request = buildPostJsonRequest(url, obj, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 按照默认策略POST请求
     * <p>
     * 默认策略 >>> 超时时间:3
     * 默认策略 >>> 超时时间单位:TimeUnit.SECONDS
     * 默认策略 >>> 重试最大次数:2
     * 默认策略 >>> 重试间隔毫秒数:0
     *
     * @param url     请求地址
     * @param params  请求参数
     * @param headMap 请求header
     * @param retry   是否重试
     * @return java.lang.String
     */
    public static String postForm(final String url, final Map<String, Object> params,
                                  final Map<String, String> headMap, final boolean retry) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient();
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry();
        }

        // 构建请求
        Request request = buildPostFormRequest(url, params, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 自定义策略GET请求
     *
     * @param url              请求地址
     * @param headMap          请求头
     * @param timeOut          超时时间
     * @param timeOutUnit      超时时间单位
     * @param retry            是否重试
     * @param maxRetries       重试最大次数
     * @param retryDelayMillis 重试间隔毫秒数
     * @return java.lang.String
     */
    public static String get(final String url, final Map<String, String> headMap, final Long timeOut, final TimeUnit timeOutUnit,
                             final boolean retry, final Integer maxRetries, final Integer retryDelayMillis) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient(timeOut, timeOutUnit);
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry(timeOut, timeOutUnit, maxRetries, retryDelayMillis);
        }

        // 构建请求
        Request request = buildGetRequest(url, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 自定义策略POST请求
     *
     * @param url              请求地址
     * @param obj              请求body
     * @param headMap          请求header
     * @param timeOut          超时时间
     * @param timeOutUnit      超时时间单位
     * @param retry            是否重试
     * @param maxRetries       重试最大次数
     * @param retryDelayMillis 重试间隔毫秒数
     * @return java.lang.String
     */
    public static String postJson(final String url, final Object obj, final Map<String, String> headMap,
                                  final Long timeOut, final TimeUnit timeOutUnit, final boolean retry,
                                  final Integer maxRetries, final Integer retryDelayMillis) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient(timeOut, timeOutUnit);
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry(timeOut, timeOutUnit, maxRetries, retryDelayMillis);
        }

        // 构建请求
        Request request = buildPostJsonRequest(url, obj, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 自定义策略POST请求
     *
     * @param url              请求地址
     * @param params           请求参数
     * @param headMap          请求header
     * @param timeOut          超时时间
     * @param timeOutUnit      超时时间单位
     * @param retry            是否重试
     * @param maxRetries       重试最大次数
     * @param retryDelayMillis 重试间隔毫秒数
     * @return java.lang.String
     */
    public static String postForm(final String url, final Map<String, Object> params, final Map<String, String> headMap,
                                  final Long timeOut, final TimeUnit timeOutUnit, final boolean retry, final Integer maxRetries,
                                  final Integer retryDelayMillis) throws IOException {
        // OkHttp配置项初始化
        OkHttpClient okHttpClient = OkHttpConfiguration.createClient(timeOut, timeOutUnit);
        if (retry) {
            okHttpClient = OkHttpConfiguration.createClientWithRetry(timeOut, timeOutUnit, maxRetries, retryDelayMillis);
        }

        // 构建请求
        Request request = buildPostFormRequest(url, params, headMap);

        // 提交请求,返回结果
        return execute(request, okHttpClient);
    }

    /**
     * 提交请求,返回结果
     *
     * @param request
     * @param okHttpClient
     * @return java.lang.String
     */
    private static String execute(final Request request, final OkHttpClient okHttpClient) throws IOException {
        Instant start = Instant.now();
        Response response = okHttpClient.newCall(request).execute();
        Instant end = Instant.now();
        log.info(">>> OkHttp调用时间统计 >>> 接口:{},执行时间:{}ms", request.url(), end.toEpochMilli() - start.toEpochMilli());
        return response.body().string();
    }

    /**
     * Request Get组装
     *
     * @param url     请求地址
     * @param headMap 请求头
     * @return okhttp3.Request
     */
    private static Request buildGetRequest(final String url, final Map<String, String> headMap) {
        Request request = new Request.Builder().url(url).build();
        if (null != headMap) {
            request = new Request.Builder().url(url).headers(buildHeaders(headMap)).build();
        }
        return request;
    }

    /**
     * Request PostJson组装
     *
     * @param url     请求地址
     * @param obj     请求body
     * @param headMap 请求header
     * @return okhttp3.Request
     */
    private static Request buildPostJsonRequest(final String url, final Object obj, final Map<String, String> headMap) {
        return new Request.Builder()
                .url(url)
                .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSONObject.toJSONString(obj)))
                .addHeader("Content-Type", "application/json")
                .headers(buildHeaders(headMap))
                .build();
    }

    /**
     * Request PostForm组装
     *
     * @param url     请求地址
     * @param params  请求参数
     * @param headMap 请求header
     * @return okhttp3.Request
     */
    private static Request buildPostFormRequest(final String url, final Map<String, Object> params, final Map<String, String> headMap) {
        FormBody.Builder formBuilder = new FormBody.Builder();
        Iterator<String> it = params.keySet().iterator();
        while (it.hasNext()) {
            String key = it.next();
            formBuilder.add(key, (String) params.get(key));
        }
        FormBody body = formBuilder.build();
        return new Request.Builder().url(url).headers(buildHeaders(headMap)).post(body).build();
    }

    /**
     * Header组装
     *
     * @param headersParams
     * @return okhttp3.Headers
     */
    private static Headers buildHeaders(final Map<String, String> headersParams) {
        Headers headers;
        okhttp3.Headers.Builder headersBuilder = new okhttp3.Headers.Builder();
        if (MapUtils.isNotEmpty(headersParams)) {
            Iterator<String> iterator = headersParams.keySet().iterator();
            String key;
            while (iterator.hasNext()) {
                key = iterator.next();
                headersBuilder.add(key, headersParams.get(key));
            }
        }
        headers = headersBuilder.build();
        return headers;
    }

}

OkHttpRetryInterceptor类自动移拦截器,用来处理重试调用的逻辑。
这里有个地方需要注意一下。在处理业务系统返回异常重试的时候,需要通过response.body().string()方法去获取具体的返回结果。但是response.body().string()方法会关闭数据流,导致在OkHttp类里面再调用response.body().string()方法时提示资源已关闭。所以需要先通过response.peekBody(Long.MAX_VALUE)方法,生成一个ResponseBody的快照,通过这个快照获取具体的返回结果。
实现的具体代码如下:

@Slf4j
public class OkHttpRetryInterceptor implements Interceptor {

    /**
     * 请求返回需要重试的Code列表
     */
    static final int[] RETRY_CODE_ARRAY = {400, 403, 404, 408, 500, 502};

    /**
     * 最大重试次数
     */
    private final int maxRetries;

    /**
     * 重试间隔毫秒数
     */
    private final int retryDelayMillis;

    public OkHttpRetryInterceptor(final int maxRetries, final int retryDelayMillis) {
        this.maxRetries = maxRetries;
        this.retryDelayMillis = retryDelayMillis;
    }

    @Override
    public Response intercept(Chain chain) {
        return retry(chain, 0);
    }

    /**
     * 重试
     * <p>
     * 递归处理
     *
     * @param chain
     * @param retryCent
     * @return okhttp3.Response
     */
    private Response retry(Chain chain, int retryCent) {
        Request request = chain.request();
        Response response = null;
        try {
            log.info(">>> OkHttp第{}次调用接口{},详细请求:{}", (retryCent + 1), request.url(), request.toString());
            response = chain.proceed(request);

            // 请求HTTP失败
            if (Arrays.asList(RETRY_CODE_ARRAY).contains(response.code()) && maxRetries > retryCent) {
                TimeUnit.MILLISECONDS.sleep(retryDelayMillis);
                return retry(chain, retryCent + 1);
            }

            // 因为response.body().string()会关闭资源,所以生成一个快照获取ResponseBody
            ResponseBody responseBodyClone = response.peekBody(Long.MAX_VALUE);
            String responseString = responseBodyClone.string();
            responseBodyClone.close();
            JSONObject jsonObject = JSONObject.parseObject(responseString);

            // 请求业务系统返回异常
            if (!jsonObject.getBoolean(Errors.SUCCESS.getMessage()) && maxRetries > retryCent) {
                TimeUnit.MILLISECONDS.sleep(retryDelayMillis);
                return retry(chain, retryCent + 1);
            }
            return response;
        } catch (Exception e) {
            if (maxRetries > retryCent) {
                try {
                    TimeUnit.MILLISECONDS.sleep(retryDelayMillis);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                return retry(chain, retryCent + 1);
            } else {
                return response;
            }
        }
    }
}
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Spring Boot中获取HTTP地址的文件并返回文件流,你可以使用`RestTemplate`来发送HTTP请求获取文件内容,并将内容返回为文件流。以下是一个示例代码: ```java import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.io.InputStream; @RestController public class FileController { @GetMapping("/file/{url}") public ResponseEntity<InputStreamResource> getFile(@PathVariable String url) throws IOException { // 创建RestTemplate对象 RestTemplate restTemplate = new RestTemplate(); // 发送HTTP请求获取文件内容 ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.GET, null, byte[].class); byte[] fileBytes = response.getBody(); // 获取文件输入流 InputStream inputStream = response.getBody(); // 设置下载的文件名 String filename = getFilenameFromUrl(url); // 返回文件流 return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") .body(new InputStreamResource(inputStream)); } private String getFilenameFromUrl(String url) { // 从URL中提取文件名 // 你可以根据实际情况来提取文件名,这里只是一个示例 int lastSlashIndex = url.lastIndexOf("/"); if (lastSlashIndex != -1 && lastSlashIndex < url.length() - 1) { return url.substring(lastSlashIndex + 1); } return "file"; } } ``` 在上述代码中,当访问`/file/{url}`时,该方法会使用`RestTemplate`发送GET请求来获取指定URL的文件内容。然后,将文件内容返回为文件流,并设置下载的文件名。 请注意,示例代码中使用了`RestTemplate`来发送HTTP请求,你可以使用其他HTTP客户端库,如`HttpClient`或`OkHttp`,根据你的项目需求进行选择。 希望这可以帮助到你!如果你有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值