Retrofit
前言
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装
快速使用
引入依赖
<dependency> <groupId>com.github.lianjiatech</groupId> <artifactId>retrofit-spring-boot-starter</artifactId> <version>2.2.16</version> </dependency>
参数设置
全局超时时间设置
retrofit: # 全局连接超时时间 global-connect-timeout-ms: 5000 # 全局读取超时时间 global-read-timeout-ms: 5000 # 全局写入超时时间 global-write-timeout-ms: 5000 # 全局完整调用超时时间 global-call-timeout-ms: 0
连接池管理
配置好自定义连接池后再通过@RetrofitClient
的poolName
属性来指定使用的连接池。
# 连接池配置 pool: # test1连接池配置 test1: # 最大空闲连接数 max-idle-connections: 3 # 连接保活时间(秒) keep-alive-second: 100
请求重试
retrofit: # 重试配置 retry: # 是否启用全局重试 enable-global-retry: true # 全局重试间隔时间 global-interval-ms: 20 # 全局最大重试次数 global-max-retries: 10 # 全局重试规则 global-retry-rules: - response_status_not_2xx # 重试拦截器 retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
重试规则支持三种配置:
-
RESPONSE_STATUS_NOT_2XX
:响应状态码不是2xx
时执行重试; -
OCCUR_IO_EXCEPTION
:发生IO异常时执行重试; -
OCCUR_EXCEPTION
:发生任意异常时执行重试;
日志打印
retrofit: # 日志打印配置 log: # 启用日志打印 enable: true # 日志打印拦截器 logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor # 全局日志打印级别 global-log-level: info # 全局日志打印策略 global-log-strategy: body
4种日志打印策略含义如下:
-
NONE
:没有日志。 -
BASIC
:记录请求和响应行。 -
HEADERS
:记录请求和响应行及其各自的标头。 -
BODY
:记录请求和响应行及其各自的标头和正文(如果存在)。
注解式拦截器
很多时候,我们希望某个接口下的某些http请求执行统一的拦截处理逻辑。为了支持这个功能,retrofit-spring-boot-starter
提供了注解式拦截器,做到了基于url路径的匹配拦截。使用的步骤主要分为2步:
-
继承
BasePathMatchInterceptor
编写拦截处理器; -
接口上使用
@Intercept
进行标注。如需配置多个拦截器,在接口上标注多个@Intercept
注解即可!
下面以给指定请求的url后面拼接timestamp时间戳为例,介绍下如何使用注解式拦截器。
继承BasePathMatchInterceptor
编写拦截处理器
@Component public class TimeStampInterceptor extends BasePathMatchInterceptor { @Override public Response doIntercept(Chain chain) throws IOException { Request request = chain.request(); HttpUrl url = request.url(); long timestamp = System.currentTimeMillis(); HttpUrl newUrl = url.newBuilder() .addQueryParameter("timestamp", String.valueOf(timestamp)) .build(); Request newRequest = request.newBuilder() .url(newUrl) .build(); return chain.proceed(newRequest); } }
接口上使用@Intercept
进行标注
@RetrofitClient(baseUrl = "${test.baseUrl}") @Intercept(handler = TimeStampInterceptor.class, include = {"/api/**"}, exclude = "/api/test/savePerson") public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); @POST("savePerson") Result<Person> savePerson(@Body Person person); }
上面的@Intercept
配置表示:拦截HttpApi
接口下/api/**
路径下(排除/api/test/savePerson
)的请求,拦截处理器使用TimeStampInterceptor
。
扩展注解式拦截器
有的时候,我们需要在拦截注解动态传入一些参数,然后再执行拦截的时候需要使用这个参数。这种时候,我们可以扩展实现自定义拦截注解。自定义拦截注解
必须使用@InterceptMark
标记,并且注解中必须包括include()、exclude()、handler()
属性信息。使用的步骤主要分为3步:
-
自定义拦截注解
-
继承
BasePathMatchInterceptor
编写拦截处理器 -
接口上使用自定义拦截注解;
例如我们需要在请求头里面动态加入accessKeyId
、accessKeySecret
签名信息才能正常发起http请求,这个时候可以自定义一个加签拦截器注解@Sign
来实现。下面以自定义@Sign
拦截注解为例进行说明。
自定义@Sign
注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @InterceptMark public @interface Sign { /** * 密钥key * 支持占位符形式配置。 * * @return */ String accessKeyId(); /** * 密钥 * 支持占位符形式配置。 * * @return */ String accessKeySecret(); /** * 拦截器匹配路径 * * @return */ String[] include() default {"/**"}; /** * 拦截器排除匹配,排除指定路径拦截 * * @return */ String[] exclude() default {}; /** * 处理该注解的拦截器类 * 优先从spring容器获取对应的Bean,如果获取不到,则使用反射创建一个! * * @return */ Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class; }
扩展自定义拦截注解
有以下2点需要注意:
-
自定义拦截注解
必须使用@InterceptMark
标记。 -
注解中必须包括
include()、exclude()、handler()
属性信息。
实现SignInterceptor
@Component public class SignInterceptor extends BasePathMatchInterceptor { private String accessKeyId; private String accessKeySecret; public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; } public void setAccessKeySecret(String accessKeySecret) { this.accessKeySecret = accessKeySecret; } @Override public Response doIntercept(Chain chain) throws IOException { Request request = chain.request(); Request newReq = request.newBuilder() .addHeader("accessKeyId", accessKeyId) .addHeader("accessKeySecret", accessKeySecret) .build(); return chain.proceed(newReq); } }
上述accessKeyId
和accessKeySecret
字段值会依据@Sign
注解的accessKeyId()
和accessKeySecret()
值自动注入,如果@Sign
指定的是占位符形式的字符串,则会取配置属性值进行注入。另外,accessKeyId
和accessKeySecret
字段必须提供setter
方法。
接口上使用@Sign
@RetrofitClient(baseUrl = "${test.baseUrl}") @Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", exclude = {"/api/test/person"}) public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); @POST("savePerson") Result<Person> savePerson(@Body Person person); }
这样就能在指定url的请求上,自动加上签名信息了。
使用retrfit访问外部应用接口
使用@Retrofit
的serviceId
和path
属性,可以实现应用服务之间的HTTP调用
@RetrofitClient(baseUrl = "https://ditu.amap.com") //@Intercept(handler = TimeStampInterceptor.class, include = {"/service/**"}, exclude = "/service/test/savePerson") public interface DituFeign { @GET("service/regeo") Call<Object> regeo(@Query("longitude") String longitude, @Query("latitude") String latitude); }
使用DituFeign去execute执行服务调用
DituFeign.regeo("121.475078", "31.223577") .execute() .body();
微服务之间的HTTP调用
实现微服务调用,需要进行如下配置:
配置ServiceInstanceChooser
为Spring
容器Bean
用户可以自行实现ServiceInstanceChooser
接口,完成服务实例的选取逻辑,并将其配置成Spring
容器的Bean
。对于Spring Cloud
应用,retrofit-spring-boot-starter
提供了SpringCloudServiceInstanceChooser
实现,用户只需将其配置成Spring
的Bean
即可。
@Bean @Autowired public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) { return new SpringCloudServiceInstanceChooser(loadBalancerClient); }
使用@Retrofit
的serviceId
和path
属性,可以实现微服务之间的HTTP调用
@RetrofitClient(serviceId = "${jy-helicarrier-api.serviceId}", path = "/m/count", errorDecoder = HelicarrierErrorDecoder.class) @Retry public interface ApiCountService { }