一、 概述
Retrofit2:Http接口调用框架(对httpclient的封装基于OkHttp3),调用http接口就像调用本地接口一样简单。
官网教程:https://square.github.io/retrofit/
2.0 版本改进参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0915/3460.html
对比过RestTemplate,再看Retrofit2的使用,后者有多简便易用,看后一目了然。
直抒主题:
二、Maven
Retrofit与spring-boot框架直接整合
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.9.0</version>
</dependency>
三、配置
配置文件如下(详细参考注释):
# ===== retrofit ===== #
retrofit:
# 连接池配置
pool:
# testpool连接池配置
testpool:
# 最大空闲连接数
max-idle-connections: 3
# 连接保活时间(秒)
keep-alive-second: 100
# 是否禁用void返回值类型
disable-void-return-type: false
# 全局转换器工厂
global-converter-factories:
- retrofit2.converter.jackson.JacksonConverterFactory
# 全局调用适配器工厂
global-call-adapter-factories:
- com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
- com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory
# 日志打印配置
log:
# 启用日志打印
enable: true
# 日志打印拦截器
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
# 全局日志打印级别
global-log-level: info
# 全局日志打印策略
global-log-strategy: body
# 重试配置
retry:
# 是否启用全局重试
enable-global-retry: true
# 全局重试间隔时间
global-interval-ms: 100
# 全局最大重试次数
global-max-retries: 1
# 全局重试规则
global-retry-rules:
- response_status_not_2xx
- occur_io_exception
# 重试拦截器
retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
# 熔断降级配置
degrade:
# 是否启用熔断降级
enable: false
# 熔断降级实现方式
degrade-type: sentinel
# 熔断资源名称解析器
resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
四、实例代码
编写请求接口:
@RetrofitClient(baseUrl = "${baseUrl}", poolName = "testpool", errorDecoder = DingErrorDecoder.class)
public interface DHttpApi {
/**
* @param appkey
* @param appsecret
* @return
*/
@GET("gettoken")
Result gettoken(@Query("appkey") String appkey, @Query("appsecret") String appsecret);
}
#配置文件url路径
baseUrl:
https://api.xxx.com
调用方式,与普通接口调用相同:
@Autowired
private DHttpApi dHttpApi;
public Result gettoken() {
return dHttpApi.gettoken(appkey, appsecret);
}
以上配置结束,已经可以使用了,是不是超简单,配置无侵入、全注释即可处理。
五、进阶(Token处理)
1. 自动在每次请求加入公共参数(Token)
为了避免每次请求都写Token,统一在此处处理,
接口加入拦截器配置:
@Intercept(handler = DTokenInterceptor.class)
public interface DingHttpApi {
拦截器获取token,代码如下:
@Component
public class DTokenInterceptor extends BasePathMatchInterceptor {
@Autowired
private Environment env;
@Override
public Response doIntercept(Chain chain) throws IOException {
String token = DTokenKeeper.getAccessToken();
// 初始获得token
if (null == token || token.equals("")) {
try {
Response resp = getToken(env.getProperty("DING.baseUrl") + "/gettoken?appkey="
+ env.getProperty("DING.appkey") + "&appsecret=" + env.getProperty("DING.appsecret"));
JSONObject jObj = JSON.parseObject(resp.body().string());
token = jObj.getString(DingConstant.TOKEN);
DingTokenKeeper.setAccessToken(token);
} catch (Exception e) {
throw new AppException("获取token失败!");
}
}
Request request = chain.request();
Request newRequest;
HttpUrl url = request.url();
// 参数加入token
HttpUrl newUrl = url.newBuilder()
.addQueryParameter(DingConstant.TOKEN, token)
.build();
newRequest = request.newBuilder()
.url(newUrl)
.build();
return chain.proceed(newRequest);
}
}
坑点:
获取token也需要进行http请求,此处不能直接使用retrofit2
因为retrofit2框架注册晚于拦截器,所以此处请求要借助原生okhttp3来完成....
/**
* 原生http请求
*/
private Response getToken(String url) throws Exception {
//1 构造Request
OkHttpClient okHttpClient = new OkHttpClient();
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(url).build();
//2 将Request封装为Call
Call call = okHttpClient.newCall(request);
//3 执行Call,得到response
return call.execute();
}
2. Token过期刷新
处理方式为记录token生成时间,请求时与有效期对比,过期则重新获取,此方式较简单,参考如下
// 在拦截器中加入判断即可
if (System.currentTimeMillis() - DingTokenKeeper.getTokenTime().longValue()) > DingConstant.REFRESH_TOKEN_VALID_TIME) {
// TODO 获取token
}
public class DingTokenKeeper {
// 保证原子性
private static AtomicLong tokenTime = new AtomicLong(0L);
/**
* dingding accessToken
*/
private static String accessToken = "";
public static void setAccessToken(String accessToken) {
log.info("Update token ---------> " + accessToken);
DingTokenKeeper.setTokenTime(new AtomicLong(System.currentTimeMillis()));
DingTokenKeeper.accessToken = accessToken;
}
public static String getAccessToken() {
return accessToken;
}
public static AtomicLong getTokenTime() {
return tokenTime;
}
public static void setTokenTime(AtomicLong tokenTime) {
DingTokenKeeper.tokenTime = tokenTime;
}
}
3. 自动拦截返回错误,统一处理异常,如token失效等
拦截器代码,错误统一处理:
@Component
public class DingErrorDecoder implements ErrorDecoder {
@Override
public RuntimeException invalidRespDecode(Request request, Response response) {
try {
String responseBody = RetrofitUtils.readResponseBody(response);
if (!response.isSuccessful()) {
throw new AppException("非法响应,request=" + request + " response=" + response + "body=" + responseBody);
} else {
JSONObject jObj = JSON.parseObject(responseBody);
// 错误处理
if (StringUtils.isNotBlank(jObj.getString(DingConstant.ERROR_CODE))) {
throw new AppException(responseBody);
}
}
} catch (ReadResponseBodyException e) {
throw new AppException(e.getMessage());
}
return null;
}
}
以上代码 即可应付多数HTTP请求,适用于微服务架构。
Token处理方式因项目而异,所谓条条大路通罗马,只要简单,实用,就可以释放生产力,创造更多价值,在此为这些开源项目点赞!
更高进阶,后续关注。
喜欢的朋友请点赞,多谢支持!