背景:
由于公司有支付相关业务线,并且有场景是定时批量进行扣款需求,项目急需高效的http请求工具,最开始作者使用的是httpClient封装的http请求工具,在单个请求时没有出问题,但是量大时请求第三方时,会出现请求丢失的情况和请求第三方接口超时的问题,作者也是找了很久才发现是这个工具的问题,后面陆续有换了hutool工具包HttpUtil使用,也不能解决此问题。后面多方查阅资料和实验发现一个神仙工具retrofit,为此专门为它写一篇文章来做记录
前言
Retrofit是Square公司开源的一款类型安全的HTTP客户端库,它通过简洁的接口定义将HTTP API转换为Java接口,极大地简化了网络请求的编写。本文将介绍如何使用Retrofit来构建一个高效、易用的HTTP请求工具,并通过一个完整的示例来展示其实现过程。http相关注解可参考官方文档: retrofit官方文档地址 。
Retrofit
是适用于Android
和Java
且类型安全的HTTP客户端,其最大的特性的是支持通过接口
的方式发起HTTP请求 。而spring-boot
是使用最广泛的Java开发框架,但是Retrofit
官方没有支持与spring-boot
框架快速整合,因此某位大神开发了retrofit-spring-boot-starter
。使用retrofit-spring-boot-starter
实现了Retrofit
与spring-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,提升开发效率。
个人经验,不喜勿喷,有更好的观点或者方案大家可以在评论区留言,作者会关注修改!