在SpringBoot
项目直接使用okhttp
、httpClient
或者RestTemplate
发起HTTP
请求,既繁琐又不方便统一管理。因此,在这里推荐一个适用于SpringBoot
项目的轻量级HTTP客户端框架retrofit-spring-boot-starter,使用非常简单方便,同时又提供诸多功能增强。目前项目已经更新至2.2.2
版本,并且会持续进行迭代优化。
前言
Retrofit
是适用于Android
和Java
且类型安全的HTTP客户端,其最大的特性的是支持通过接口
的方式发起HTTP请求 。而spring-boot
是使用最广泛的Java开发框架,但是Retrofit
官方没有支持与spring-boot
框架快速整合,因此我们开发了retrofit-spring-boot-starter
。
retrofit-spring-boot-starter
实现了Retrofit
与spring-boot
框架快速整合,并且支持了诸多功能增强,极大简化开发 。
🚀项目持续优化迭代。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
功能特性
- 自定义注入OkHttpClient
- 注解式拦截器
- 连接池管理
- 日志打印
- 请求重试
- 错误解码器
- 全局拦截器
- 熔断降级
- 微服务之间的HTTP调用
- 调用适配器
- 数据转换器
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
快速使用
引入依赖
<dependency> <groupId>com.github.lianjiatech</groupId> <artifactId>retrofit-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency>
定义http接口
接口必须使用@RetrofitClient
注解标记 !http相关注解可参考官方文档:retrofit官方文档。
@RetrofitClient(baseUrl = "${test.baseUrl}") public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); }
注入使用
将接口注入到其它Service中即可使用!
@Service public class TestService { @Autowired private HttpApi httpApi; public void test() { // 通过httpApi发起http请求 } }
HTTP请求相关注解
HTTP
请求相关注解,全部使用了retrofit
原生注解。详细信息可参考官方文档:retrofit官方文档 ,以下是一个简单说明。
注解分类 | 支持的注解 |
请求方式 | @GET @HEAD @POST @PUT @DELETE @OPTIONS |
请求头 | @Header @HeaderMap @Headers |
Query参数 | @Query @QueryMap @QueryName |
path参数 | @Path |
form-encoded参数 | @Field @FieldMap @FormUrlEncoded |
文件上传 | @Multipart @Part @PartMap |
url参数 | @Url |
配置项说明
retrofit-spring-boot-starter
支持了多个可配置的属性,用来应对不同的业务场景。您可以视情况进行修改,具体说明如下:
配置 | 默认值 | 说明 |
enable-log | true | 启用日志打印 |
logging-interceptor | DefaultLoggingInterceptor | 日志打印拦截器 |
pool | 连接池配置 | |
disable-void-return-type | false | 禁用java.lang.Void返回类型 |
retry-interceptor | DefaultRetryInterceptor | 请求重试拦截器 |
global-converter-factories | JacksonConverterFactory | 全局转换器工厂 |
global-call-adapter-factories | BodyCallAdapterFactory,ResponseCallAdapterFactory | 全局调用适配器工厂 |
enable-degrade | false | 是否启用熔断降级 |
degrade-type | sentinel | 熔断降级实现方式(目前仅支持Sentinel) |
resource-name-parser | DefaultResourceNameParser | 熔断资源名称解析器,用于解析资源名称 |
yml
配置方式:
retrofit: enable-response-call-adapter: true # 启用日志打印 enable-log: true # 连接池配置 pool: test1: max-idle-connections: 3 keep-alive-second: 100 test2: max-idle-connections: 5 keep-alive-second: 50 # 禁用void返回值类型 disable-void-return-type: false # 日志打印拦截器 logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor # 请求重试拦截器 retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor # 全局转换器工厂 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 # 是否启用熔断降级 enable-degrade: true # 熔断降级实现方式 degrade-type: sentinel # 熔断资源名称解析器 resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
高级功能
自定义注入OkHttpClient
通常情况下,通过@RetrofitClient
注解属性动态创建OkHttpClient
对象能够满足大部分使用场景。但是在某些情况下,用户可能需要自定义OkHttpClient
,这个时候,可以在接口上定义返回类型是OkHttpClient.Builder
的静态方法来实现。代码示例如下:
@RetrofitClient(baseUrl = "http://ke.com") public interface HttpApi3 { @OkHttpClientBuilder static OkHttpClient.Builder okhttpClientBuilder() { return new OkHttpClient.Builder() .connectTimeout(1, TimeUnit.SECONDS) .readTimeout(1, TimeUnit.SECONDS) .writeTimeout(1, TimeUnit.SECONDS); } @GET Result<Person> getPerson(@Url String url, @Query("id") Long id); }
方法必须使用
@OkHttpClientBuilder
注解标记!
注解式拦截器
很多时候,我们希望某个接口下的某些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的请求上,自动加上签名信息了。
连接池管理
默认情况下,所有通过Retrofit
发送的http请求都会使用max-idle-connections=5 keep-alive-second=300
的默认连接池。当然,我们也可以在配置文件中配置多个自定义的连接池,然后通过@RetrofitClient
的poolName
属性来指定使用。比如我们要让某个接口下的请求全部使用poolName=test1
的连接池,代码实现如下:
- 配置连接池。
retrofit: # 连接池配置 pool: test1: max-idle-connections: 3 keep-alive-second: 100 test2: max-idle-connections: 5 keep-alive-second: 50
- 通过
@RetrofitClient
的poolName
属性来指定使用的连接池。
@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1") public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); }