在SpringBoot项目中快速使用Retrofit2,easy-retrofit-spring-boot-starter提供了一个基于注解的配置创建Retrofit实例,并通过更多的注释实现了通用功能的增强。
本项目使用Springboot的动态代理机制,在Spring上下文的生命周期前期,将Retrofit实例注入到Spring上下文。
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}'
}
| 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(*) |
前提条件:你已经掌握了Retrofit的基本用法
如下代码展示了在在传统Retrofit2的使用中,GitHubService接口需要被显式的创建。
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
在传统Retrofit2的使用中,GitHubService接口需要被显式的创建。
通过retrofit-spring-boot-starter, GitHubService不再需要显式的创建,而是被Spring容器动态代理创建。
如下展示了如何使用retrofit-spring-boot-starter快速使用Retrofit2的方式。
@EnableRetrofit在Springboot的启动类上增加@EnableRetrofit注解,代表可以开启Retrofit2的自动配置。
@EnableRetrofit
@SpringBootApplication
public class QuickStartApplication {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
您可以指定basePackages,比如@EnableRetrofit(basePackages = "xxx.demo.api"),"xxx.demo.api"是您的Retrofit api
文件夹名。默认情况下,将扫描starter类文件所在目录中的所有文件
@RetrofitBuilder@RetrofitBuilder 注解可以用来配置Retrofit2的配置信息,如baseUrl等。他完整的代替了显式的Retrofit.Builder()
来创建Retrofit实例。
@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);
}
和 application.yml
app:
hello:
url: http://localhost:8080/
请保持app.hello.url 在你的资源配置文件中, baseUrl 也可以是一个URL, 就像 http://localhost:8080/ 一样。
当然你也可以直接写成@RetrofitBuilder(baseUrl = "http://localhost:8080/")。
可以在Spring的Controller中注入Retrofit API接口。
@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());
}
}
你可以参考 retrofit-spring-boot-starter-sample-quickstart & retrofit-spring-boot-starter-sample-backend-services
是的,恭喜你,你的代码应该能正常工作。
@RetrofitBuilder, 如果你需要你可以在@RetrofitBuilder中设置Retrofit的其他属性, @RetrofitBuilder中的属性名称与Retrofit.Builder()中的方法名是相同的
你可以按需的添加其他的Retrofit属性到 @RetrofitBuilder
@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);
}
创建一个自定义的ConvertFactory类 需要继承BaseConverterFactoryBuilder类
public class GsonConvertFactoryBuilder extends BaseConverterFactoryBuilder {
@Override
public Converter.Factory buildConverterFactory() {
return GsonConverterFactory.create();
}
}
创建一个自定义的CallAdapterFactory类 需要继承BaseCallAdapterFactoryBuilder
public class RxJavaCallAdapterFactoryBuilder extends BaseCallAdapterFactoryBuilder {
@Override
public CallAdapter.Factory buildCallAdapterFactory() {
return RxJavaCallAdapterFactory.create();
}
}
创建一个自定义的CallBackExecutor 需要继承 BaseCallBackExecutorBuilder
public class CallBackExecutorBuilder extends BaseCallBackExecutorBuilder {
@Override
public Executor buildCallBackExecutor() {
return command -> command.run();
}
}
创建一个自定义的OKHttpClient 需要继承 BaseOkHttpClientBuilder
public class OkHttpClientBuilder extends BaseOkHttpClientBuilder {
@Override
public OkHttpClient.Builder buildOkHttpClientBuilder(OkHttpClient.Builder builder) {
return builder.connectTimeout(Duration.ofMillis(30000));
}
}
重要事项:
当需要在自定义的Builder中使用spring容器管理的对象时,只需在类头上使用@Component,并注入所需的对象
@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));
}
}
okhttpclient:
timeout: 30000
创建一个自定义的 OKHttpClient Interceptor 需要继承BaseInterceptor
,并且在接口名上声明如@RetrofitInterceptor(handler = MyRetrofitInterceptor.class)
@RetrofitBuilder(baseUrl = "${app.hello.url}",
addConverterFactory = {GsonConvertFactoryBuilder.class},
client = OkHttpClientBuilder.class)
@RetrofitInterceptor(handler = LoggingInterceptor.class)
@RetrofitInterceptor(handler = MyRetrofitInterceptor.class)
public interface BaseApi {
}
@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);
}
}
重要事项:
当需要在自定义的Builder中使用spring容器管理的对象时,只需在类头上使用@Component,并注入所需的对象
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);
}
}
小贴士:
如果只想设置拦截器,而不想对OKHttpClient对象进行其他修改,则可以从@RetrofitBuilder
删除client = OkHttpClientBuilder.class属性,无需自定义OKHttpClient
你可以在@RetrofitInterceptor 上设置 include,exclude, type 和 sort 属性
like @RetrofitInterceptor(handler = MyRetrofitInterceptor.class, exclude = {"/v1/hello/*"})
当使用exclude, 相应的API将忽略此拦截器
当使用sort, 请确保所有拦截器都使用了sort属性,因为默认情况下,sort为0. 您可以通过int类型的值确保拦截器的执行顺序。
默认的,拦截器从上到下装载。**
当使用type, type是一个Interceptor的枚举类, 你可以指定此拦截器是OkHttpClient 的addInterceptor()
方式或addNetworkInterceptor()方式
你可以参考 retrofit-spring-boot-starter-sample-builder & retrofit-spring-boot-starter-sample-backend-services
如果你有成百上千个接口方法,它来自同一个HTTP URL源,你希望你的代码结构更有序,看起来与后台服务Controller结构一致,你可以这样做
extends 模式使用 extends 关键字来继承声明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);
}
@RetrofitBase 模式使用@RetrofitBase 来设置有@RetrofitBuilder的接口
@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);
}
如果 HelloApi 使用 extends BaseApi 并且使用了 @RetrofitBase(baseInterface = BaseApi.class),
starter会优先去使用 @RetrofitBase(baseInterface = BaseApi.class)
你可以使用@RetrofitUrlPrefix去定义一个URL前缀,就像SpringBoot MVC中的@RequestMapping
@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);
}
在运行中,这两个接口会生成相同的URL /v1/hello/{message}
你可以参考 retrofit-spring-boot-starter-sample-inherit & retrofit-spring-boot-starter-sample-backend-services Warning: 如果在同一Controller注入父接口和子接口,可能会发生以下错误
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
所以,需要使用@Qualifier(""),其值为API接口类全名, 请尽量不要混用父接口与子接口
@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());
}
}
当Retrofit配置相同且仅为baseUrl的后缀部分不同时,会只创建一个Retrofit实例。
在retrofit-spring-boot-starter中可以看到,一个retrofit实例被称之为RetrofitClient
,一个Retrofit实例的接口被称之为RetrofitApiService。
你可以通过Demo中的Log查看到RetrofitClient和RetrofitApiService之间的关系。
通过这样的方式,可以减少Retrofit实例的数量,降低内存占用。
你可以参考 retrofit-spring-boot-starter-sample-single-instance & retrofit-spring-boot-starter-sample-backend-services
你可以使用@RetrofitDynamicBaseUrl 动态的改变@RetrofitBuilder中的baseUrl
你可以参考 [retrofit-spring-boot-starter-sample-dynamic-url](https://github.com/liuziyuan/easy-retrofit-demo/tree/main/retrofit-spring
$ claude mcp add retrofit-spring-boot-starter \
-- python -m otcore.mcp_server <graph>