English Document
Spring-boot-starter for retrofit, supporting rapid integration and feature enhancement .
The project continues to be optimized and iterated, and everyone is welcome to submit ISSUEs and PRs! Please give us a star. Your star is our motivation for continuous updates!
Github project address: https://github.com/LianjiaTech/retrofit-spring-boot-starter
gitee project address: https://gitee.com/lianjiatech/retrofit-spring-boot-starter
Sample demo: https://github.com/ismart-yuxi/retrofit-spring-boot-demo
Thanks to
@ismart-yuxi
for writing the sample demo for this project
< dependency >
< groupId >com.github.lianjiatech</ groupId >
< artifactId >retrofit-spring-boot-starter</ artifactId >
< version >3.1.3</ version >
</ dependency >
If the startup fails, there is a high probability that there is a dependency conflict. Please introduce or exclude related dependencies .
The interface must be marked with @RetrofitClient
annotation ! For HTTP related information, please refer to the official documentation: retrofit official documentation.
@ RetrofitClient ( baseUrl = "${test.baseUrl}" )
public interface UserService {
/**
* 根据id查询用户姓名
*/
@ POST ( "getName" )
String getName ( @ Query ( "id" ) Long id );
}
Note: Method request paths should start with
/
with caution . ForRetrofit
, ifbaseUrl=http://localhost:8080/api/test/
and the method request path isperson
, the complete request path of the method is:http://localhost:8080/api/test/person
. If the method request path is/person
, the complete request path of the method is:http://localhost:8080/person
.
Inject the interface into other Services and use it!
@ Service
public class BusinessService {
@ Autowired
private UserService userService ;
public void doBusiness () {
// call userService
}
}
By default, the SpringBoot
scan path is automatically used for RetrofitClient
registration . You can also add @RetrofitScan
to the configuration class to manually specify the scan path.
Annotations related to HTTP
requests all use Retrofit
native annotations. The following is a simple explanation:
Annotation classification | Supported annotations |
---|---|
Request method | @GET @HEAD @POST @PUT @DELETE @OPTIONS @HTTP |
Request header | @Header @HeaderMap @Headers |
Query parameters | @Query @QueryMap @QueryName |
path parameter | @Path |
form-encoded parameters | @Field @FieldMap @FormUrlEncoded |
Request body | @Body |
File upload | @Multipart @Part @PartMap |
url parameters | @Url |
For detailed information, please refer to the official documentation: retrofit official documentation
The component supports multiple configurable attributes to cope with different business scenarios. The specific supported configuration attributes and default values are as follows:
Note: The application only needs to configure the configuration items to be changed !
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
# 是否聚合打印请求日志
aggregate : true
# 全局重试配置
global-retry :
# 是否启用全局重试
enable : false
# 全局重试间隔时间
interval-ms : 100
# 全局最大重试次数
max-retries : 2
# 全局重试规则
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
If you only need to modify the timeout of OkHttpClient
, you can modify it through @RetrofitClient
related fields or modify the global timeout configuration.
If you need to modify other configurations OkHttpClient
, you can do so by customizing OkHttpClient
. The steps are as follows:
Implement the SourceOkHttpClientRegistrar
interface and call SourceOkHttpClientRegistry#register()
method to register OkHttpClient
@ Component
public class CustomOkHttpClientRegistrar implements SourceOkHttpClientRegistrar {
@ Override
public void register ( SourceOkHttpClientRegistry registry ) {
// 注册customOkHttpClient,超时时间设置为1s
registry . register ( "customOkHttpClient" , new OkHttpClient . Builder ()
. connectTimeout ( Duration . ofSeconds ( 1 ))
. writeTimeout ( Duration . ofSeconds ( 1 ))
. readTimeout ( Duration . ofSeconds ( 1 ))
. addInterceptor ( chain -> chain . proceed ( chain . request ()))
. build ());
}
}
Specify OkHttpClient
to be used by the current interface through @RetrofitClient.sourceOkHttpClient
@ RetrofitClient ( baseUrl = "${test.baseUrl}" , sourceOkHttpClient = "customOkHttpClient" )
public interface CustomOkHttpUserService {
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
User getUser ( @ Query ( "id" ) Long id );
}
Note: The component will not use the specified
OkHttpClient
directly, but will create a new one based on theOkHttpClient
.
The component provides an annotation interceptor that supports interception based on URL path matching. The steps to use are as follows:
BasePathMatchInterceptor
@Intercept
annotation to specify the interceptor to useIf you need to use multiple interceptors, just mark multiple
@Intercept
annotations on the interface.
BasePathMatchInterceptor
to write interception processor @ Component
public class PathMatchInterceptor extends BasePathMatchInterceptor {
@ Override
protected Response doIntercept ( Chain chain ) throws IOException {
Response response = chain . proceed ( chain . request ());
// response的Header加上path.match
return response . newBuilder (). header ( "path.match" , "true" ). build ();
}
}
By default, the component automatically sets scope
of BasePathMatchInterceptor
to prototype
. This feature can be turned off via retrofit.auto-set-prototype-scope-for-path-math-interceptor=false
. After closing, you need to manually set scope
to prototype
.
@ Component
@ Scope ( "prototype" )
public class PathMatchInterceptor extends BasePathMatchInterceptor {
}
@Intercept
to annotate the interface @ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Intercept ( handler = PathMatchInterceptor . class , include = { "/api/user/**" }, exclude = "/api/user/getUser" )
// @Intercept() 如果需要使用多个路径匹配拦截器,继续添加@Intercept即可
public interface InterceptorUserService {
/**
* 根据id查询用户姓名
*/
@ POST ( "getName" )
Response < String > getName ( @ Query ( "id" ) Long id );
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
Response < User > getUser ( @ Query ( "id" ) Long id );
}
The @Intercept
configuration above means: intercept requests under the /api/user/**
path under the InterceptorUserService
interface (excluding /api/user/getUser
), and the interception processor uses PathMatchInterceptor
.
Sometimes, we need to dynamically pass in some parameters in the "interception annotation" and then use these parameters when intercepting. At this time, we can use "custom interception annotations". The steps are as follows:
@InterceptMark
mark must be used, and the annotation must include include、exclude、handler
fields.BasePathMatchInterceptor
to write interception processor For example, we need to "dynamically add accessKeyId
and accessKeySecret
signature information to the request header before initiating an HTTP request." In this case, we can customize @Sign
annotation to achieve this.
@Sign
annotation @ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . TYPE )
@ Documented
@ InterceptMark
public @interface Sign {
String accessKeyId ();
String accessKeySecret ();
String [] include () default { "/**" };
String [] exclude () default {};
Class <? extends BasePathMatchInterceptor > handler () default SignInterceptor . class ;
}
The interceptor used is SignInterceptor
specified in the @Sign
annotation.
SignInterceptor
@ Component
@ Setter
public class SignInterceptor extends BasePathMatchInterceptor {
private String accessKeyId ;
private String accessKeySecret ;
@ Override
public Response doIntercept ( Chain chain ) throws IOException {
Request request = chain . request ();
Request newReq = request . newBuilder ()
. addHeader ( "accessKeyId" , accessKeyId )
. addHeader ( "accessKeySecret" , accessKeySecret )
. build ();
Response response = chain . proceed ( newReq );
return response . newBuilder (). addHeader ( "accessKeyId" , accessKeyId )
. addHeader ( "accessKeySecret" , accessKeySecret ). build ();
}
}
Note: The
accessKeyId
andaccessKeySecret
fields must providesetter
methods.
accessKeyId
and accessKeySecret
field values of the interceptor will be automatically injected based on accessKeyId()
and accessKeySecret()
values of the @Sign
annotation. If @Sign
specifies a string in the form of a placeholder, the configuration attribute value will be used for injection.
@Sign
on the interface @ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Sign ( accessKeyId = "${test.accessKeyId}" , accessKeySecret = "${test.accessKeySecret}" , include = "/api/user/getAll" )
public interface InterceptorUserService {
/**
* 查询所有用户信息
*/
@ GET ( "getAll" )
Response < List < User >> getAll ();
}
The component supports global log printing and declarative log printing.
By default, global log printing is enabled, and the default configuration is as follows:
retrofit :
# 全局日志打印配置
global-log :
# 启用日志打印
enable : true
# 全局日志打印级别
log-level : info
# 全局日志打印策略
log-strategy : basic
# 是否聚合打印请求日志
aggregate : true
# 日志名称,默认为{@link LoggingInterceptor} 的全类名
logName : com.github.lianjiatech.retrofit.spring.boot.log.LoggingInterceptor
The meanings of the four log printing strategies are as follows:
NONE
: No logs.BASIC
: Logs request and response lines.HEADERS
: Logs request and response lines and their respective headers.BODY
: Logs request and response lines and their respective headers and bodies (if present). If you only need to print logs for some requests, you can use the @Logging
annotation on the relevant interface or method.
If you need to modify the log printing behavior, you can inherit LoggingInterceptor
and configure it as Spring bean
.
Component support supports global retry and declarative retry.
Global retry is turned off by default, and the default configuration items are as follows:
retrofit :
# 全局重试配置
global-retry :
# 是否启用全局重试
enable : false
# 全局重试间隔时间
interval-ms : 100
# 全局最大重试次数
max-retries : 2
# 全局重试规则
retry-rules :
- response_status_not_2xx
- occur_io_exception
Retry rules support three configurations:
RESPONSE_STATUS_NOT_2XX
: Retry when the response status code is not 2xx
OCCUR_IO_EXCEPTION
: Retry when an IO exception occursOCCUR_EXCEPTION
: Retry when any exception occurs If only some requests need to be retried, you can use the @Retry
annotation on the corresponding interface or method.
If you need to modify the request retry behavior, you can inherit RetryInterceptor
and configure it as Spring bean
.
Circuit breaker downgrade is turned off by default, and currently supports sentinel
and resilience4j
implementations.
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : sentinel
Configure degrade-type=sentinel
to enable it, and then declare the @SentinelDegrade
annotation on the relevant interface or method.
Remember to manually introduce Sentinel
dependencies:
< dependency >
< groupId >com.alibaba.csp</ groupId >
< artifactId >sentinel-core</ artifactId >
< version >1.6.3</ version >
</ dependency >
In addition, global Sentinel
circuit breaker downgrade is also supported:
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : sentinel
# 全局sentinel降级配置
global-sentinel-degrade :
# 是否开启
enable : true
# ...其他sentinel全局配置
Configure degrade-type=resilience4j
to enable it. Then declare @Resilience4jDegrade
on the relevant interface or method.
Remember to manually introduce Resilience4j
dependency:
< dependency >
< groupId >io.github.resilience4j</ groupId >
< artifactId >resilience4j-circuitbreaker</ artifactId >
< version >1.7.1</ version >
</ dependency >
Global resilience4j circuit breaker downgrade can be enabled through the following configuration:
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : resilience4j
# 全局resilience4j降级配置
global-resilience4j-degrade :
# 是否开启
enable : true
# 根据该名称从#{@link CircuitBreakerConfigRegistry}获取CircuitBreakerConfig,作为全局熔断配置
circuit-breaker-config-name : defaultCircuitBreakerConfig
Circuit breaker configuration management:
Implement the CircuitBreakerConfigRegistrar
interface and register CircuitBreakerConfig
.
@ Component
public class CustomCircuitBreakerConfigRegistrar implements CircuitBreakerConfigRegistrar {
@ Override
public void register ( CircuitBreakerConfigRegistry registry ) {
// 替换默认的CircuitBreakerConfig
registry . register ( Constants . DEFAULT_CIRCUIT_BREAKER_CONFIG , CircuitBreakerConfig . ofDefaults ());
// 注册其它的CircuitBreakerConfig
registry . register ( "testCircuitBreakerConfig" , CircuitBreakerConfig . custom ()
. slidingWindowType ( CircuitBreakerConfig . SlidingWindowType . TIME_BASED )
. failureRateThreshold ( 20 )
. minimumNumberOfCalls ( 5 )
. permittedNumberOfCallsInHalfOpenState ( 5 )
. build ());
}
}
CircuitBreakerConfig
is specified by circuitBreakerConfigName
. Include retrofit.degrade.global-resilience4j-degrade.circuit-breaker-config-name
or @Resilience4jDegrade.circuitBreakerConfigName
If the user needs to use other circuit breaker degradation implementation, inherit BaseRetrofitDegrade
and configure it Spring Bean
.
If @RetrofitClient
does not set fallback
or fallbackFactory
, when the circuit breaker is triggered, RetrofitBlockException
will be thrown directly. Users can customize the method return value when fusing occurs by setting fallback
or fallbackFactory
.
Note: The
fallback
class must be the implementation class of the current interface,fallbackFactory
must beFallbackFactory<T>
implementation class, and the generic parameter type must be the current interface type. In addition,fallback
andfallbackFactory
instances must be configured asSpring Bean
.
Compared with fallbackFactory
fallback
is that it can sense the abnormal cause (cause) of each circuit breaker. The reference example is as follows:
@ Slf4j
@ Service
public class HttpDegradeFallback implements HttpDegradeApi {
@ Override
public Result < Integer > test () {
Result < Integer > fallback = new Result <>();
fallback . setCode ( 100 )
. setMsg ( "fallback" )
. setBody ( 1000000 );
return fallback ;
}
}
@ Slf4j
@ Service
public class HttpDegradeFallbackFactory implements FallbackFactory < HttpDegradeApi > {
@ Override
public HttpDegradeApi create ( Throwable cause ) {
log . error ( "触发熔断了! " , cause . getMessage (), cause );
return new HttpDegradeApi () {
@ Override
public Result < Integer > test () {
Result < Integer > fallback = new Result <>();
fallback . setCode ( 100 )
. setMsg ( "fallback" )
. setBody ( 1000000 );
return fallback ;
}
};
}
}
When HTTP
request error occurs (including an exception or the response data does not meet expectations), the error decoder can decode HTTP
related information into a custom exception. You can specify the error decoder of the current interface in the errorDecoder errorDecoder()
annotation of @RetrofitClient
. A custom error decoder needs to implement the ErrorDecoder
interface:
ServiceInstanceChooser
Users can implement the ServiceInstanceChooser
interface by themselves, complete the service instance selection logic, and configure it as Spring Bean
. For Spring Cloud
applications, the following implementation can be used.
@ Service
public class SpringCloudServiceInstanceChooser implements ServiceInstanceChooser {
private LoadBalancerClient loadBalancerClient ;
@ Autowired
public SpringCloudServiceInstanceChooser ( LoadBalancerClient loadBalancerClient ) {
this . loadBalancerClient = loadBalancerClient ;
}
/**
* Chooses a ServiceInstance URI from the LoadBalancer for the specified service.
*
* @param serviceId The service ID to look up the LoadBalancer.
* @return Return the uri of ServiceInstance
*/
@ Override
public URI choose ( String serviceId ) {
ServiceInstance serviceInstance = loadBalancerClient . choose ( serviceId );
Assert . notNull ( serviceInstance , "can not found service instance! serviceId=" + serviceId );
return serviceInstance . getUri ();
}
}
serviceId
and path
@ RetrofitClient ( serviceId = "user" , path = "/api/user" )
public interface ChooserOkHttpUserService {
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
User getUser ( @ Query ( "id" ) Long id );
}
If we need to perform unified interception processing on HTTP
requests of the entire system, we can implement the global interceptor GlobalInterceptor
and configure it as spring Bean
.
@ Component
public class MyGlobalInterceptor implements GlobalInterceptor {
@ Override
public Response intercept ( Chain chain ) throws IOException {
Response response = chain . proceed ( chain . request ());
// response的Header加上global
return response . newBuilder (). header ( "global" , "true" ). build ();
}
}
Implement the NetworkInterceptor
interface and configure it as spring Bean
.
Retrofit
can adapt the Call<T>
object to the return value type of the interface method through CallAdapterFactory
. The component extends some CallAdapterFactory
implementations:
BodyCallAdapterFactory
HTTP
requests synchronously and adapt the response body content to the return value type of the method.BodyCallAdapterFactory
, with the lowest priority.ResponseCallAdapterFactory
HTTP
request synchronously, adapt the response body content to Retrofit.Response<T>
and return it.ResponseCallAdapterFactory
can be used only if the method return value type is Retrofit.Response<T>
.CallAdapterFactory
Retrofit
will select the corresponding CallAdapterFactory
to perform adaptation processing based on the method return value type . The currently supported return value types are as follows:
String
: Adapt Response Body
to String
and return it.Long
/ Integer
/ Boolean
/ Float
/ Double
): Adapt Response Body
to the above basic typesJava
type: Adapt Response Body
to the corresponding Java
object and return itCompletableFuture<T>
: Adapt Response Body
into CompletableFuture<T>
object and return itVoid
: You can use Void
if you don’t care about the return type.Response<T>
: Adapt Response
into Response<T>
object and return itCall<T>
: Does not perform adaptation processing and directly returns the Call<T>
objectMono<T>
: Project Reactor
reactive return typeSingle<T>
: Rxjava
responsive return type (supports Rxjava2/Rxjava3
)Completable
: Rxjava
responsive return type, HTTP
request has no response body (supports Rxjava2/Rxjava3
) CallAdapter
can be extended by inheriting CallAdapter.Factory
.
The component supports global call adapter factories through retrofit.global-call-adapter-factories
configuration:
retrofit :
# 全局转换器工厂(组件扩展的`CallAdaptorFactory`工厂已经内置,这里请勿重复配置)
global-call-adapter-factories :
# ...
For each Java interface, you can also specify CallAdapter.Factory
used by the current interface through @RetrofitClient.callAdapterFactories
.
Suggestion: Configure
CallAdapter.Factory
asSpring Bean
Retrofit
uses Converter
to convert the @Body
annotated object into Request Body
and convert Response Body
into a Java
object. You can choose from the following Converter
:
The component supports configuring the global Converter.Factory
through retrofit.global-converter-factories
. The default is retrofit2.converter.jackson.JacksonConverterFactory
.
If you need to modify the Jackson
configuration, just overwrite the bean
configuration of JacksonConverterFactory
yourself.
retrofit :
# 全局转换器工厂
global-converter-factories :
- com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
- retrofit2.converter.jackson.JacksonConverterFactory
For each Java
interface, you can also specify Converter.Factory
used by the current interface through @RetrofitClient.converterFactories
.
Suggestion: Configure
Converter.Factory
asSpring Bean
.
Annotations such as @RetrofitClient
, @Retry
, @Logging
, and @Resilience4jDegrade
support meta-annotations, inheritance, and @AliasFor
.
@ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . TYPE )
@ Documented
@ Inherited
@ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Logging ( logLevel = LogLevel . WARN )
@ Retry ( intervalMs = 200 )
public @interface MyRetrofitClient {
@ AliasFor ( annotation = RetrofitClient . class , attribute = "converterFactories" )
Class <? extends Converter . Factory >[] converterFactories () default { GsonConverterFactory . class };
@ AliasFor ( annotation = Logging . class , attribute = "logStrategy" )
LogStrategy logStrategy () default LogStrategy . BODY ;
}
@ FormUrlEncoded
@ POST ( "token/verify" )
Object tokenVerify ( @ Field ( "source" ) String source , @ Field ( "signature" ) String signature , @ Field ( "token" ) String token );
@ FormUrlEncoded
@ POST ( "message" )
CompletableFuture < Object > sendMessage ( @ FieldMap Map < String , Object > param );
// 对文件名使用URLEncoder进行编码
public ResponseEntity importTerminology ( MultipartFile file ){
String fileName = URLEncoder . encode ( Objects . requireNonNull ( file . getOriginalFilename ()), "utf-8" );
okhttp3 . RequestBody requestBody = okhttp3 . RequestBody . create ( MediaType . parse ( "multipart/form-data" ), file . getBytes ());
MultipartBody . Part part = MultipartBody . Part . createFormData ( "file" , fileName , requestBody );
apiService . upload ( part );
return ok (). build ();
}
HTTP
upload interface @ POST ( "upload" )
@ Multipart
Void upload ( @ Part MultipartBody . Part file );
HTTP
download interface @ RetrofitClient ( baseUrl = "https://img.ljcdn.com/hc-picture/" )
public interface DownloadApi {
@ GET ( "{fileKey}" )
Response < ResponseBody > download ( @ Path ( "fileKey" ) String fileKey );
}
HTTP
download usage @ SpringBootTest ( classes = { RetrofitBootApplication . class })
@ RunWith ( SpringRunner . class )
public class DownloadTest {
@ Autowired
DownloadApi downLoadApi ;
@ Test
public void download () throws Exception {
String fileKey = "6302d742-ebc8-4649-95cf-62ccf57a1add" ;
Response < ResponseBody > response = downLoadApi . download ( fileKey );
ResponseBody responseBody = response . body ();
// 二进制流
InputStream is = responseBody . byteStream ();
// 具体如何处理二进制流,由业务自行控制。这里以写入文件为例
File tempDirectory = new File ( "temp" );
if (! tempDirectory . exists ()) {
tempDirectory . mkdir ();
}
File file = new File ( tempDirectory , UUID . randomUUID (). toString ());
if (! file . exists ()) {
file . createNewFile ();
}
FileOutputStream fos = new FileOutputStream ( file );
byte [] b = new byte [ 1024 ];
int length ;
while (( length = is . read ( b )) > 0 ) {
fos . write ( b , 0 , length );
}
is . close ();
fos . close ();
}
}
Dynamic URLs can be implemented using the @url
annotation. At this time, baseUrl
can be configured with any legal URL. For example: http://github.com/
. At runtime, requests will only be initiated based on the @Url
address.
Note:
@url
must be placed in the first position of method parameters. In addition,@GET
,@POST
and other annotations do not need to define the endpoint path.
@ GET
Map < String , Object > test3 ( @ Url String url , @ Query ( "name" ) String name );
DELETE
request adds request body @ HTTP ( method = "DELETE" , path = "/user/delete" , hasBody = true )
GET
request okhttp3
itself does not support adding request bodies GET
requests. The source code is as follows:
The author gave specific reasons, you can refer to: issue
However, if you really need to do this, you can use: @HTTP(method = "get", path = "/user/get", hasBody = true)
and use lowercase get
to bypass the above restrictions.
If you have any questions, please raise an issue or join the QQ group for feedback.
Group number: 806714302