Retrofit
is a type-safe HTTP client for Android
and Java
, and its biggest feature is that it supports initiating HTTP requests through an interface
. Spring-boot is the most widely used Java development framework, but Retrofit
officially does not support rapid integration with the spring-boot framework, so we developed retrofit-spring-boot-starter
.
retrofit-spring-boot-starter
enables rapid integration of retrofit
and spring-boot
frameworks, and supports many feature enhancements, greatly simplifying development.
Features
< ul class="list-paddingleft-2">
for quick use
Introducing dependent
<dependency> <
groupId>com.github.lianjiatech</ groupId>
<artifactId>retrofit-spring-boot-starterartifactId>
<version> 2.2.2version>
dependency> copy code
definition http interface
Interfaces must be marked with @RetrofitClient
annotations! For the http concern, please refer to the official documentation: [retrofit official documentation]
@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface HttpApi { @GET("person")
Result getPerson(@Query("id" ) Long id) ; }Copy code
injection can be used by
injecting interfaces into other services!
@Service
public class TestService { @Autowired
private HttpApi httpApi; public void test() {
http request via httpAPI }} Copy the code
HTTP request concern solution
HTTP
request concern solution, all using retrofit
native annotations. **For details, please refer to the official documentation: [retrofit official documentation]
annotation
classification | supported annotation |
---|---|
request methods | @GET @HEAD @POST @PUT @DELETE @OPTIONS |
request | headers @Header @HeaderMap @Headers |
The Query | @Query @Path @QueryMap @QueryName |
path parameter |
|
The form-coded parameter | @Field @FieldMap @FormUrlEncoded |
file upload | @Multipart @Part @PartMap |
URL parameter | @Url |
description of the configuration item
Retrofit-spring-boot-starter
supports several configurable properties to address different business scenarios. You can modify it as appropriate, as follows:
Configure | the default value | to |
---|---|---|
enable-log | true | enable log printing |
logging-interceptor | DefaultLoggingInterceptorlogprintinterceptorpool | |
connection pool configuration | ||
disable-void-return-type | false | disables java.lang.void return type |
retry-interceptor | DefaultRetryInterceptor | request retry interceptor |
global-converter-factories | JacksonConverterFactoryGlobal | converter |
factoryglobal-call-adapter-factories | BodyCallAdapterFactory, ResponseCallAdapterFactory | Global Call Adapter Factory |
enable-degrade | false | Whether circuit breaker downgrade is enabled degrade-type |
sentinel | Circuit breaker downgrade implementation (currently only supported Sentinel) | |
resource-name-parser | DefaultResourceNameParser | fuses the resource name resolver to resolve resource names |
YML
configuration method
:
retrofit: enable-response-call-adapter:
true
# Enable log printing
enable-log: true
# Connection pool configuration pool:
test1:
max-idle-connections: 3
keep-alive-second: 100
test2:
max-idle-connections: 5
keep-alive-second: 50 # Disable void return value type
disable-void-return-type:
false # Log print interceptor
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor # Request retry interceptor
retry-interceptor
: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
# Global converter factory
global-converter-factories:
- retrofit2.converter.jackson.JacksonConverterFactory
# Global call adapter factory
global-call-adapter-factories:
- com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
- com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory
# Whether circuit breaker downgrade
is enabled enable-degrade: true
# Circuit breaker implementation
degrade-type: sentinel
# Circuit breaker resource name parser
resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
copies code
for advanced functionality
Custom injection of OkHttpClient In general, dynamically creating OkHttpClient
objects by @RetrofitClient
annotation properties can meet most usage scenarios. However, in some cases, users may need to customize
OkHttpClient
, in this case, you can define the return type on the interface as a static method implementation of OkHttpClient.Builder.
The code example is as follows:
@RetrofitClient(baseUrl = "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 getPerson(@Url String url, @Query(
"The id
method must be marked with
@OkHttpClientBuilder
annotation!”!
Annotation interceptors
Many times, we want certain HTTP requests under a certain interface to perform a unified interception processing logic. To support this feature, Retrofit-Spring-Boot-Starter
provides annotated interceptors for URL-path matching. The steps used are mainly divided into 2 steps:
-
inherit BasePathMatchInterceptor
to write interception handlers; -
The interface is labeled with @Intercept
. If you need to configure multiple interceptors, just mark multiple@Intercept
annotations on the interface!
The following is an example of using annotation interceptors by concatenating timestamps after the URL of a specified request.
Inherit the BasePathMatchInterceptor to write the interceptor
handler
@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
annotated @RetrofitClient on the copy code interface
(baseUrl = "${ test.baseUrl}")
@Intercept(handler = TimeStampInterceptor. class, include = {"/api/**"}, exclude = "/api/test/savePerson")
public interface HttpApi { @GET("person")
Result getPerson(@Query("id") Long id) ; @POST("savePerson")
Result savePerson(@Body Person person) ; The
@Intercept
configuration above the copy code indicates that it intercepts requests under the /
api/** path under the HttpApi
interface (excluding /api/test/savePerson
) and intercepts the processor TimeStampInterceptor
。
Extended annotation interceptors
Sometimes, we need to dynamically pass in some parameters in the interception annotation, and then use this parameter when performing the interception. In this case, we can extend the implementation of custom interception annotations. Custom interception annotations
must use @InterceptMark
tags and must include information for the include(), exclude(), handler()
attributes. The steps used are mainly divided into 3 steps:
- custom interception
-
annotations inherit BasePathMatchInterceptor
to write interception handlers -
Use custom interception annotations on the interface;
For example, we need to dynamically add accessKeyId
and accessKeySecret
signature information to the request header to initiate http requests normally, and at this time we can customize a signed interceptor annotation @Sign
to achieve. The following is an example of a custom @Sign
interception annotation.
Custom @Sign
Annotation
@Retention (RetentionPolicy.RUNTIME)
@Target( ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
/** * Key key * supports placeholder configuration. * * @return
*/
String accessKeyId();
/** Key supports placeholder configuration. * * @return
*/
String accessKeySecret();
/** * interceptor match path * * @return
*/
String[] include() default {"/**"};
/** * interceptor exclude matching, exclude specified path interception * * @return
*/
String[] exclude() default {};
/** * Interceptor class that handles the annotation * Get the corresponding bean from the spring container first, if you can't get it, create one with reflection! * * @return
*/
Class extends BasePathMatchInterceptor> handler() default SignInterceptor. class; }Copy code
extension custom interception annotations have the following 2 points to note:
-
annotations
must use@InterceptMark
markup. -
Annotations must include information for the include(), exclude(), handler()
attributes.
custom interception
Implement 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);
}} Copy
the
above accessKeyId
and accessKeySecret field values will be based on the accessKeyId() and
accessKeySecret()
of the
@Sign annotations.
Values are injected automatically, and if @Sign
specifies a string as a placeholder, the configuration property value is taken for injection. In addition, the accessKeyId and accessKeySecret
fields must provide setter
methods.
Use @Sign
@RetrofitClient on the interface (baseUrl = "${test.baseUrl}"
). @Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", exclude = {"/api/test/person"})
public interface HttpApi { @GET("person")
Result getPerson (@Query("id") Long id) ; @POST("savePerson")
Result savePerson(@Body Person person) ; Copy the code
so that the signature information is automatically added to the request for the specified URL.
Connection Pool Management
By default, all http requests sent via Retrofit
use the default connection pool of max-idle-connections=5 keep-alive-second=300.
Of course, we can also configure multiple custom connection pools in the configuration file and then specify the use by @RetrofitClient
the poolName
property. For example, if we want to let all requests under an interface use the poolName=test1
connection pool, the code implementation is as follows:
-
configure the connection pool.
retrofit:
# Connection pool configuration pool:
test1:
max-idle-connections: 3
keep-alive-second: 100
test2:
max-idle-connections: 5
keep-alive-second: 50
The copy code -
specifies the connection pool used by
@RetrofitClient
thepoolName
property.@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1")
public interface HttpApi {@GET("person")
ResultgetPerson(@Query("id" ) Long id) ; }Copy code
log printing
In many cases, we want to log down HTTP requests. Through retrofit.enableLog
configuration, you can globally control whether the log is enabled or not. For each interface, you can control whether it is enabled through the enableLog @RetrofitClient
, and through logLevel
and logStrategy
, you can specify the log printing level and log printing policy of each interface.
retrofit-spring-boot-starter
supports 5 log print levels (ERROR
, WARN
, INFO, DEBUG,
TRACE
), default INFO
; SUPPORTS FOUR LOG PRINTING POLICIES (NONE
, BASIC,
HEADERS
, BODY
), AND THE DEFAULT BASIC
IS SUPPORTED. The four log printing strategies have the following meanings
:
-
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).
retrofit-spring-boot-starter
uses DefaultLoggingInterceptor by default to perform real log printing functions, and its underlying layer is the native
HttpLoggingInterceptor
of okhttp. Of course, you can also customize the implementation of your own log print interceptor, only need to inherit the BaseLoggingInterceptor (you can refer to the
implementation of DefaultLoggingInterceptor
for details), and then configure it in the
configuration file.
retrofit:
# log print interceptor
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
Copy code
request to retry
retrofit-spring-boot-starter
supports request retry by simply adding @Retry
annotations to the interface or method. @Retry
supports maxRetries
, interval Ms,
and retryRules
configurations. The retry rule supports three configurations
:
-
RESPONSE_STATUS_NOT_2XX
: retry is performed when the response status code is not2xx;
-
OCCUR_IO_EXCEPTION
: Retry occurs when an IO exception occurs; -
OCCUR_EXCEPTION
: Retry occurs when any exception occurs;
The default response status code is not 2xx
or an IO exception is automatically retried. If needed, you can also inherit BaseRetryInterceptor
to implement your own request retry interceptor and then configure it.
retrofit:
# Request to retry the interceptor retry-interceptor
: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
Copy code
error decoder
in When HTTP request errors occur (including exceptions or response data that do 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() annotation @RetrofitClient
, and the custom error decoder needs to implement the ErrorDecoder
interface:
/** * Error decoder. ErrorDecoder. * When an exception occurs in the request or an invalid response result is received, the HTTP-related information is decoded into the exception, and the invalid response is judged by the business itself * * When an exception occurs in the request or an invalid response result is received, the HTTP related information is decoded into the exception, * and the invalid response is determined by the business itself. * * @author Chen Tim Ming
*/
public interface ErrorDecoder {
/** * When the invalid response is invalid, the HTTP information is decoded into the exception, and the invalid response is determined by the service. * When the response is invalid, decode the HTTP information into the exception, invalid response is determined by business. * * @param request request
* @param response response
* @return If it returns null, the processing is ignored and the processing continues with the original response.
*/
default RuntimeException invalidRespDecode(Request request, Response response) {
if (! response.isSuccessful()) {
throw RetrofitException.errorStatus(request, response); } return null;
}
/** * When an IO exception occurs in the request, the HTTP information is decoded into the exception. * When an IO exception occurs in the request, the HTTP information is decoded into the exception. * * @param request request
* @param cause IOException
* @return RuntimeException
*/
default RuntimeException ioExceptionDecode(Request request, IOException cause) {
return RetrofitException.errorExecuting(request, cause); }
/** * When an exception other than an IO exception occurs in the request, the HTTP information is decoded into the exception. * When the request has an exception other than the IO exception, the HTTP information is decoded into the exception. * * @param request request
* @param cause Exception
* @return RuntimeException
*/
default RuntimeException exceptionDecode(Request request, Exception cause) {
return RetrofitException.errorUnknown(request, cause); }}Copy codeGlobal
interceptor
Global application interceptor If we need to perform unified interception processing of http requests for the entire system, we can customize the implementation of the global
interceptor BaseGlobalInterceptor
, and configure it as
a bean
in the spring
container! For example, we need to bring the origin information to the HTTP request initiated by the entire system.
@Component
public class SourceInterceptor extends BaseGlobalInterceptor {
@Override
public Response doIntercept(Chain chain) throws IOException { Request request = chain.request(); Request newReq = request.newBuilder() .addHeader("source", "test")
.build(); return chain.proceed(newReq);
}}} Copy code
Global network interceptor only needs to implement the NetworkInterceptor
interface and configure it as a bean
in the spring
container to support automatic weaving into the global network interceptor.
In
a distributed service architecture, circuit breaker degradation of unstable external services is one of the important measures to ensure high service availability. Since the stability of external services is not guaranteed, the response time will become longer when the external service is unstable. Correspondingly, caller response times become longer and threads accumulate, which can eventually exhaust the caller’s thread pool and render the entire service unavailable. Therefore, we need to circuit down unstable weak dependent service calls, temporarily cut off unstable calls, and avoid local instability leading to an avalanche of the overall service.
retrofit-spring-boot-starter
supports the circuit breaker downgrade function, which is based on [Sentinel]
1. Enable the circuit breaker downgrade function By default, the circuit breaker downgrade function
is turned off. You need to set the corresponding configuration items to enable the circuit breaker degrading function
:
retrofit: # Whether to enable circuit breaker degrade enable-degrade:
true
# Circuit breaker downgrade implementation (currently only supported by Sentinel)
degrade-type: sentinel # resource name
resolver resource-name-parser
: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
replication code
resource
name resolver is used to implement user-defined resource names, the default configuration is DefaultResourceNameParser
, the corresponding resource name format is ‘HTTP_OUT:GET:In
addition, since the circuit breaker
degrade function is optional, enabling circuit breaker downgrade requires users to introduce Sentinel dependencies:
< by themselves dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId> sentinel-coreartifactId>
<version>1.6.3version>
dependency > copy code
2. Configure downgrade rules (optional)
retrofit-spring-boot-starter
supports annotated configuration of downgrade rules via @Degrade
annotation to configure the demotion rule. @Degrade
annotations can be configured on interfaces or methods, with higher priority over methods.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface Degrade {
/** * RT threshold or exception ratio threshold count. */
double count();
/** * Degrade recover timeout (in seconds) when degradation occurs. */
int timeWindow() default 5;
/** * Degrade strategy (0: average RT, 1: exception ratio). */
DegradeStrategy degradeStrategy() default DegradeStrategy.AVERAGE_RT; }Copy
the
codeIf the application project supports configuring downgrade rules through the configuration center, you can ignore the annotation configuration method.
3. @RetrofitClient set fallback or fallbackFactory (optional)
if @RetrofitClient
do not set fallback
or FallbackFactory
, when the circuit breaker is triggered, will directly throw the RetrofitBlockException
exception. Users can customize the method return value during circuit breaker by setting fallback
or fallbackFactory
. The fallback class must be the implementation class of the current interface, the fallbackFactory must be the FallbackFactory
implementation class, and the generic parameter type is the current interface type. In addition, fallback and
fallbackFactory
instances must be configured as Beans
of the Spring
container.
The main difference between fallbackFactory
and fallback
is the ability to sense the abnormal cause of each circuit breaker. The reference example is as follows:
@Slf4j
@Service
public class HttpDegradeFallback implements HttpDegradeApi { @Override
public Result test() {
Result fallback = new Result<>();
fallback.setCode(100)
.setMsg("fallback")
.setBody(1000000);
return fallback; }}Copy Code@Slf4j
@Service
public class HttpDegradeFallbackFactory implements FallbackFactory<HttpDegradeApi> {
/** * Returns an instance of the fallback appropriate for the given cause * * @param Cause fallback cause
* @return An instance of the Retrofit interface implemented. an instance that implements the retrofit interface.
*/
@Override
public HttpDegradeApi create(Throwable cause) {
log.error("Trigger circuit breaker!) , cause.getMessage(), cause);
return new HttpDegradeApi() {
@Override
public Result test() {
Result fallback = new Result<>();
fallback.setCode(100)
.setMsg("fallback")
.setBody(1000000);
return fallback; } }}} Copy HTTP
calls
between microservices in code In order to be able to use microservice calls
, you need to configure the following configuration:
Configure ServiceInstanceChooser
to Spring
Container Bean
users can implement the ServiceInstanceChooser interface by themselves, complete the selection logic of the service instance, and configure it as a Spring container bean
. For Spring
Cloud
applications, retrofit-spring-boot-starter
provides a SpringCloud ServiceInstanceChooser
implementation, which you can simply configure as a Spring
Bean
Can.
@Bean
@Autowired
public ServiceInstanceChooser serviceInstanceChooser( LoadBalancerClient loadBalancerClient) {
return new SpringCloudServiceInstanceChooser(loadBalancerClient); The copy code
uses the @Retrofit
serviceId
and path
properties to implement HTTP calls
between microservices @RetrofitClient(serviceId = "${jy-helicarrier-api.serviceId}", path = "/m/count", errorDecoder = HelicarrierErrorDecoder. class)
@Retry
public interface ApiCountService {}Copy code
calls adapter and data transcoder
Calling the adapter Retrofit can adapt the Call object to the return value type of the interface method by calling
the adapter CallAdapterFactory.
retrofit-spring-boot-starter
extends 2 CallAdapterFactory
implementations:
-
enabled by default and can be turned off by configuring retrofit.enable-body-call-adapter=false
-
Synchronously execute HTTP requests and adapt the response body content to the return value type instance of the interface method. -
Retrofit.Response
,java.util.concurrent.CompletableFuture
, all other return types can use the adapter.
-
ResponseCallAdapterFactory
-
enabled by default. You can configure retrofit.enable-response-call-adapter=false
to turn off -
synchronous execution of http requests, and adapt the response body content to Retrofit.Response Return.
-
If the return value type of the method is Retrofit.Response
, you can use the adapter.
> BodyCallAdapterFactory
With the exception of Retrofit.Call,
Retrofit automatically selects the corresponding CallAdapterFactory
to perform adaptation processing according to the method return value type! Together with Retrofit’s default CallAdapterFactory
, it can support multiple forms of method return value types
:
-
Adapts the response body content to a CompletableFuture
object to -
Void
: Can be used without concern for the return typeVoid
。 If the HTTP status code is not 2xx, throw the error directly! -
: Adapt
-
response content to a corresponding Java type object to return, if the http status code is not 2xx, throw the error directly!
>
Call: Directly return Call
without performing adaptation processing ObjectCompletableFuture:
return
Response: Adapt the response content to the Response object and return any other Java type
the
/** * Call * does not perform adaptation processing, and directly returns the Call object * @param id
* @return
* /
@GET ("person")
Call> getPersonCall(@Query("id") Long id); /** * CompletableFuture
adapts the response body content to the CompletableFuture object return * @param id
* @return
*/
@GET ( "person")
CompletableFuture> getPersonCompletableFuture(@Query("id") Long id);
/** * Void * Void can be used without concern for the return type. If the HTTP status code is not 2xx, throw the error directly! * @param id
* @return
*/
@GET("person")
Void getPersonVoid( @Query("id") Long id); /** * Response *
Adapts the response content to the Response object and returns * @param id
* @return
* /
@GET ("person"
). Response> getPersonResponse(@Query("id") Long id);
/** * Any other Java type * Adapt the response body content to a corresponding Java type object and return, if the http status code is not 2xx, throw the error directly! * @param id
* @return
*/
@GET("person")
Result getPerson (@Query("id") Long id) ; We
can also implement our own CallAdapter by inheriting the CallAdapter.Factory
extension!
retrofit-spring-boot-starter
supports configuring the global call adapter factory via retrofit.global-call-adapter-factories
, where factory instances are fetched from the Spring container first, and if not, reflection is created. The default global call adapter factory is [BodyCallAdapterFactory, ResponseCallAdapterFactory]
!
retrofit:
# Global call adapter factory
global-call-adapter-factories:
- com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
- com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory
copies code
for each Java interface and can also be @RetrofitClient
The callAdapterFactories()
of the annotation specifies the CallAdapter.Factory
used by the current interface, and the specified factory instance is still fetched from the Spring container first.
Note: If CallAdapter.Factory
does not have a public
parameterless constructor, manually configure it as a Bean
object for the Spring
container!
The data transcoder
Retrofit
uses Converter
to convert @Body
annotated objects into request bodies and response body data into a Java
object, using the following options Converter
:
-
[Gson] -
[Jackson] -
[Moshi] -
[Protobuf] -
[Wire] -
[Simple XML] -
[JAXB]
retrofit-spring-boot-starter
supports configuring a global data converter factory via retrofit.global-converter-factories
, where the converter factory instance is fetched from the Spring container first, and if it is not obtained, reflection is created. The default global data converter factory is retrofit2.converter.jackson.JacksonConverterFactory
, you can directly configure jackson serialization rules through spring.jackson.*
, the configuration can be referred to [Customize the Jackson ObjectMapper]
retrofit:
# Global converter factory
global-converter-factories:
- retrofit2.converter.jackson.JacksonConverterFactory
copies code
for each Java interface and can also be annotated through @RetrofitClient
converterFactories()
specifies the Converter.Factory
used by the current interface, and the specified converter factory instance is still obtained from the Spring container first.
Note: If Converter.Factory
does not have a public
parameterless constructor, manually configure it as a Bean
object for the Spring
container!
retrofit-spring-boot-starter
A lightweight HTTP
client framework for SpringBoot
projects, has been running stably online for more than a year, and has been used by multiple external companies. Interested friends can try the
original | https://juejin.cn/post/6898485806587969544