使用retrofit来封装高效的http请求工具,吊打okhttp、httpClient等工具

背景:

由于公司有支付相关业务线,并且有场景是定时批量进行扣款需求,项目急需高效的http请求工具,最开始作者使用的是httpClient封装的http请求工具,在单个请求时没有出问题,但是量大时请求第三方时,会出现请求丢失的情况和请求第三方接口超时的问题,作者也是找了很久才发现是这个工具的问题,后面陆续有换了hutool工具包HttpUtil使用,也不能解决此问题。后面多方查阅资料和实验发现一个神仙工具retrofit,为此专门为它写一篇文章来做记录

前言

Retrofit是Square公司开源的一款类型安全的HTTP客户端库,它通过简洁的接口定义将HTTP API转换为Java接口,极大地简化了网络请求的编写。本文将介绍如何使用Retrofit来构建一个高效、易用的HTTP请求工具,并通过一个完整的示例来展示其实现过程。http相关注解可参考官方文档: retrofit官方文档地址 。

Retrofit是适用于AndroidJava且类型安全的HTTP客户端,其最大的特性的是支持通过接口的方式发起HTTP请求 。而spring-boot是使用最广泛的Java开发框架,但是Retrofit官方没有支持与spring-boot框架快速整合,因此某位大神开发了retrofit-spring-boot-starter。使用retrofit-spring-boot-starter实现了Retrofitspring-boot框架快速整合

实践指南

1. 添加依赖

  使用maven可以参考下面的坐标,目前作者使用的是2.3.12版本

 <dependency>
    <groupId>com.github.lianjiatech</groupId>
    <artifactId>retrofit-spring-boot-starter</artifactId>
    <version>2.3.12</version>
</dependency>

使用gradle可以参考下面坐标

dependencies {
        implementation 'com.squareup.retrofit2:retrofit:2.9.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
        implementation 'com.squareup.okhttp3:okhttp:4.9.1'
}

2.添加application.yml的配置

retrofit:
  # 全局转换器工厂
  global-converter-factories:
    - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
    - retrofit2.converter.jackson.JacksonConverterFactory
  # 全局调用适配器工厂(组件扩展的调用适配器工厂已经内置,这里请勿重复配置)
  global-call-adapter-factories:

  # 全局日志打印配置
  global-log:
    # 启用日志打印
    enable: true
    # 全局日志打印级别
    log-level: info
    # 全局日志打印策略
    log-strategy: basic

  # 全局重试配置
  global-retry:
    # 是否启用全局重试
    enable: false
    # 全局重试间隔时间
    interval-ms: 100
    # 全局最大重试次数
    max-retries: 3
    # 全局重试规则
    retry-rules:
      - response_status_not_2xx
      - occur_io_exception

  # 全局超时时间配置
  global-timeout:
    # 全局读取超时时间
    read-timeout-ms: 10000
    # 全局写入超时时间
    write-timeout-ms: 10000
    # 全局连接超时时间
    connect-timeout-ms: 10000
    # 全局完整调用超时时间
    call-timeout-ms: 0

  # 熔断降级配置
  degrade:
    # 熔断降级类型。默认none,表示不启用熔断降级
    degrade-type: none
    # 全局sentinel降级配置
    global-sentinel-degrade:
      # 是否开启
      enable: false
      # 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N)
      count: 1000
      # 熔断时长,单位为 s
      time-window: 5
      # 降级策略(0:平均响应时间;1:异常比例;2:异常数量)
      grade: 0

    # 全局resilience4j降级配置
    global-resilience4j-degrade:
      # 是否开启
      enable: false
      # 根据该名称从#{@link CircuitBreakerConfigRegistry}获取CircuitBreakerConfig,作为全局熔断配置
      circuit-breaker-config-name: defaultCircuitBreakerConfig
  # 自动设置PathMathInterceptor的scope为prototype
  auto-set-prototype-scope-for-path-math-interceptor: true

3.创建Retrofit实例接口

作者一次性封装的请求比较多,并且默认失败重试3请求,几乎涵盖所有场景的需求,各位需要可以自取,不用都复制下来,挑适合自己,http的相关配置可以参考Retrofit官方文档地址 ,如下代码

import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient;
import com.github.lianjiatech.retrofit.spring.boot.interceptor.Intercept;
import com.github.lianjiatech.retrofit.spring.boot.retry.Retry;
import com.squareup.okhttp.RequestBody;
import org.springblade.pay.client.retrofit.fallback.HttpApiClientFallBackFactory;
import org.springblade.pay.client.retrofit.intercept.TimeoutInterceptor;
import retrofit2.http.*;
import java.util.Map;


@Intercept(handler = TimeoutInterceptor.class)
@RetrofitClient(baseUrl = "https://www.baidu.com/",fallbackFactory = HttpApiClientFallBackFactory.class)
public interface HttpApiClient {


	/**
	 * 发送get请求
	 *
	 * @param url     网址
	 * @return
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@GET
	public  String getToUrl(@Url String url);

	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	public  String postToUrl(@Url String url);

	/**
	 * 发送get请求
	 * 超时时间单位 毫秒
	 * @param url     网址
	 * @param  connectTimeout 连接超时
	 * @param  readTimeout 读取超时
	 * @param  writeTimeout 写超时
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@GET
	public  String getToUrl(@Url String url,
							@Header("CONNECT_TIMEOUT") String connectTimeout,
							@Header("READ_TIMEOUT") String readTimeout,
							@Header("WRITE_TIMEOUT") String writeTimeout) ;

	/**
	 * 发送get请求
	 *
	 * @param url     网址
	 * @param options  post表单数据
	 * @return
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@GET
	public  String getToMap(@Url String url , @QueryMap Map<String, Object> options);


	/**
	 * 发送get请求
	 * 超时时间单位 毫秒
	 * @param url     网址
	 * @param options  post表单数据
	 * @param  connectTimeout 连接超时
	 * @param  readTimeout 读取超时
	 * @param  writeTimeout 写超时
	 * @return
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@GET
	public  String getToMap(@Url String url , @QueryMap Map<String, Object> options,
							@Header("CONNECT_TIMEOUT") String connectTimeout,
							@Header("READ_TIMEOUT") String readTimeout,
							@Header("WRITE_TIMEOUT") String writeTimeout) ;
	/**
	 * 发送post请求
	 *
	 * @param url 网址
	 * @param options  post表单数据
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	@Headers({"CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000"})
	public String post(@Url String url,  @QueryMap Map<String, Object> options) ;

	/**
	 * 发送post请求
	 * 超时时间单位 毫秒
	 * @param url 网址
	 * @param options  post表单数据
	 * @param  connectTimeout 连接超时
	 * @param  readTimeout 读取超时
	 * @param  writeTimeout 写超时
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	public String post(@Url String url,  @QueryMap Map<String, Object> options,
					   @Header("CONNECT_TIMEOUT") String connectTimeout,
					   @Header("READ_TIMEOUT") String readTimeout,
					   @Header("WRITE_TIMEOUT") String writeTimeout) ;

	/**
	 * 发送post请求
	 *
	 * @param url 网址
	 * @param requestBody  请求体body参数
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	public String post(@Url String url,  @Body Object requestBody) ;


	/**
	 * 发送post请求
	 * 超时时间单位 毫秒
	 * @param url 网址
	 * @param requestBody  请求体body参数
	 * @param  connectTimeout 连接超时
	 * @param  readTimeout 读取超时
	 * @param  writeTimeout 写超时
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	public String post(@Url String url,  @Body Object requestBody,
					   @Header("CONNECT_TIMEOUT") String connectTimeout,
					   @Header("READ_TIMEOUT") String readTimeout,
					   @Header("WRITE_TIMEOUT") String writeTimeout) ;


	/**
	 * 发送post请求
	 *
	 * @param url 网址
	 * @param file 请求文件
	 * @param  description 文件描述
	 * @return 返回数据
	 */
	@Multipart
	@PUT
	public String post(@Url String url, @Part("file") RequestBody file, @Part("description") RequestBody description) ;

	/**
	 * 发送post请求
	 * 超时时间单位 毫秒
	 *
	 * @param url 网址
	 * @param file 请求文件
	 * @param  description 文件描述
	 * @param  connectTimeout 连接超时
	 * @param  readTimeout 读取超时
	 * @param  writeTimeout 写超时
	 * @return 返回数据
	 */
	public String post(@Url String url, @Part("file") RequestBody file,
					   @Part("description") RequestBody description,
					   @Header("CONNECT_TIMEOUT") String connectTimeout,
					   @Header("READ_TIMEOUT") String readTimeout,
					   @Header("WRITE_TIMEOUT") String writeTimeout) ;


	/**
	 * 发送post请求
	 *
	 * @param url 网址
	 * @param requestBody  请求体body参数
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	@Headers({"Content-Type:application/soap+xml;charset=UTF-8","CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000"})
	public String postToSoapXml(@Url String url, @Body String requestBody) ;


	/**
	 * 发送post请求
	 *
	 * @param url 网址
	 * @param requestBody  请求体body参数
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	@Headers({"Content-Type:application/xml;Content-Encoding=UTF-8;charset=UTF-8","CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000"})
	public String postToXml(@Url String url, @Body String requestBody) ;


	/**
	 * 发送post请求
	 * Header格式自定义
	 * @param url 网址
	 * @param requestBody  请求体body参数
	 * @return 返回数据
	 */
	@Retry(maxRetries=3,intervalMs = 30000)
	@POST
	public String postToCustomHeaders(@Url String url,
									  @Header("Content-Type") String contentType,
									  @Header("Content-Encoding") String contentEncoding,
									  @Header("CONNECT_TIMEOUT") String connectTimeout,
									  @Header("READ_TIMEOUT") String readTimeout,
									  @Header("WRITE_TIMEOUT") String writeTimeout,
									  @Body String requestBody) ;
}

4.超时拦截器类TimeoutInterceptor

针对请求工具可以统一设置请求接口的超时时间等相关信息,作者创建了超时拦截器类(TimeoutInterceptor),代码如下

import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor;
import okhttp3.Request;
import okhttp3.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.TextUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;


/**
 * @Auther: Patrick
 * @Date: 2023-10-08 14:50
 * @Description:
 */
@Component
@Slf4j
public class TimeoutInterceptor extends BasePathMatchInterceptor {
	public static final String CONNECT_TIMEOUT = "CONNECT_TIMEOUT";
	public static final String READ_TIMEOUT = "READ_TIMEOUT";
	public static final String WRITE_TIMEOUT = "WRITE_TIMEOUT";
	@Override
	protected Response doIntercept(Chain chain) throws IOException {
		Request request = chain.request();

		int connectTimeout = chain.connectTimeoutMillis();
		int readTimeout = chain.readTimeoutMillis();
		int writeTimeout = chain.writeTimeoutMillis();

		String connectNew = request.header(CONNECT_TIMEOUT);
		String readNew = request.header(READ_TIMEOUT);
		String writeNew = request.header(WRITE_TIMEOUT);

		if (!TextUtils.isEmpty(connectNew)) {
			connectTimeout = Integer.valueOf(connectNew);
		}
		if (!TextUtils.isEmpty(readNew)) {
			readTimeout = Integer.valueOf(readNew);
		}
		if (!TextUtils.isEmpty(writeNew)) {
			writeTimeout = Integer.valueOf(writeNew);
		}
		return chain
			.withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
			.withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
			.withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
			.proceed(request);
	}
}

5.熔断降级HttpApiClientFallBackFactory类

作者粗略的编写了工具请求失败后,请求失败后熔断降级,大家需要可以自己自定义降级实现

HttpApiClientFallBackFactory类的代码如下:

import com.github.lianjiatech.retrofit.spring.boot.degrade.FallbackFactory;
import com.squareup.okhttp.RequestBody;
import lombok.extern.slf4j.Slf4j;
import org.springblade.pay.client.retrofit.HttpApiClient;
import org.springframework.stereotype.Service;
import java.util.Map;





/**
 * @Auther: Patrick
 * @Date: 2023-08-25 14:02
 * @Description:
 */
@Slf4j
@Service
public class HttpApiClientFallBackFactory implements FallbackFactory<HttpApiClient> {

	@Override
	public HttpApiClient create(Throwable cause) {
		log.error("触发熔断,异常信息: {}", cause);
		return new HttpApiClient(){

			@Override
			public String getToUrl(String url) {
				return null;
			}

			@Override
			public String postToUrl(String url) {
				return null;
			}

			@Override
			public String getToUrl(String url, String connectTimeout, String readTimeout, String writeTimeout) {
				return null;
			}

			@Override
			public String getToMap(String url, Map<String, Object> options) {
				return null;
			}

			@Override
			public String getToMap(String url, Map<String, Object> options, String connectTimeout, String readTimeout, String writeTimeout) {
				return null;
			}

			@Override
			public String post(String url, Map<String, Object> options) {
				return null;
			}

			@Override
			public String post(String url, Map<String, Object> options, String connectTimeout, String readTimeout, String writeTimeout) {
				return null;
			}

			@Override
			public String post(String url, Object requestBody) {
				return null;
			}

			@Override
			public String post(String url, Object requestBody, String connectTimeout, String readTimeout, String writeTimeout) {
				return null;
			}

			@Override
			public String post(String url, RequestBody file, RequestBody description) {
				return null;
			}

			@Override
			public String post(String url, RequestBody file, RequestBody description, String connectTimeout, String readTimeout, String writeTimeout) {
				return null;
			}

			@Override
			public String postToSoapXml(String url, String requestBody) {
				return null;
			}

			@Override
			public String postToXml(String url, String requestBody) {
				return null;
			}


			@Override
			public String postToCustomHeaders(String url, String contentType, String contentEncoding, String connectTimeout, String readTimeout, String writeTimeout, String requestBody) {
				return null;
			}

		};
	}
}

5.工具实际在业务中使用示例

作者使用的依赖注入的方式使用,如下代码,其他方式各位可以灵活发挥,选择适合自己的方式即可

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServerTest {

    @Resource
	private  HttpApiClient httpApiClient;

    @Test
    public void send() {
        Map<String,String> paramMap= new TreeMap<String,String>();
			paramMap.put("send_time", new Date());
			//报文流水号
			paramMap.put("order_id", CommonUtil.orderNo(2));
			paramMap.put("version", 3);
			paramMap.put("id", 1111111);
       	String postString = httpApiClient.post("http://www.csdn.net/", paramMap);
        System.out.println(postString);
    }
}
  

结尾

通过Retrofit,我们可以以非常优雅的方式实现HTTP请求,它简化了网络编程的复杂性,提高了代码的可读性和可维护性。无论是简单的GET请求还是复杂的POST请求,甚至是上传下载文件,Retrofit都能轻松应对,是现代Android和Java后端开发中不可或缺的工具之一。希望本文能帮助你快速上手Retrofit,提升开发效率。

个人经验,不喜勿喷,有更好的观点或者方案大家可以在评论区留言,作者会关注修改!

相关推荐

  1. HarmonyOS 网络请求工具封装,直接无脑用!!!

    2024-04-30 16:24:01       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-30 16:24:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-30 16:24:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-30 16:24:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-30 16:24:01       20 阅读

热门阅读

  1. python 调用 llama

    2024-04-30 16:24:01       11 阅读
  2. 在C++中,单冒号(:)的作用

    2024-04-30 16:24:01       9 阅读
  3. 【打工日常】Docker部署一款私有云存储网盘项目

    2024-04-30 16:24:01       12 阅读
  4. 数字证书简记

    2024-04-30 16:24:01       8 阅读