Retrofit is a type safe HTTP client for Android and Java. Supporting HTTP requests through interfaces is the strongest feature of Retrofit. Spring-boot is the most widely used java development framework, but there is no official retrofit support for rapid integration with spring-boot framework, so we developed retrofit-spring-boot-starter.
Retrofit-spring-boot-starter realizes the rapid integration of Retrofit and spring-boot, supports many enhanced features and greatly simplifies development.
🚀The project is in continuous optimization iteration. We welcome everyone to mention ISSUE and PR! Your star✨ is our power for continuous updating!
- Custom injection OkHttpClient
- HTTP calls between microservices
- Annotation interceptor
- Connection pool management
- Log printing
- Request retry
- Error decoder
- Global interceptor
- CallAdapter
- Converter
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>2.1.6</version>
</dependency>You can configure @Configuration for the class with @RetrofitScan or directly configure it to the startup class of spring-boot, as follows:
@SpringBootApplication
@RetrofitScan("com.github.lianjiatech.retrofit.spring.boot.test")
public class RetrofitTestApplication {
public static void main(String[] args) {
SpringApplication.run(RetrofitTestApplication.class, args);
}
}The interface must be marked with @RetrofitClient annotation! Related annotations of HTTP can refer to the official documents: Retrofit official documents.
@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface HttpApi {
@GET("person")
Result<Person> getPerson(@Query("id") Long id);
}Inject the interface into other Service and use!
@Service
public class TestService {
@Autowired
private HttpApi httpApi;
public void test() {
// Initiate HTTP request via HTTP Api
}
}All of the related annotations of HTTP request use native annotations of retrofit. For more information, please refer to the official document: Retrofit official documents. The following is a brief description:
| Annotation classification | Supported annotations |
|---|---|
| Request method | @GET @HEAD @POST @PUT @DELETE @OPTIONS |
| Request header | @Header @HeaderMap @Headers |
| Query param | @Query @QueryMap @QueryName |
| Path param | @Path |
| Form-encoded param | @Field @FieldMap @FormUrlEncoded |
| File upload | @Multipart @Part @PartMap |
| Url param | @Url |
Retrofit-spring-boot-starter supports multiple configurable properties to deal with different business scenarios. You can modify it as appropriate. The specific instructions are as follows:
| Configuration item | Default value | description |
|---|---|---|
| enable-body-call-adapter | true | Whether to enable the bodycalladapter |
| enable-response-call-adapter | true | Whether to enable ResponseCallAdapter |
| enable-log | true | Enable log printing |
| logging-interceptor | DefaultLoggingInterceptor | Log print interceptor |
| pool | Connection pool configuration | |
| disable-void-return-type | false | disable java.lang.Void return type |
| retry-interceptor | DefaultRetryInterceptor | Retry Interceptor |
yml Configuration:
retrofit:
# Enable BodyCallAdapter
enable-body-call-adapter: true
# Enable ResponseCallAdapter
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 java.lang.Void return type
disable-void-return-type: false
# Log print interceptor
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
# Retry Interceptor
retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptorIn general, dynamic creation of OkHttpClient object through the @RetrofitClient annotation can satisfy most usage scenarios. But in some cases, users may need to customize OkHttpClient. At this time, you can define a static method with the return type of OkHttpClient.Builder on the interface to achieve this. The code example is as follows:
@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);
}The method must be marked with
@OkHttpClientBuilderannotation!
By configuring the serviceId and path properties of @Retrofit, HTTP calls between microservices can be realized.
@RetrofitClient(serviceId = "${jy-helicarrier-api.serviceId}", path = "/m/count", errorDecoder = HelicarrierErrorDecoder.class)
@Retry
public interface ApiCountService {
}Users need to implement the ServiceInstanceChooser interface by themselves, complete the selection logic of the service instance, and configure it as the Bean of the Spring container.
For Spring Cloud applications, retrofit-spring-boot-starter provides the implementation of SpringCloudServiceInstanceChooser, Users only need to configure it as the Bean of Spring.
public interface ServiceInstanceChooser {
/**
* 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
*/
URI choose(String serviceId);
}@Bean
@Autowired
public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) {
return new SpringCloudServiceInstanceChooser(loadBalancerClient);
}In many cases, we hope that certain http requests in a certain interface execute a unified interception processing logic. So as to support this feature, retrofit-spring-boot-starter provide annotation interceptor and at the same time achieves matching interception based on URL path. The use is mainly divided into 2 steps:
- Inherit
BasePathMatchInterceptorand write interceptor processor; - Mark the interface with
@Intercept.
The following is an example of how to use annotation interceptors by splicing timestamp after the URL of a specified request.
@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);
}
}@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);
}The above @Intercept: Intercept the request under the path /api/** in the HttpApi interface (excluding /api/test/savePerson).The interception processor uses TimeStampInterceptor.
Sometimes, we need to dynamically pass in some parameters in the intercept annotation and then use these parameter when performing interception. In this case, we can extend the implementation of custom intercept annotation. You must mark custom intercept annotation with @InterceptMark and the annotation must include include(), exclude(), handler() attribute information. The use is mainly divided into 3 steps:
- Custom intercept annotation
- Inherit
BasePathMatchInterceptorand write interceptor processor - Mark the interface with custom intercept annotation
For example, we need to dynamically add the signature information of accesskeyid and accesskeysecret in the request header to initiate HTTP requests normally. In this case, we can customize a signature interceptor Annotation @sign to implement.The following is an example of the custom @sign intercept annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
/**
* secret key
* Support the configuration in the form of placeholder.
*
* @return
*/
String accessKeyId();
/**
* secret key
* Support the configuration in the form of placeholder.
*
* @return
*/
String accessKeySecret();
/**
* Interceptor matching path.
*
* @return
*/
String[] include() default {"/**"};
/**
* Interceptor excludes matching and intercepting by specified path.
*
* @return
*/
String[] exclude() default {};
/**
* The interceptor class which handles the annotation.
* Get the corresponding bean from the spring container firstly.If not, use
* reflection to create one!
*
* @return
*/
Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}There are two points to be noted in the extension of the custom intercept annotation:
Custom intercept annotationmust be marked with@InterceptMark.- The annotation must include
include(), exclude(), handler()attribute information.
@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);
}
}The above accessKeyId and accessKeySecret value will be automatically injected according to the accessKeyId() and accessKeySecret() values of the @sign annotation.If @Sign specifies a string in the form of a placeholder, the configuration property value will be taken for injection. In addition, accessKeyId and accessKeySecret value must provide setter method.
@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);
}In this way, the signature information can be automatically added to the request of the specified URL.
By default, all HTTP requests sent through Retrofit will 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 usage through the poolName attribute of @retrofitclient. For example, we want to make all requests under an interface use the connection pool of poolName = test1. 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
-
Use the
poolNameproperty of@retrofitclientto specify the connection pool to be used.@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1") public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); }
In many cases, we want to record the http request log. You can global control whether the log is enabled through retrofit.enableLog configuration. For each interface, you can control whether to enable it through the enableLog of @RetrofitClient. You can specify the log printing level and log printing strategy of each interface through logLevel and logStrategy. Retrofit-spring-boot-starter supports five log printing levels( ERROR, WARN, INFO, DEBUG, TRACE) which default to INFO and four log printing strategy( NONE, BASIC, HEADERS, BODY) which default to BASIC. The meanings of the 4 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).
By default, retrofit-spring-boot-starter uses DefaultLoggingInterceptor to perform the real log printing function. The bottom is okhttp native HttpLoggingInterceptor. Of course, you can also customize and implement your own log printing interceptor by simply inheriting the baselogginginterceptor( For details, please refer to the implementation of defaultlogginginterceptor), and then configure it in the configuration file.
retrofit:
# log printing interceptor
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptorRetrofit-spring-boot-starter supports request retry feature by adding @Retry annotation to the interface or method. @Retry supports the configuration of maxRetries, intervalMs and retryRules. The retry rule supports three configurations:
RESPONSE_STATUS_NOT_2XX: Retry when the response status code is not2xx;OCCUR_IO_EXCEPTION: Retry when an IO exception occurs;OCCUR_EXCEPTION: Retry when any exception occurs;
The default response status code is not 2xx or automatically retry when an IO exception occurs. If necessary, you can also inherit BaseRetryInterceptor to implement your own request retry interceptor and then configure it.
retrofit:
# request retry interceptor
retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptorWhen an error occurs in the HTTP request (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() annotated by @RetrofitClient. The custom error decoder needs to implement the ErrorDecoder interface:
/**
* 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 Tianming Chen
*/
public interface ErrorDecoder {
/**
* 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.
*
* @param request request
* @param cause IOException
* @return RuntimeException
*/
default RuntimeException ioExceptionDecode(Request request, IOException cause) {
return RetrofitException.errorExecuting(request, cause);
}
/**
* 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);
}
}If we need to implement unified interception processing for HTTP requests of the whole system, we can customize the implementation of global interceptor BaseGlobalInterceptor and configure it as a Bean in Spring! For example, we need to carry source information for all http requests initiated in 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);
}
}You only need to implement the NetworkInterceptor interface and configure it as a bean in the spring container to support automatic weaving into the global network interceptor.
Retrofit can adapt the Call<T> object to the return value type of the interface method by calling the adapter CallAdapterFactory. Retrofit-spring-boot-starter extends two implementations of CallAdapterFactory:
BodyCallAdapterFactory- Feature is enabled by default, and can be disabled by configuring
retrofit.enable-body-call-adapter=false. - Execute the http request synchronously and adapt the response body to an instance of the return value type of the interface method.
- All return types can use this adapter except
Retrofit.Call<T>,Retrofit.Response<T>,java.util.concurrent.CompletableFuture<T>.
- Feature is enabled by default, and can be disabled by configuring
ResponseCallAdapterFactory- Feature is enabled by default, and can be disabled by configuring
retrofit.enable-response-call-adapter=false. - Execute the http request synchronously, adapt the response body content to
Retrofit.Response<T>and return. - If the return value type of the method is
Retrofit.Response<T>, you can use this adapter.
- Feature is enabled by default, and can be disabled by configuring
Retrofit automatically selects the corresponding CallAdapterFactory to perform adaptation processing according to the method return value type! With the default CallAdapterFactory of retrofit, it can support various types of method return values:
Call<T>: Do not perform adaptation processing, directly return theCall<T>object.CompletableFuture<T>: Adapt the response body content to aCompletableFuture<T>object and return.Void: You can useVoidregardless of the return type. If the http status code is not 2xx, just throw an error!Response<T>: Adapt the response content to aResponse<T>object and return.- 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, just throw an error!
/**
* Call<T>
* do not perform adaptation processing, directly return the Call<T> object.
* @param id
* @return
*/
@GET("person")
Call<Result<Person>> getPersonCall(@Query("id") Long id);
/**
* CompletableFuture<T>
* Adapt the response body content to a CompletableFuture<T> object and return.
* @param id
* @return
*/
@GET("person")
CompletableFuture<Result<Person>> getPersonCompletableFuture(@Query("id") Long id);
/**
* Void
* You can use Void regardless of the return type. If the http status code is not 2xx, just throw an error!
* @param id
* @return
*/
@GET("person")
Void getPersonVoid(@Query("id") Long id);
/**
* Response<T>
* Adapt the response content to a Response<T> object and return.
* @param id
* @return
*/
@GET("person")
Response<Result<Person>> 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, just throw an error!
* @param id
* @return
*/
@GET("person")
Result<Person> getPerson(@Query("id") Long id);We can also implement our own CallAdapter by inheriting the CallAdapter.Factory extension and then configure the custom CallAdapterFactory as the bean of spring!
The custom configuration of
CallAdapter.Factoryhas higher priority!
Retrofit uses Converter to convert the object annotated with @Body into the request body, and the response body data into a Java object. The following types of Converter can be used:
- Gson: com.squareup.Retrofit:converter-gson
- Jackson: com.squareup.Retrofit:converter-jackson
- Moshi: com.squareup.Retrofit:converter-moshi
- Protobuf: com.squareup.Retrofit:converter-protobuf
- Wire: com.squareup.Retrofit:converter-wire
- Simple XML: com.squareup.Retrofit:converter-simplexml
Retrofit-spring-boot-starter uses jackson to perform serialization conversion by default. You can directly configure jackson serialization rules through spring.jackson.*. For configuration, please refer to Customize the Jackson ObjectMapper! If you need to use other serialization methods, introduce the corresponding dependency in the project, and then configure the corresponding ConverterFactory as a spring bean.
We can also implement our own Converter by extending the extension of Converter.Factory and then configure the custom Converter.Factory as the bean of spring!
The custom configuration of
Converter.Factoryhas higher priority!
// Encode file names with URLEncoder
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 file = MultipartBody.Part.createFormData("file", fileName, requestBody);
apiService.upload(file);@POST("upload")
@Multipart
Void upload(@Part MultipartBody.Part file);Realize dynamic URL through @url annotation
Note: @url must be placed in the first position of the method parameter. The original definition of @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);If you have any questions, welcome to raise issue or add QQ group to feedback.
QQ Group Number: 806714302
