- Quick Start
- Advanced Configuration
- Interface Inheritance
- URL Prefix
- Single Retrofit Instance
- Dynamic URL
- Global Configuration
- Interceptor Extension
- Why is there another retrofit-spring-boot-starter
- Maintainers
- Contributing
- License
easy-retrofit-spring-boot-starter
provides easier use and enhancement of common functions of Retrofit2 in SpringBoot project, and realizes the enhancement of common functions through more annotations.
Maven:
<dependency>
<groupId>io.github.easyretrofit</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${latest-version}</version>
</dependency>
Gradle:
dependencies {
implementation 'io.github.easyretrofit:spring-boot-starter:${latest-version}'
}
This version is compatible with SpringBoot2 and Springboot3 versions under different JDKs as follows:
jdk version | Springboot2 version | Springboot3 version |
---|---|---|
jdk8 | 2.0.0.RELEASE - 2.7.x | NA |
jdk11 | 2.0.0.RELEASE - 2.7.x | NA |
jdk17 | 2.4.2 - 2.7.x | 3.0.0 - latest |
jdk21 | 2.7.16 - 2.7.x | 3.1.0 - latest |
The @EnableRetrofit
Annotation will enable to use retrofit-spring-boot-stater.
@EnableRetrofit
@SpringBootApplication
public class QuickStartApplication {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
You can specify basePackages like @EnableRetrofit(basePackages = "xxx.demo.api")
, "xxx.demo.api" is your retrofit APIs
folder name. By default, all files in the directory where the starter class file is located will be scanned
@RetrofitBuilder
will create a Retrofit.Builder()
object, and it will be managed by Spring container
baseUrl
can be a URL string or a properties in a resource file
// baseUrl = "http://localhost:8080/"
@RetrofitBuilder(baseUrl = "${app.hello.url}")
public interface HelloApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("v1/hello/{message}")
Call<ResponseBody> hello(@Path("message") String message);
}
and application.yml
app:
hello:
url: http://localhost:8080/
Use @Autowired
to inject API Interface, the retrofit-spring-boot-starter will help you to create instance of API
Interface file.
@RestController
@RequestMapping("/v1/hello")
public class HelloController {
@Autowired
private HelloApi helloApi;
@GetMapping("/{message}")
public ResponseEntity<String> hello(@PathVariable String message) throws IOException {
final ResponseBody body = helloApi.hello(message).execute().body();
return ResponseEntity.ok(body.string());
}
}
You can refer to retrofit-spring-boot-starter-sample-quickstart & retrofit-spring-boot-starter-sample-backend-services
You can set the other properties of Retrofit in @RetrofitBuilder, the @RetrofitBuilder
properties name is same as
method name of Retrofit.Builder()
@RetrofitBuilder(baseUrl = "${app.hello.url}",
addConverterFactory = {GsonConvertFactoryBuilder.class},
addCallAdapterFactory = {RxJavaCallAdapterFactoryBuilder.class},
callbackExecutor = CallBackExecutorBuilder.class,
client = OkHttpClientBuilder.class,
validateEagerly = false)
@RetrofitInterceptor(handler = LoggingInterceptor.class)
@RetrofitInterceptor(handler = MyRetrofitInterceptor.class)
public interface HelloApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("v1/hello/{message}")
Call<HelloBean> hello(@Path("message") String message);
}
Create a custom ConvertFactory need extend BaseConverterFactoryBuilder
public class GsonConvertFactoryBuilder extends BaseConverterFactoryBuilder {
@Override
public Converter.Factory buildConverterFactory() {
return GsonConverterFactory.create();
}
}
Create a custom CallAdapterFactory need extend BaseCallAdapterFactoryBuilder
public class RxJavaCallAdapterFactoryBuilder extends BaseCallAdapterFactoryBuilder {
@Override
public CallAdapter.Factory buildCallAdapterFactory() {
return RxJavaCallAdapterFactory.create();
}
}
Create a custom CallBackExecutor need extend BaseCallBackExecutorBuilder
public class CallBackExecutorBuilder extends BaseCallBackExecutorBuilder {
@Override
public Executor buildCallBackExecutor() {
return command -> command.run();
}
}
Create a custom OKHttpClient need extend BaseOkHttpClientBuilder
public class OkHttpClientBuilder extends BaseOkHttpClientBuilder {
@Override
public OkHttpClient.Builder buildOkHttpClientBuilder(OkHttpClient.Builder builder) {
return builder.connectTimeout(Duration.ofMillis(30000));
}
}
important:
When you need to use the objects managed by the spring container in the Custom Builder, you only need to
use @Component
on the class header and inject the objects you need
@Component
public class MyOkHttpClient extends BaseOkHttpClientBuilder {
@Value("${okhttpclient.timeout}")
private int timeout;
@Override
public OkHttpClient.Builder buildOkHttpClientBuilder(OkHttpClient.Builder builder) {
return builder.connectTimeout(Duration.ofMillis(timeout));
}
}
and application.yml
okhttpclient:
timeout: 30000
The OkHttpClient Interceptor object can be created separately from the OkHttpClient object, which makes it more flexible to expand and use
Create a custom Interceptor of OKHttpClient need extend BaseInterceptor
important:
When you need to use the objects managed by the spring container in the Custom Interceptor, you only need to
use @Component
on the class header and inject the objects you need
@Component
public class MyRetrofitInterceptor extends BaseInterceptor {
/**
* The context is created and registered in the spring container by retrofit-spring-boot-starter. The context object includes all retrofit-spring-boot-starter context objects
*/
@Autowired
private RetrofitResourceContext context;
@SneakyThrows
@Override
protected Response executeIntercept(Chain chain) {
Request request = chain.request();
String clazzName = Objects.requireNonNull(request.tag(Invocation.class)).method().getDeclaringClass().getName();
final RetrofitServiceBean currentServiceBean = context.getRetrofitServiceBean(clazzName);
// TODO if you need
return chain.proceed(request);
}
}
and set class name like @RetrofitInterceptor(handler = MyRetrofitInterceptor.class)
public class LoggingInterceptor extends BaseInterceptor {
private HttpLoggingInterceptor httpLoggingInterceptor;
public LoggingInterceptor() {
httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
}
@SneakyThrows
@Override
protected Response executeIntercept(Chain chain) {
return httpLoggingInterceptor.intercept(chain);
}
}
Tips:
When you only want to set the interceptor without making other modifications to the OKHttpClient object, you can delete
the client = OkHttpClientBuilder.class
property of @RetrofitBuilder
and There is no need to customize your
OKHttpClient
you could set include
,exclude
, type
and sort
properties in @RetrofitInterceptor
like @RetrofitInterceptor(handler = MyRetrofitInterceptor.class, exclude = {"/v1/hello/*"})
When exclude
is used, the corresponding API will ignore this interceptor.
When you use sort
, please ensure that all interceptors use sort, because by default, sort is 0. You can ensure the
execution order of your interceptors through int type. By default, the interceptor is loaded from top to bottom.
When you use type
, type is an Interceptor Enum , You can specify whether this interceptor is to be addInterceptor()
or addNetworkInterceptor()
in OkHttpClient
You can refer to retrofit-spring-boot-starter-sample-retrofitbuilder & retrofit-spring-boot-starter-sample-backend-services
If you have hundreds of Interface method, it is from a same source Base URL, and you want your code structure to be more orderly and look consistent with the source service structure, you could do this,
Use extends
keyword to inherit the interface that declares @RetrofitBuilder
@RetrofitBuilder(baseUrl = "${app.hello.url}",
addConverterFactory = {GsonConvertFactoryBuilder.class},
client = OkHttpClientBuilder.class)
@RetrofitInterceptor(handler = LoggingInterceptor.class)
@RetrofitInterceptor(handler = MyRetrofitInterceptor.class)
public interface BaseApi {
}
public interface HelloApi extends BaseApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("v1/hello/{message}")
Call<HelloBean> hello(@Path("message") String message);
}
Use @RetrofitBase annotation to set @RetrofitBuilder Interface file
@RetrofitBase(baseInterface = BaseApi.class)
public interface HelloApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("v1/hello/{message}")
Call<HelloBean> hello(@Path("message") String message);
}
if HelloApi
use extends BaseApi
and used @RetrofitBase(baseInterface = BaseApi.class)
, The starter first to
use @RetrofitBase(baseInterface = BaseApi.class)
You can use @RetrofitUrlPrefix
to define the prefix of URL, just like using @RequestMapping
of springboot
@RetrofitUrlPrefix("/v1/hello/")
public interface HelloApi extends BaseApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("{message}")
Call<HelloBean> hello(@Path("message") String message);
}
public interface HelloApi extends BaseApi {
/**
* call hello API method of backend service
*
* @param message message
* @return
*/
@GET("v1/hello/{message}")
Call<HelloBean> hello(@Path("message") String message);
}
The URLs of the two hello
methods are the same
You can refer to retrofit-spring-boot-starter-sample-inherit & retrofit-spring-boot-starter-sample-backend-services
Warning: If you inject the parent Interface and the inherited Interface at the same place, the following errors may occur
Description:
Field api in io.liuziyuan.demo.controller.HelloController required a single bean, but 2 were found:
- io.github.easyretrofit.samples.inherit.api.BaseApi: defined in null
- io.github.easyretrofit.samples.inherit.api.HelloApi: defined in null
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
So, you need use @Qualifier(""), and the value is API Interface class full name, Please try not to use the parent class in the injected place
@RestController
@RequestMapping("/v1/hello")
public class HelloController {
@Autowired
@Qualifier("io.github.easyretrofit.samples.inherit.api.BaseApi")
private BaseApi baseApi;
@Autowired
@Qualifier("io.github.easyretrofit.samples.inherit.api.helloApi")
private HelloApi helloApi;
@GetMapping("/{message}")
public ResponseEntity<String> hello(@PathVariable String message) throws IOException {
final HelloBean helloBody = helloApi.hello(message).execute().body();
return ResponseEntity.ok(helloBody.getMessage());
}
}
Create a single Retrofit instance When the Retrofit configuration is the same and only the SUFFIX part of the baseUrl
is different
You can refer to retrofit-spring-boot-starter-sample-single-instance & retrofit-spring-boot-starter-sample-backend-services
You can use @RetrofitDynamicBaseUrl
to dynamically change the baseUrl
in @RetrofitBuilder
You can refer to retrofit-spring-boot-starter-sample-awesome & retrofit-spring-boot-starter-sample-backend-services
In spring cloud micro services cluster, You can use @RetrofitCloudService
to call another micro service.
This function depends on spring-cloud-starter-loadbalancer
,so adding to pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
@RetrofitBuilder
@RetrofitCloudService(name = "catalog")
public interface RetrofitApi {
@GET("echo/{string}")
Call<ResponseBody> echo(@Path("string") String string);
}
the catalog
is name of provider micro service. That's the name in the registry center.
you could use@RetrofitDynamicBaseUrl
dynamic change baseUrl
of@RetrofitBuilder
You can refer to retrofit-spring-boot-starter-sample-awesome & retrofit-spring-boot-starter-sample-backend-services
You can configure the retrofit-spring-boot-starter
global configuration in the Spring Boot project's configuration file application.yml
.
When global configuration is enabled, all the configurations in the @RetrofitBuilder
will use the global configuration.
retrofit:
global:
enable: true
overwrite-type: global_first
base-url: http://localhost:8080
converter-factory-builder-clazz: io.github.liuziyuan.test.retrofit.spring.boot.common.JacksonConverterFactoryBuilder,io.github.liuziyuan.test.retrofit.spring.boot.common.GsonConverterFactoryBuilder
call-adapter-factory-builder-clazz: io.github.liuziyuan.test.retrofit.spring.boot.common.RxJavaCallAdapterFactoryBuilder
validate-eagerly: false
The properties here are exactly the same as those in @RetrofitBuilder
. You can refer to the comments in @RetrofitBuilder
for explanation.
The overwrite-type
provides two modes: global_first
and local_first
.
When global_first
, the global configuration will be merged with the @RetrofitBuilder
configuration and use the properties of the global configuration. The properties of the global configuration will be used if they are empty.
When local_first
, the global configuration will be merged with the @RetrofitBuilder
configuration and use the properties of the @RetrofitBuilder
configuration. The properties of the @RetrofitBuilder
configuration will be used if they are empty.
Provide a denyGlobalConfig = true
in @RetrofitBuilder
to refuse the global configuration and keep its independence without being polluted by the global configuration.
@RetrofitBuilder(baseUrl = "http://localhost:8080/", denyGlobalConfig = true)
public interface HelloApi {
}
You can refer to retrofit-spring-boot-starter-sample-global-config
You can write Interceptor-based extensions for Retrofit,
Take for example @RetrofitCloudService
, an Interceptor-based extension that implements load balancing for Retrofit in Spring Cloud
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@RetrofitDynamicBaseUrl
@RetrofitInterceptor(handler = RetrofitCloudInterceptor.class)
public @interface RetrofitCloudService {
@AliasFor(
annotation = RetrofitDynamicBaseUrl.class,
attribute = "value"
)
String name() default "";
}
@Component
public class RetrofitCloudInterceptor extends BaseInterceptor {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RetrofitResourceContext context;
@SneakyThrows
@Override
protected Response executeIntercept(Chain chain) {
Request request = chain.request();
final Method method = super.getRequestMethod(request);
String clazzName = super.getClazzNameByMethod(method);
final RetrofitApiInterfaceBean currentServiceBean = context.getRetrofitApiInterfaceBean(clazzName);
RetrofitCloudService annotation = null;
annotation = currentServiceBean.getSelfClazz().getAnnotation(RetrofitCloudService.class);
if (annotation == null) {
annotation = currentServiceBean.getParentClazz().getAnnotation(RetrofitCloudService.class);
}
final String serviceName = annotation.name();
if (StringUtils.isNotEmpty(serviceName)) {
final URI uri = loadBalancerClient.choose(serviceName).getUri();
final HttpUrl httpUrl = HttpUrl.get(uri);
HttpUrl newUrl = request.url().newBuilder()
.scheme(httpUrl.scheme())
.host(httpUrl.host())
.port(httpUrl.port())
.build();
request = request.newBuilder().url(newUrl).build();
}
return chain.proceed(request);
}
}
public class RetrofitCloudServiceExtension implements RetrofitInterceptorExtension {
@Override
public Class<? extends Annotation> createAnnotation() {
return RetrofitCloudService.class;
}
@Override
public Class<? extends BaseInterceptor> createInterceptor() {
return RetrofitCloudInterceptor.class;
}
}
@Configuration
public class RetrofitSpringCouldWebConfig {
@Bean
@ConditionalOnMissingBean
public RetrofitCloudServiceExtension retrofitSpringCouldWebConfig() {
return new RetrofitCloudServiceExtension();
}
@Bean
@ConditionalOnMissingBean
public RetrofitCloudInterceptor retrofitCloudInterceptor() {
return new RetrofitCloudInterceptor();
}
}
@RetrofitBuilder
@RetrofitCloudService(name = "catalog")
public interface RetrofitApi {
@GET("echo/{string}")
Call<ResponseBody> echo(@Path("string") String string);
}
You can refer to retrofit-spring-boot-starter-sample-plugin
First, thank lianjiatech for providing an almost perfect project of retrofit-spring-boot-starter.
However, in use, I found that it will create a retrofit instance for each API Interface file, which in my opinion is a waste of resources. After reading the code, I think it is difficult to modify the original basis in a short time, so I repeated a wheel.
In my work, the team will use retrofit as the API of BFF layer HTTP client to request micro services. Therefore, there will be hundreds of interface files in BFF. Therefore, I improved the time of creating retrofit instance, allowing one retrofit interface to inherit one base interface, which can define and configure retrofit attributes
Feel free to dive in! Open an issue or submit PRs.
Standard Readme follows the Contributor Covenant Code of Conduct.
This project exists thanks to all the people who contribute.
MIT © liuziyuan