diff --git a/README.md b/README.md index 1c3f15b..530d561 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ [![Author](https://img.shields.io/badge/Author-chentianming-orange.svg?style=flat-square)](https://juejin.im/user/3562073404738584/posts) [![QQ-Group](https://img.shields.io/badge/QQ%E7%BE%A4-806714302-orange.svg?style=flat-square) ](https://img.ljcdn.com/hc-picture/6302d742-ebc8-4649-95cf-62ccf57a1add) +[English Document](https://github.com/LianjiaTech/retrofit-spring-boot-starter/blob/master/README_EN.md) **`retrofit-spring-boot-starter`实现了`Retrofit`与`spring-boot`框架快速整合,并且支持了诸多功能增强,极大简化开发**。 @@ -27,7 +28,7 @@ gitee项目地址:[https://gitee.com/lianjiatech/retrofit-spring-boot-starter] ## 功能特性 -- [x] [自定义OkHttpClient属性](#自定义OkHttpClient属性) +- [x] [自定义OkHttpClient](#自定义OkHttpClient) - [x] [注解式拦截器](#注解式拦截器) - [x] [日志打印](#日志打印) - [x] [请求重试](#请求重试) @@ -40,7 +41,7 @@ gitee项目地址:[https://gitee.com/lianjiatech/retrofit-spring-boot-starter] - [x] [元注解](#元注解) - [x] [其他功能示例](#其他功能示例) -## 快速使用 +## 快速开始 ### 引入依赖 @@ -88,9 +89,9 @@ gitee项目地址:[https://gitee.com/lianjiatech/retrofit-spring-boot-starter] ``` -### 定义http接口 +### 定义HTTP接口 -**接口必须使用`@RetrofitClient`注解标记**!http相关注解可参考官方文档:[retrofit官方文档](https://square.github.io/retrofit/)。 +**接口必须使用`@RetrofitClient`注解标记**!HTTP相关注解可参考官方文档:[retrofit官方文档](https://square.github.io/retrofit/)。 ```java @RetrofitClient(baseUrl = "${test.baseUrl}") @@ -101,7 +102,7 @@ public interface HttpApi { } ``` -> 友情提示:**方法请求路径慎用`/`开头**。对于`Retrofit`而言,如果`baseUrl=http://localhost:8080/api/test/`,方法请求路径如果是`person`,则该方法完整的请求路径是:`http://localhost:8080/api/test/person`。而方法请求路径如果是`/person`,则该方法完整的请求路径是:`http://localhost:8080/person`。 +> 注意:**方法请求路径慎用`/`开头**。对于`Retrofit`而言,如果`baseUrl=http://localhost:8080/api/test/`,方法请求路径如果是`person`,则该方法完整的请求路径是:`http://localhost:8080/api/test/person`。而方法请求路径如果是`/person`,则该方法完整的请求路径是:`http://localhost:8080/person`。 ### 注入使用 @@ -115,16 +116,16 @@ public class TestService { private HttpApi httpApi; public void test() { - // 通过httpApi发起http请求 + // 使用`httpApi`发起HTTP请求 } } ``` -**默认情况下,自动使用`SpringBoot`扫描路径进行`retrofitClient`注册**。你也可以在配置类加上`@RetrofitScan`手工指定扫描路径。 +**默认情况下,自动使用`SpringBoot`扫描路径进行`RetrofitClient`注册**。你也可以在配置类加上`@RetrofitScan`手工指定扫描路径。 ## HTTP请求相关注解 -`HTTP`请求相关注解,全部使用了`retrofit`原生注解。**详细信息可参考官方文档:[retrofit官方文档](https://square.github.io/retrofit/)**,以下是一个简单说明。 +`HTTP`请求相关注解,全部使用了`Retrofit`原生注解,以下是一个简单说明: | 注解分类|支持的注解 | |------------|-----------| @@ -137,9 +138,11 @@ public class TestService { |文件上传|`@Multipart` `@Part` `@PartMap`| |url参数|`@Url`| -## 配置项说明 +> 详细信息可参考官方文档:[retrofit官方文档](https://square.github.io/retrofit/) -组件支持了多个可配置的属性,用来应对不同的业务场景,具体可支持的配置项及默认值如下: +## 配置属性 + +组件支持了多个可配置的属性,用来应对不同的业务场景,具体可支持的配置属性及默认值如下: **注意:应用只需要配置要更改的配置项!** @@ -198,47 +201,61 @@ retrofit: ## 高级功能 -### 自定义OkHttpClient属性 +### 自定义OkHttpClient + +1. 实现`SourceOkHttpClientRegistrar`接口,调用`SourceOkHttpClientRegistry#register()`方法注册`OkHttpClient`。 + + ```java + @Slf4j + @Component + public class CustomSourceOkHttpClientRegistrar implements SourceOkHttpClientRegistrar { + @Override + public void register(SourceOkHttpClientRegistry registry) { + + // 替换默认的SourceOkHttpClient + registry.register(Constants.DEFAULT_SOURCE_OK_HTTP_CLIENT, new OkHttpClient.Builder() + .addInterceptor(chain -> { + log.info("============替换默认的SourceOkHttpClient============="); + return chain.proceed(chain.request()); + }) + .build()); + + // 添加新的SourceOkHttpClient + registry.register("testSourceOkHttpClient", new OkHttpClient.Builder() + .addInterceptor(chain -> { + log.info("============使用testSourceOkHttpClient============="); + return chain.proceed(chain.request()); + }) + .build()); + } + } + ``` -通过实现`SourceOkHttpClientRegistrar`接口,可以实现自定义注册。然后使用`@RetrofitClient`的`sourceOkHttpClient`属性指定`源OkHttpClient`。 -组件会基于该`OkHttpClient`创建当前接口使用的`OkHttpClient`。可以配置超时时间、代理、连接池、分发等等属性。 +2. 通过`@RetrofitClient.sourceOkHttpClient`指定当前接口要使用的`OkHttpClient`。 -```java + ```java + @RetrofitClient(baseUrl = "${test.baseUrl}", sourceOkHttpClient = "testSourceOkHttpClient") + public interface CustomOkHttpTestApi { + + @GET("person") + Result getPerson(@Query("id") Long id); + } + ``` + +> 注意:组件不会直接使用指定的`OkHttpClient`,而是基于该`OkHttpClient`创建一个新的。 -@Slf4j -@Component -public class CustomSourceOkHttpClientRegistrar implements SourceOkHttpClientRegistrar { - @Override - public void register(SourceOkHttpClientRegistry registry) { - - // 替换默认的SourceOkHttpClient - registry.register(Constants.DEFAULT_SOURCE_OK_HTTP_CLIENT, new OkHttpClient.Builder() - .addInterceptor(chain -> { - log.info("============替换默认的SourceOkHttpClient============="); - return chain.proceed(chain.request()); - }) - .build()); - - // 添加新的SourceOkHttpClient - registry.register("testSourceOkHttpClient", new OkHttpClient.Builder() - .addInterceptor(chain -> { - log.info("============使用testSourceOkHttpClient============="); - return chain.proceed(chain.request()); - }) - .build()); - } -} -``` ### 注解式拦截器 -很多时候,我们希望某个接口下的某些http请求执行统一的拦截处理逻辑。为了支持这个功能,组件提供了**注解式拦截器**,做到了**基于url路径的匹配拦截**。使用的步骤主要分为2步: +组件提供了**注解式拦截器**,支持基于url路径匹配拦截,使用的步骤如下: + +1. 继承`BasePathMatchInterceptor` +2. 使用`@Intercept`注解指定要使用的拦截器 -1. 继承`BasePathMatchInterceptor`编写拦截处理器; -2. 接口上使用`@Intercept`进行标注。如需配置多个拦截器,在接口上标注多个`@Intercept`注解即可! +> 如果需要使用多个拦截器,在接口上标注多个`@Intercept`注解即可。 -下面以*给指定请求的url后面拼接timestamp时间戳*为例,介绍下如何使用注解式拦截器。 +下面以"给指定请求的url后面拼接timestamp时间戳"为例,介绍下如何使用注解式拦截器。 #### 继承`BasePathMatchInterceptor`编写拦截处理器 @@ -281,15 +298,15 @@ public interface HttpApi { 上面的`@Intercept`配置表示:拦截`HttpApi`接口下`/api/**`路径下(排除`/api/test/savePerson`)的请求,拦截处理器使用`TimeStampInterceptor`。 -### 扩展注解式拦截器 +### 自定义拦截注解 -有的时候,我们需要在**拦截注解**动态传入一些参数,然后再执行拦截的时候需要使用这个参数。这种时候,我们可以扩展实现**自定义拦截注解**。`自定义拦截注解`必须使用`@InterceptMark`标记,并且**注解中必须包括`include()、exclude()、handler()`属性信息**。使用的步骤主要分为3步: +有的时候,我们需要在"拦截注解"动态传入一些参数,然后在拦截的时候使用这些参数。 这时候,我们可以使用"自定义拦截注解",步骤如下: -1. 自定义拦截注解 +1. 自定义注解。必须使用`@InterceptMark`标记,并且注解中必须包括`include、exclude、handler`字段。 2. 继承`BasePathMatchInterceptor`编写拦截处理器 -3. 接口上使用自定义拦截注解; +3. 接口上使用自定义注解 -例如我们需要**在请求头里面动态加入`accessKeyId`、`accessKeySecret`签名信息才能正常发起http请求**,这个时候**可以自定义一个加签拦截器注解`@Sign`来实现**。下面以自定义`@Sign`拦截注解为例进行说明。 +例如,我们需要"在请求头里面动态加入`accessKeyId`、`accessKeySecret`签名信息才能再发起HTTP请求",这时候可以自定义`@Sign`注解来实现。 #### 自定义`@Sign`注解 @@ -300,50 +317,20 @@ public interface HttpApi { @Documented @InterceptMark public @interface Sign { - /** - * 密钥key - * 支持占位符形式配置。 - * - * @return - */ + String accessKeyId(); - /** - * 密钥 - * 支持占位符形式配置。 - * - * @return - */ String accessKeySecret(); - /** - * 拦截器匹配路径 - * - * @return - */ String[] include() default {"/**"}; - /** - * 拦截器排除匹配,排除指定路径拦截 - * - * @return - */ String[] exclude() default {}; - /** - * 处理该注解的拦截器类 - * 优先从spring容器获取对应的Bean,如果获取不到,则使用反射创建一个! - * - * @return - */ Class handler() default SignInterceptor.class; } ``` -扩展`自定义拦截注解`有以下2点需要注意: - -1. `自定义拦截注解`必须使用`@InterceptMark`标记。 -2. 注解中必须包括`include()、exclude()、handler()`属性信息。 +在`@Sign`注解中指定了使用的拦截器是`SignInterceptor`。 #### 实现`SignInterceptor` @@ -375,7 +362,9 @@ public class SignInterceptor extends BasePathMatchInterceptor { } ``` -**上述`accessKeyId`和`accessKeySecret`字段值会依据`@Sign`注解的`accessKeyId()`和`accessKeySecret()`值自动注入,如果`@Sign`指定的是占位符形式的字符串,则会取配置属性值进行注入**。另外,**`accessKeyId`和`accessKeySecret`字段必须提供`setter`方法**。 +> 注意:`accessKeyId`和`accessKeySecret`字段必须提供`setter`方法。 + +拦截器的`accessKeyId`和`accessKeySecret`字段值会依据`@Sign`注解的`accessKeyId()`和`accessKeySecret()`值自动注入,如果`@Sign`指定的是占位符形式的字符串,则会取配置属性值进行注入。 #### 接口上使用`@Sign` @@ -392,15 +381,13 @@ public interface HttpApi { } ``` -这样就能在指定url的请求上,自动加上签名信息了。 - ### 日志打印 组件支持支持全局日志打印和声明式日志打印。 #### 全局日志打印 -全局日志打印默认开启,开启后所有`HTTP`请求都会打印日志,默认配置如下: +默认情况下,全局日志打印是开启的,默认配置如下: ```yaml retrofit: @@ -414,7 +401,7 @@ retrofit: log-strategy: basic ``` -**4种日志打印策略含义如下**: +四种日志打印策略含义如下: 1. `NONE`:No logs. 2. `BASIC`:Logs request and response lines. @@ -427,7 +414,7 @@ retrofit: #### 日志打印自定义扩展 -如果需要修改日志打印行为,继承`LoggingInterceptor`,并将其配置成Spring bean即可! +如果需要修改日志打印行为,可以继承`LoggingInterceptor`,并将其配置成`Spring bean`。 #### 聚合日志打印 @@ -446,7 +433,7 @@ public LoggingInterceptor loggingInterceptor(RetrofitProperties retrofitProperti #### 全局重试 -全局重试默认关闭。开启之后,所有`HTTP`请求都会按照配置参数自动重试,默认配置项如下: +全局重试默认关闭,默认配置项如下: ```yaml retrofit: @@ -464,23 +451,23 @@ retrofit: - occur_io_exception ``` -**重试规则支持三种配置**: +重试规则支持三种配置: -1. `RESPONSE_STATUS_NOT_2XX`:响应状态码不是`2xx`时执行重试; -2. `OCCUR_IO_EXCEPTION`:发生IO异常时执行重试; -3. `OCCUR_EXCEPTION`:发生任意异常时执行重试; +1. `RESPONSE_STATUS_NOT_2XX`:响应状态码不是`2xx`时执行重试 +2. `OCCUR_IO_EXCEPTION`:发生IO异常时执行重试 +3. `OCCUR_EXCEPTION`:发生任意异常时执行重试 #### 声明式重试 -如果只需要在指定某些请求才执行重试,可以使用声明式重试!具体就是在接口或者方法上声明`@Retry`注解。 +如果只有一部分请求需要重试,可以在相应的接口或者方法上使用`@Retry`注解。 #### 请求重试自定义扩展 -如果需要修改请求重试行为,继承`RetryInterceptor`,并将其配置成Spring bean即可! +如果需要修改请求重试行为,可以继承`RetryInterceptor`,并将其配置成`Spring bean`。 ### 熔断降级 -熔断降级功能默认关闭。当前支持`sentinel`和`resilience4j`两种实现。 +熔断降级默认关闭,当前支持`sentinel`和`resilience4j`两种实现。 ```yaml retrofit: @@ -490,9 +477,11 @@ retrofit: degrade-type: sentinel ``` -#### sentinel熔断降级 +#### Sentinel + +配置`degrade-type=sentinel`开启,然后在相关接口或者方法上声明`@SentinelDegrade`注解即可。 -配置`degrade-type=sentinel`开启。然后在相关接口或者方法上声明`@SentinelDegrade`即可。另外项目需要自行引入`sentinel`依赖。 +记得手动引入`Sentinel`依赖: ```xml @@ -503,7 +492,7 @@ retrofit: ``` -通过以下配置可开启全局sentinel熔断降级: +此外,还支持全局`Sentinel`熔断降级: ```yaml retrofit: @@ -518,9 +507,11 @@ retrofit: # ...其他sentinel全局配置 ``` -#### resilience4j熔断降级 +#### Resilience4j -配置`degrade-type=resilience4j`开启。然后在相关接口或者方法上声明`@Resilience4jDegrade`即可。另外项目需要自行引入`resilience4j`依赖。 +配置`degrade-type=resilience4j`开启。然后在相关接口或者方法上声明`@Resilience4jDegrade`即可。 + +记得手动引入`Resilience4j`依赖: ```xml @@ -547,42 +538,46 @@ retrofit: circuit-breaker-config-name: defaultCircuitBreakerConfig ``` -通过实现`CircuitBreakerConfigRegistrar`接口,可以注入自定义CircuitBreakerConfig。 然后使用`@Resilience4jDegrade`的`circuitBreakerConfigName` -指定采用熔断配置。 - -```java +熔断配置管理: + +1. 实现`CircuitBreakerConfigRegistrar`接口,注册`CircuitBreakerConfig`。 + + ```java + @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()); + } + } + ``` + +2. 通过`circuitBreakerConfigName`指定`CircuitBreakerConfig`。包括`retrofit.degrade.global-resilience4j-degrade.circuit-breaker-config-name`或者`@Resilience4jDegrade.circuitBreakerConfigName` -@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()); - } -} -``` #### 扩展熔断降级 -如果用户需要使用其他的熔断降级实现,继承`BaseRetrofitDegrade`,并将其配置`bean`即可,具体可参考`SentinelRetrofitDegrade`。 +如果用户需要使用其他的熔断降级实现,继承`BaseRetrofitDegrade`,并将其配置`Spring Bean`。 -#### @RetrofitClient设置fallback或者fallbackFactory (可选) +#### 配置fallback或者fallbackFactory (可选) -如果`@RetrofitClient`不设置`fallback`或者`fallbackFactory`,当触发熔断时,会直接抛出`RetrofitBlockException`异常。**用户可以通过设置`fallback` -或者`fallbackFactory`来定制熔断时的方法返回值**。`fallback`类必须是当前接口的实现类,`fallbackFactory`必须是`FallbackFactory` -实现类,泛型参数类型为当前接口类型。另外,`fallback`和`fallbackFactory`实例必须配置成`Spring`容器的`Bean`。 +如果`@RetrofitClient`不设置`fallback`或者`fallbackFactory`,当触发熔断时,会直接抛出`RetrofitBlockException`异常。 用户可以通过设置`fallback`或者`fallbackFactory`来定制熔断时的方法返回值。 -**`fallbackFactory`相对于`fallback`,主要差别在于能够感知每次熔断的异常原因(cause)**。参考示例如下: +> 注意:`fallback`类必须是当前接口的实现类,`fallbackFactory`必须是`FallbackFactory` +实现类,泛型参数类型为当前接口类型。另外,`fallback`和`fallbackFactory`实例必须配置成`Spring Bean`。 + +`fallbackFactory`相对于`fallback`,主要差别在于能够感知每次熔断的异常原因(cause),参考示例如下: ```java @@ -602,17 +597,10 @@ public class HttpDegradeFallback implements HttpDegradeApi { ``` ```java - @Slf4j @Service public class HttpDegradeFallbackFactory implements FallbackFactory { - /** - * Returns an instance of the fallback appropriate for the given cause - * - * @param cause fallback cause - * @return 实现了retrofit接口的实例。an instance that implements the retrofit interface. - */ @Override public HttpDegradeApi create(Throwable cause) { log.error("触发熔断了! ", cause.getMessage(), cause); @@ -625,8 +613,9 @@ public class HttpDegradeFallbackFactory implements FallbackFactory`对象适配成接口方法的返回值类型。组件扩展2种`CallAdapterFactory` -实现: +`Retrofit`可以通过`CallAdapterFactory`将`Call`对象适配成接口方法的返回值类型。组件扩展了一些`CallAdapterFactory`实现: 1. `BodyCallAdapterFactory` - - 默认启用,可通过配置`retrofit.enable-body-call-adapter=false`关闭 - - 同步执行http请求,将响应体内容适配成接口方法的返回值类型实例。 - - 除了`Retrofit.Call`、`Retrofit.Response`、`java.util.concurrent.CompletableFuture`之外,其它返回类型都可以使用该适配器。 + - 同步执行`HTTP`请求,将响应体内容适配成方法的返回值类型。 + - 任意方法返回值类型都可以使用`BodyCallAdapterFactory`,优先级最低。 2. `ResponseCallAdapterFactory` - - 默认启用,可通过配置`retrofit.enable-response-call-adapter=false`关闭 - - 同步执行http请求,将响应体内容适配成`Retrofit.Response`返回。 - - 如果方法的返回值类型为`Retrofit.Response`,则可以使用该适配器。 - -**Retrofit自动根据方法返回值类型选用对应的`CallAdapterFactory`执行适配处理!加上Retrofit默认的`CallAdapterFactory`,可支持多种形式的方法返回值类型:** - -- 基础类型(`String`/`Long`/`Integer`/`Boolean`/`Float`/`Double`):直接将响应内容转换为上述基础类型。 -- 其它任意POJO类型: 将响应体内容适配成一个对应的POJO类型对象返回,如果http状态码不是2xx,直接抛错!(推荐) -- `CompletableFuture`: 将响应体内容适配成`CompletableFuture`对象返回!(异步调用推荐) -- `Void`: 不关注返回类型可以使用`Void`。如果http状态码不是2xx,直接抛错!(不关注返回值) -- `Response`: 将响应内容适配成`Response`对象返回!(不推荐) -- `Call`: 不执行适配处理,直接返回`Call`对象!(不推荐) - -**响应式编程支持**: - -- `Mono`: project-reactor响应式返回类型 -- `Single`:rxjava响应式返回类型(支持rxjava2/rxjava3) -- `Completable`:rxjava响应式返回类型,http请求没有响应体(支持rxjava2/rxjava3) - + - 同步执行`HTTP`请求,将响应体内容适配成`Retrofit.Response`返回。 + - 只有方法返回值类型为`Retrofit.Response`,才可以使用`ResponseCallAdapterFactory`。 +3. 响应式编程相关`CallAdapterFactory` + +**`Retrofit`会根据方法返回值类型选择对应的`CallAdapterFactory`执行适配处理**,目前支持的返回值类型如下: + +- `String`:将`Response Body`适配成`String`返回。 +- 基础类型(`Long`/`Integer`/`Boolean`/`Float`/`Double`):将`Response Body`适配成上述基础类型 +- 任意`Java`类型: 将`Response Body`适配成对应的`Java`对象返回 +- `CompletableFuture`: 将`Response Body`适配成`CompletableFuture`对象返回 +- `Void`: 不关注返回类型可以使用`Void` +- `Response`: 将`Response`适配成`Response`对象返回 +- `Call`: 不执行适配处理,直接返回`Call`对象 +- `Mono`: `Project Reactor`响应式返回类型 +- `Single`:`Rxjava`响应式返回类型(支持`Rxjava2/Rxjava3`) +- `Completable`:`Rxjava`响应式返回类型,`HTTP`请求没有响应体(支持`Rxjava2/Rxjava3`) ```java @RetrofitClient(baseUrl = "${test.baseUrl}") public interface HttpApi { - /** - * 基础类型(`String`/`Long`/`Integer`/`Boolean`/`Float`/`Double`):直接将响应内容转换为上述基础类型。 - */ @POST("getString") String getString(@Body Person person); - /** - * 其它任意POJO类型: 将响应体内容适配成一个对应的POJO类型对象返回,如果http状态码不是2xx,直接抛错! - */ @GET("person") Result getPerson(@Query("id") Long id); - /** - * `CompletableFuture` :将响应体内容适配成CompletableFuture对象返回,异步调用 - */ @GET("person") CompletableFuture> getPersonCompletableFuture(@Query("id") Long id); - /** - * `Void`: 不关注返回类型可以使用`Void`,如果http状态码不是2xx,直接抛错! - */ @POST("savePerson") Void savePersonVoid(@Body Person person); - /** - * `Response`:将响应内容适配成Response对象返回 - */ @GET("person") Response> getPersonResponse(@Query("id") Long id); - /** - * `Call`:不执行适配处理,直接返回Call对象 - */ @GET("person") Call> getPersonCall(@Query("id") Long id); - - /** - * `Mono` : project-reactor响应式返回类型 - */ @GET("person") Mono> monoPerson(@Query("id") Long id); - - /** - * `Single`:rxjava响应式返回类型(支持rxjava2/rxjava3) - */ + @GET("person") Single> singlePerson(@Query("id") Long id); - - /** - * `Completable`:rxjava响应式返回类型,http请求没有响应体(支持rxjava2/rxjava3) - */ + @GET("ping") Completable ping(); } ``` -**我们也可以通过继承`CallAdapter.Factory`扩展实现自己的`CallAdapter`**! +可以通过继承`CallAdapter.Factory`扩展`CallAdapter`。 -组件支持通过`retrofit.global-call-adapter-factories`配置全局调用适配器工厂,工厂实例优先从Spring容器获取,如果没有获取到,则反射创建。 +组件支持通过`retrofit.global-call-adapter-factories`配置全局调用适配器工厂: ```yaml retrofit: - # 全局转换器工厂(组件扩展的转换器工厂已经内置,这里请勿重复配置) + # 全局转换器工厂(组件扩展的`CallAdaptorFactory`工厂已经内置,这里请勿重复配置) global-call-adapter-factories: # ... ``` -针对每个Java接口,还可以通过`@RetrofitClient`注解的`callAdapterFactories()`指定当前接口采用的`CallAdapter.Factory`,指定的工厂实例依然优先从Spring容器获取。 +针对每个Java接口,还可以通过`@RetrofitClient.callAdapterFactories`指定当前接口采用的`CallAdapter.Factory`。 -**建议:将`CallAdapter.Factory`手动将其配置成`Spring`容器的`Bean`对象**! +> 建议:将`CallAdapter.Factory`配置成`Spring Bean` ### 数据转码器 -`Retrofit`使用`Converter`将`@Body`注解标注的对象转换成请求体,将响应体数据转换成一个`Java`对象,可以选用以下几种`Converter`: +`Retrofit`使用`Converter`将`@Body`注解的对象转换成`Request Body`,将`Response Body`转换成一个`Java`对象,可以选用以下几种`Converter`: - [Gson](https://github.com/google/gson): com.squareup.Retrofit:converter-gson - [Jackson](https://github.com/FasterXML/jackson): com.squareup.Retrofit:converter-jackson @@ -872,24 +762,24 @@ retrofit: - [JAXB](https://docs.oracle.com/javase/tutorial/jaxb/intro/index.html): com.squareup.retrofit2:converter-jaxb - fastJson:com.alibaba.fastjson.support.retrofit.Retrofit2ConverterFactory -组件支持通过`retrofit.global-converter-factories`配置全局数据转换器工厂,转换器工厂实例优先从Spring容器获取,如果没有获取到,则反射创建。 -默认的全局数据转换器工厂是`retrofit2.converter.jackson.JacksonConverterFactory`。如果需要修改jackson配置,自行覆盖`JacksonConverterFactory`的`bean` -配置即可。 +组件支持通过`retrofit.global-converter-factories`配置全局`Converter.Factory`,默认的是`retrofit2.converter.jackson.JacksonConverterFactory`。 + +如果需要修改`Jackson`配置,自行覆盖`JacksonConverterFactory`的`bean`配置即可。 ```yaml retrofit: - # 全局调用适配器工厂(组件扩展的调用适配器工厂已经内置,这里请勿重复配置) + # 全局转换器工厂(组件扩展的转换器工厂已经内置,这里请勿重复配置) global-converter-factories: - retrofit2.converter.jackson.JacksonConverterFactory ``` -针对每个Java接口,还可以通过`@RetrofitClient`注解的`converterFactories()`指定当前接口采用的`Converter.Factory`,指定的转换器工厂实例依然优先从Spring容器获取。 +针对每个`Java`接口,还可以通过`@RetrofitClient.converterFactories`指定当前接口采用的`Converter.Factory`。 -**建议:将`Converter.Factory`手动将其配置成`Spring`容器的`Bean`对象**! +> 建议:将`Converter.Factory`配置成`Spring Bean`。 ### 元注解 -`@RetrofitClient`、`@Retry`、`@Logging`、`@Resilience4jDegrade`等注解支持元注解、继承以及`@AliasFor`。 我们可以随意组合、调整相关注解: +`@RetrofitClient`、`@Retry`、`@Logging`、`@Resilience4jDegrade`等注解支持元注解、继承以及`@AliasFor`。 ```java @@ -912,12 +802,12 @@ public @interface MyRetrofitClient { ## 其他功能示例 -### form参数接口调用 +### form参数 ```java - @FormUrlEncoded +@FormUrlEncoded @POST("token/verify") - Object tokenVerify(@Field("source") String source,@Field("signature") String signature,@Field("token") String token); +Object tokenVerify(@Field("source") String source,@Field("signature") String signature,@Field("token") String token); @FormUrlEncoded @@ -925,33 +815,32 @@ public @interface MyRetrofitClient { CompletableFuture sendMessage(@FieldMap Map param); ``` -### 上传文件 +### 文件上传 -#### 构建MultipartBody.Part +#### 创建MultipartBody.Part ```java // 对文件名使用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(); - } + 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上传接口 +#### `HTTP`上传接口 ```java @POST("upload") @Multipart Void upload(@Part MultipartBody.Part file); - ``` -### 下载文件 +### 文件下载 -#### http下载接口 +#### `HTTP`下载接口 ```java @RetrofitClient(baseUrl = "https://img.ljcdn.com/hc-picture/") @@ -963,7 +852,7 @@ public interface DownloadApi { ``` -#### http下载使用 +#### `HTTP`下载使用 ```java @SpringBootTest(classes = RetrofitTestApplication.class) @@ -1005,31 +894,30 @@ public class DownloadTest { 使用`@url`注解可实现动态URL。此时,`baseUrl`配置任意合法url即可。例如: `http://github.com/` 。运行时只会根据`@Url`地址发起请求。 -**注意:`@url`必须放在方法参数的第一个位置。原有定义`@GET`、`@POST`等注解上,不需要定义端点路径**! +> 注意:`@url`必须放在方法参数的第一个位置,另外,`@GET`、`@POST`等注解上,不需要定义端点路径。 ```java @GET Map test3(@Url String url,@Query("name") String name); - ``` -### DELETE请求传请求体 +### `DELETE`请求添加请求体 ```java @HTTP(method = "DELETE", path = "/user/delete", hasBody = true) ``` -### GET请求传请求体 +### `GET`请求添加请求体 -**okhttp3自身不支持`GET`请求带请求体**。源码如下: +`okhttp3`自身不支持`GET`请求添加请求体,源码如下: ![image](https://user-images.githubusercontent.com/30620547/108949806-0a9f7780-76a0-11eb-9eb4-326d5d546e98.png) ![image](https://user-images.githubusercontent.com/30620547/108949831-1ab75700-76a0-11eb-955c-95d324084580.png) -作者给出了具体原因,可以参考这个issue:https://github.com/square/okhttp/issues/3154 +作者给出了具体原因,可以参考: [issue](https://github.com/square/okhttp/issues/3154) -but,如果实在需要这么干,可以使用`@HTTP(method = "get", path = "/user/get", hasBody = true)`。使用小写`get`绕过上述限制。 +但是,如果实在需要这么做,可以使用:`@HTTP(method = "get", path = "/user/get", hasBody = true)`,使用小写`get`绕过上述限制。 ## 反馈建议 diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..4f91f02 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,862 @@ +## retrofit-spring-boot-starter + +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Build Status](https://api.travis-ci.com/LianjiaTech/retrofit-spring-boot-starter.svg?branch=master)](https://travis-ci.com/github/LianjiaTech/retrofit-spring-boot-starter) +[![Maven central](https://maven-badges.herokuapp.com/maven-central/com.github.lianjiatech/retrofit-spring-boot-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.lianjiatech/retrofit-spring-boot-starter) +[![GitHub release](https://img.shields.io/github/v/release/lianjiatech/retrofit-spring-boot-starter.svg)](https://github.com/LianjiaTech/retrofit-spring-boot-starter/releases) +[![License](https://img.shields.io/badge/JDK-1.8+-4EB1BA.svg)](https://docs.oracle.com/javase/8/docs/index.html) +[![License](https://img.shields.io/badge/SpringBoot-1.5+-green.svg)](https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/) +[![Author](https://img.shields.io/badge/Author-chentianming-orange.svg?style=flat-square)](https://juejin.im/user/3562073404738584/posts) +[![QQ-Group](https://img.shields.io/badge/QQ%E7%BE%A4-806714302-orange.svg?style=flat-square) ](https://img.ljcdn.com/hc-picture/6302d742-ebc8-4649-95cf-62ccf57a1add) + +[中文文档](https://github.com/LianjiaTech/retrofit-spring-boot-starter/blob/master/README.md) + +**`retrofit-spring-boot-starter` realizes the rapid integration of `Retrofit` and `spring-boot` framework, and supports many functional enhancements, which greatly simplifies development**. + + +## Features + +- [x] [Customize OkHttpClient](#Customize OkHttpClient) +- [x] [Annotation Interceptor](#Annotation Interceptor) +- [x] [Log Print](#Log Print) +- [x] [Request Retry](#Request Retry) +- [x] [Fusing Degrade](#Fusing Degrade) +- [x] [Error Decoder](#Error Decoder) +- [x] [HTTP Calls Between Microservices](#HTTP Calls Between Microservices) +- [x] [Global Interceptor](#Global Interceptor) +- [x] [Call Adapter](#Call Adapter) +- [x] [Data Converter](#Data Converter) +- [x] [Meta-annotation](#Meta-annotation) +- [x] [Other Examples](#Other Examples) + +## Quick Start + +### Import Dependencies + +```xml + + com.github.lianjiatech + retrofit-spring-boot-starter + 2.3.5 + +``` + +**This project depends on Retrofit-2.9.0, okhttp-3.14.9, and okio-1.17.5 versions. If the startup fails, you can try to manually introduce complete dependencies**: + +```xml + + com.github.lianjiatech + retrofit-spring-boot-starter + 2.3.5 + + + com.squareup.okhttp3 + logging-interceptor + 3.14.9 + + + com.squareup.okhttp3 + okhttp + 3.14.9 + + + com.squareup.okio + okio + 1.17.5 + + + com.squareup.retrofit2 + retrofit + 2.9.0 + + + com.squareup.retrofit2 + converter-jackson + 2.9.0 + +``` + +### Define HTTP Interface + +**Interfaces must be marked with the `@RetrofitClient` annotation**!For HTTP related annotations, please refer to the official documentation:[Retrofit official documentation](https://square.github.io/retrofit/). + +```java +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface HttpApi { + + @GET("person") + Result getPerson(@Query("id") Long id); +} +``` + +> Notice:**The method request path should be cautiously used at the beginning of `/`**. For `Retrofit`, if `baseUrl=http://localhost:8080/api/test/` and the method request path is `person`, then 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 Using + +**Inject the interface into other services to use**: + +```java +@Service +public class TestService { + + @Autowired + private HttpApi httpApi; + + public void test() { + // Use `httpApi` to initiate HTTP requests + } +} +``` + +**Automatically use `Spring Boot` scan path for `RetrofitClient` registration by default**. You can also manually specify the scan path by adding `@RetrofitScan` to the configuration class. + +## HTTP Related Annotations + +`HTTP` request related annotations, all use `Retrofit` native annotations, the following is a brief description: + +| Classification | Supported Annotations | +|------------|-----------| +| Request Method |`@GET` `@HEAD` `@POST` `@PUT` `@DELETE` `@OPTIONS` `@HTTP`| +| Request Header |`@Header` `@HeaderMap` `@Headers`| +| Query Parameter |`@Query` `@QueryMap` `@QueryName`| +| Path Parameter |`@Path`| +| Form-encoded Parameter |`@Field` `@FieldMap` `@FormUrlEncoded`| +| Request Body |`@Body`| +| File Upload |`@Multipart` `@Part` `@PartMap`| +| Url Parameter |`@Url`| + +> For details, please refer to the official documentation:[Retrofit official documentation](https://square.github.io/retrofit/) + + +## Configuration Properties + +The component supports multiple configurable properties to deal with different business scenarios. The specific supported configuration properties and default values are as follows: + +```yaml +retrofit: + global-converter-factories: + - retrofit2.converter.jackson.JacksonConverterFactory + global-call-adapter-factories: + global-log: + enable: true + log-level: info + log-strategy: basic + + global-retry: + enable: false + interval-ms: 100 + max-retries: 2 + retry-rules: + - response_status_not_2xx + - occur_io_exception + + degrade: + degrade-type: none + global-sentinel-degrade: + enable: false + # Threshold corresponding to each degrade policy. Average response time (ms), exceptions ratio (0-1), number of exceptions (1-N) + count: 1000 + time-window: 5 + # Degradation strategy (0: average response time; 1: ratio of exceptions; 2: number of exceptions) + grade: 0 + + global-resilience4j-degrade: + enable: false + # Get CircuitBreakerConfig from {@link CircuitBreakerConfigRegistry} based on this name as a global circuit breaker configuration + circuit-breaker-config-name: defaultCircuitBreakerConfig +``` + +## Advanced Features + +### Customize OkHttpClient + +1. Implement the `SourceOkHttpClientRegistrar` interface and call the `SourceOkHttpClientRegistry#register()` method to register the `OkHttpClient`. + + ```java + @Slf4j + @Component + public class CustomSourceOkHttpClientRegistrar implements SourceOkHttpClientRegistrar { + @Override + public void register(SourceOkHttpClientRegistry registry) { + + registry.register(Constants.DEFAULT_SOURCE_OK_HTTP_CLIENT, new OkHttpClient.Builder() + .addInterceptor(chain -> { + log.info("============Replace the default SourceOkHttpClient============="); + return chain.proceed(chain.request()); + }) + .build()); + + registry.register("testSourceOkHttpClient", new OkHttpClient.Builder() + .addInterceptor(chain -> { + log.info("============testSourceOkHttpClient============="); + return chain.proceed(chain.request()); + }) + .build()); + } + } + ``` + +2. Specify the `OkHttpClient` to be used by the current interface through `@RetrofitClient.sourceOkHttpClient`. + + ```java + @RetrofitClient(baseUrl = "${test.baseUrl}", sourceOkHttpClient = "testSourceOkHttpClient") + public interface CustomOkHttpTestApi { + + @GET("person") + Result getPerson(@Query("id") Long id); + } + ``` + +> Note: The component will not use the specified `OkHttpClient` directly, but will create a new one based on that `OkHttpClient`. + + + +### Annotation Interceptor + +The component provides **Annotation Interceptor**, which supports interception based on url path matching. The steps used are as follows: + +1. Inherit `BasePathMatchInterceptor` +2. Use the `@Intercept` annotation to specify the interceptor to use + +> If you need to use multiple interceptors, you can mark multiple `@Intercept` annotations on the interface. + +The following is an example of "splicing timestamp behind the specified request url" to introduce how to use annotation interceptors. + +#### Inherit `BasePathMatchInterceptor` + +```java +@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); + } +} + +``` + +#### Use the `@Intercept` annotation to specify the interceptor to use + +```java +@RetrofitClient(baseUrl = "${test.baseUrl}") +@Intercept(handler = TimeStampInterceptor.class, include = {"/api/**"}, exclude = "/api/test/savePerson") +@Intercept(handler = TimeStamp2Interceptor.class) // Need more than one, just add it directly +public interface HttpApi { + + @GET("person") + Result getPerson(@Query("id") Long id); + + @POST("savePerson") + Result savePerson(@Body Person person); +} +``` + +### Custom Interception Annotation + +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 Annotation", the steps are as follows: + +1. Custom annotation. The `@InterceptMark` tag must be used, and the `include, exclude, handler` fields must be included in the annotation. +2. inherit `BasePathMatchInterceptor` +3. Use custom annotation on interfaces + +For example, we need to "dynamically add `accessKeyId` and `accessKeySecret` signature information in the request header to initiate an HTTP request", which can be achieved by customizing the `@Sign` annotation. + +#### Custom `@Sign` Annotation + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@InterceptMark +public @interface Sign { + + String accessKeyId(); + + String accessKeySecret(); + + String[] include() default {"/**"}; + + String[] exclude() default {}; + + Class handler() default SignInterceptor.class; +} +``` + +The interceptor specified in the `@Sign` annotation is `SignInterceptor`. + +#### Implement `SignInterceptor` + +```java +@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); + } +} +``` + +> Note: The `accessKeyId` and `accessKeySecret` fields must provide a `setter` method. + +The `accessKeyId` and `accessKeySecret` field values of the interceptor 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. + +#### Using `@Sign` on the interface + +```java +@RetrofitClient(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); +} +``` + +### Log Print + +Component support supports global log printing and declarative log printing. + +#### Global Log Printing + +By default, global log printing is enabled, and the default configuration is as follows: + +```yaml +retrofit: + global-log: + enable: true + log-level: info + log-strategy: basic +``` + +The meanings of the four log printing strategies are as follows: + +1. `NONE`:No logs. +2. `BASIC`:Logs request and response lines. +3. `HEADERS`:Logs request and response lines and their respective headers. +4. `BODY`:Logs request and response lines and their respective headers and bodies (if present). + +#### Declarative Log Printing + +If only some requests are required to print the log, you can use the `@Logging` annotation on the relevant interface or method. + +#### Log printing custom extension + +If you need to modify the log printing behavior, you can inherit `LoggingInterceptor` and configure it as a `Spring bean`. + +#### Aggregate log printing + +If the logs of the same request need to be aggregated and printed together, `AggregateLoggingInterceptor` can be configured. + +```java +@Bean +public LoggingInterceptor loggingInterceptor(RetrofitProperties retrofitProperties){ + return new AggregateLoggingInterceptor(retrofitProperties.getGlobalLog()); +} +``` + +### Request Retry + +Component support supports global retry and declarative retry. + +#### Global Retry + +Global retry is disabled by default, and the default configuration items are as follows: + +```yaml +retrofit: + global-retry: + enable: false + interval-ms: 100 + max-retries: 2 + retry-rules: + - response_status_not_2xx + - occur_io_exception + ``` + +The retry rule supports three configurations: + +1. `RESPONSE_STATUS_NOT_2XX`: retry when response status code is not `2xx` +2. `OCCUR_IO_EXCEPTION`: Execute retry when IO exception occurs +3. `OCCUR_EXCEPTION`: perform a retry on any exception + +#### Declarative Retry + +If only a part of the request needs to be retried, you can use the `@Retry` annotation on the corresponding interface or method. + +#### Request retry custom extension + +If you need to modify the request retry behavior, you can inherit `RetryInterceptor` and configure it as a `Spring bean`. + +### Fusing Degrade + +The circuit breaker degrade is disabled by default, and currently supports both `sentinel` and `resilience4j` implementations. + +```yaml +retrofit: + degrade: + # Fuse degrade type. The default is none, which means that fuse downgrade is not enabled + degrade-type: sentinel +``` + +#### Sentinel + +Configure `degrade-type=sentinel` to enable, and then declare the `@SentinelDegrade` annotation on the relevant interface or method. + +Remember to manually import `Sentinel` dependencies: + +```xml + + + com.alibaba.csp + sentinel-core + 1.6.3 + +``` + +In addition, global `Sentinel` circuit breaker degrade are also supported: + +```yaml +retrofit: + degrade: + degrade-type: sentinel + global-sentinel-degrade: + enable: true + # Other sentinel global configuration +``` + +#### Resilience4j + +Configure `degrade-type=resilience4j` to enable. Then declare `@Resilience4jDegrade` on the relevant interface or method. + +Remember to manually import `Resilience4j` dependencies: + +```xml + + + io.github.resilience4j + resilience4j-circuitbreaker + 1.7.1 + +``` + +In addition, global `Resilience4j` circuit breaker degrade are also supported: + +```yaml +retrofit: + degrade: + degrade-type: resilience4j + global-resilience4j-degrade: + enable: true + # Get CircuitBreakerConfig from {@link CircuitBreakerConfigRegistry} based on this name as a global circuit breaker configuration + circuit-breaker-config-name: defaultCircuitBreakerConfig +``` + +Circuit breaker configuration management: + +1. Implement the `CircuitBreakerConfigRegistrar` interface and register the `CircuitBreakerConfig`. + + ```java + @Component + public class CustomCircuitBreakerConfigRegistrar implements CircuitBreakerConfigRegistrar { + @Override + public void register(CircuitBreakerConfigRegistry registry) { + + registry.register(Constants.DEFAULT_CIRCUIT_BREAKER_CONFIG, CircuitBreakerConfig.ofDefaults()); + + registry.register("testCircuitBreakerConfig", CircuitBreakerConfig.custom() + .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) + .failureRateThreshold(20) + .minimumNumberOfCalls(5) + .permittedNumberOfCallsInHalfOpenState(5) + .build()); + } + } + ``` + +2. Specify the `CircuitBreakerConfig` via `circuitBreakerConfigName`. Include `retrofit.degrade.global-resilience4j-degrade.circuit-breaker-config-name` or `@Resilience4jDegrade.circuitBreakerConfigName` + + +#### Extended circuit breaker degrade + +If the user needs to use another circuit breaker degrade implementation, inherit `BaseRetrofitDegrade` and configure it with `Spring Bean`. + +#### Configure fallback or fallbackFactory (optional) + +If `@RetrofitClient` does not set `fallback` or `fallbackFactory`, when a circuit breaker is triggered, a `RetrofitBlockException` exception will be thrown directly. Users can customize the method return value when blown by setting `fallback` or `fallbackFactory`. + +> Note: `fallback` class must be the implementation class of the current interface, `fallbackFactory` must be `FallbackFactory` +Implementation class, the generic parameter type is the current interface type. In addition, `fallback` and `fallbackFactory` instances must be configured as `Spring Bean`. + +The main difference between `fallbackFactory` and `fallback` is that it can perceive the abnormal cause (cause) of each fuse. The reference example is as follows: + +```java + +@Slf4j +@Service +public class HttpDegradeFallback implements HttpDegradeApi { + + @Override + public Result test() { + Result fallback = new Result<>(); + fallback.setCode(100) + .setMsg("fallback") + .setBody(1000000); + return fallback; + } +} +``` + +```java +@Slf4j +@Service +public class HttpDegradeFallbackFactory implements FallbackFactory { + + @Override + public HttpDegradeApi create(Throwable cause) { + log.error("触发熔断了! ", cause.getMessage(), cause); + return new HttpDegradeApi() { + @Override + public Result test() { + Result fallback = new Result<>(); + fallback.setCode(100) + .setMsg("fallback") + .setBody(1000000); + return fallback; + } + }; + } +} +``` + +### Error Decoder + +When a request error occurs in `HTTP` (including an exception or the response data does not meet expectations), the error decoder can decode the `HTTP` related information into a custom exception. You can use `errorDecoder()` in the `@RetrofitClient` annotation +Specifies the error decoder of the current interface. Custom error decoders need to implement the `ErrorDecoder` interface: + + +### HTTP Calls Between Microservices + +#### Inherit `ServiceInstanceChooser` + +Users can implement the `ServiceInstanceChooser` interface by themselves, complete the selection logic of service instances, and configure them as `Spring Bean`. For `Spring Cloud` +Application, component provides `SpringCloudServiceInstanceChooser` implementation, users only need to configure it as `Spring Bean`. + +```java +@Bean +@Autowired +public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) { + return new SpringCloudServiceInstanceChooser(loadBalancerClient); +} +``` + +#### Specify `serviceId` and `path` + +```java + +@RetrofitClient(serviceId = "${jy-helicarrier-api.serviceId}", path = "/m/count", errorDecoder = HelicarrierErrorDecoder.class) +public interface ApiCountService {} +``` + +## Global Interceptor + +### Global Application Interceptor + +If we need to perform unified interception processing for `HTTP` requests of the entire system, we can implement the global interceptor `GlobalInterceptor` and configure it as `spring Bean`. + +```java +@Component +public class SourceGlobalInterceptor implements GlobalInterceptor { + + @Autowired + private TestService testService; + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Request newReq = request.newBuilder() + .addHeader("source", "test") + .build(); + testService.test(); + return chain.proceed(newReq); + } +} +``` + +### Global Network Interceptor + +Implement the `NetworkInterceptor` interface and configure it as a `spring Bean`. + +## Call Adapter + +`Retrofit` can adapt `Call` objects to the return type of interface methods through `CallAdapterFactory`. The component extends some `CallAdapterFactory` implementations: + +1. `BodyCallAdapterFactory` + - Execute the `HTTP` request synchronously, adapting the content of the response body to the return value type of the method. + - Any method return value type can use `BodyCallAdapterFactory`, with the lowest priority. +2. `ResponseCallAdapterFactory` + - Execute the `HTTP` request synchronously, adapt the content of the response body to `Retrofit.Response` and return it. + - The `ResponseCallAdapterFactory` can only be used if the method return value type is `Retrofit.Response`. +3. Reactive programming related `CallAdapterFactory`, supports the following method return value types: + +**`Retrofit` will select the corresponding `CallAdapterFactory` to perform adaptation processing according to the return value type of the method**. The currently supported return value types are as follows: + +- String:Adapt `Response Body` to `String` to return. +- Basic type (`Long`/`Integer`/`Boolean`/`Float`/`Double`): adapt `Response Body` to the above basic type +- Any `Java` type: adapt the `Response Body` to the corresponding `Java` object and return it +- `CompletableFuture`: adapt `Response Body` to a `CompletableFuture` object and return it +- `Void`: `Void` can be used regardless of the return type +- `Response`: `Response`: adapt `Response` to a `Response` object and return it +- `Call`: No adaptation processing is performed, and the `Call` object is returned directly +- `Mono`: `Project Reactor` reactive return type +- `Single`: `Rxjava` reactive return type (supports `Rxjava2/Rxjava3`) +- `Completable`: `Rxjava` reactive return type, `HTTP` request has no response body (supports `Rxjava2/Rxjava3`) + +```java +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface HttpApi { + + @POST("getString") + String getString(@Body Person person); + + @GET("person") + Result getPerson(@Query("id") Long id); + + @GET("person") + CompletableFuture> getPersonCompletableFuture(@Query("id") Long id); + + @POST("savePerson") + Void savePersonVoid(@Body Person person); + + @GET("person") + Response> getPersonResponse(@Query("id") Long id); + + @GET("person") + Call> getPersonCall(@Query("id") Long id); + + @GET("person") + Mono> monoPerson(@Query("id") Long id); + + @GET("person") + Single> singlePerson(@Query("id") Long id); + + @GET("ping") + Completable ping(); +} + +``` + +`CallAdapter` can be extended by extending `CallAdapter.Factory`. + +Components support configuring global call adapter factories via `retrofit.global-call-adapter-factories`: + +```yaml +retrofit: + # The `CallAdaptorFactory` factory extended by the component has been built in, please do not repeat the configuration here + global-call-adapter-factories: + # ... +``` + +For each Java interface, you can also specify the `CallAdapter.Factory` used by the current interface through `@RetrofitClient.callAdapterFactories`. + +> Recommendation: configure `CallAdapter.Factory` as `Spring Bean` + +### Data Converter + +`Retrofit` uses `Converter` to convert the object annotated with `@Body` into `Request Body`, and `Response Body` into a `Java` object. You can choose the following `Converter`: + +- [Gson](https://github.com/google/gson): com.squareup.Retrofit:converter-gson +- [Jackson](https://github.com/FasterXML/jackson): com.squareup.Retrofit:converter-jackson +- [Moshi](https://github.com/square/moshi/): com.squareup.Retrofit:converter-moshi +- [Protobuf](https://developers.google.com/protocol-buffers/): com.squareup.Retrofit:converter-protobuf +- [Wire](https://github.com/square/wire): com.squareup.Retrofit:converter-wire +- [Simple XML](http://simple.sourceforge.net/): com.squareup.Retrofit:converter-simplexml +- [JAXB](https://docs.oracle.com/javase/tutorial/jaxb/intro/index.html): com.squareup.retrofit2:converter-jaxb +- fastJson:com.alibaba.fastjson.support.retrofit.Retrofit2ConverterFactory + +Configure the global `Converter.Factory` through `retrofit.global-converter-factories`, the default is `retrofit2.converter.jackson.JacksonConverterFactory`. + +If you need to modify the `Jackson` configuration, you can override the `bean` configuration of the `JacksonConverterFactory` by yourself. + +```yaml +retrofit: + # The `Converter.Factory` of the component extension has been built in, please do not repeat the configuration here + global-converter-factories: + - retrofit2.converter.jackson.JacksonConverterFactory +``` + +For each `Java` interface, you can also specify the `Converter.Factory` used by the current interface through `@RetrofitClient.converterFactories`. + +> Recommendation: Configure `Converter.Factory` as `Spring Bean`. + +### Meta-annotation + +Annotations such as `@RetrofitClient`, `@Retry`, `@Logging`, `@Resilience4jDegrade` support meta-annotations, inheritance, and `@AliasFor`. + +```java + +@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[] converterFactories() default {GsonConverterFactory.class}; + + @AliasFor(annotation = Logging.class, attribute = "logStrategy") + LogStrategy logStrategy() default LogStrategy.BODY; +} +``` + +## Other Examples + +### Form Parameter + +```java +@FormUrlEncoded +@POST("token/verify") + Object tokenVerify(@Field("source") String source,@Field("signature") String signature,@Field("token") String token); + + +@FormUrlEncoded +@POST("message") +CompletableFuture sendMessage(@FieldMap Map param); +``` + +### File Upload + +#### Create MultipartBody.Part + +```java +// 对文件名使用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 + +```java +@POST("upload") +@Multipart +Void upload(@Part MultipartBody.Part file); +``` + +### File Download + +#### `HTTP` Download Interface + +```java +@RetrofitClient(baseUrl = "https://img.ljcdn.com/hc-picture/") +public interface DownloadApi { + + @GET("{fileKey}") + Response download(@Path("fileKey") String fileKey); +} + +``` + +#### `HTTP` Download Using + +```java +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +public class DownloadTest { + @Autowired + DownloadApi downLoadApi; + + @Test + public void download() throws Exception { + String fileKey = "6302d742-ebc8-4649-95cf-62ccf57a1add"; + Response 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 URL + +Use the `@url` annotation to implement dynamic URLs. At this point, `baseUrl` can be configured with any legal url. For example: `http://github.com/` . The runtime will only make requests based on the `@Url` address. + +> Note: `@url` must be placed in the first position of the method parameter. In addition, on annotations such as `@GET` and `@POST`, there is no need to define the endpoint path. +> +```java + @GET + Map test3(@Url String url,@Query("name") String name); +``` + +### `DELETE` request adds request body + +```java +@HTTP(method = "DELETE", path = "/user/delete", hasBody = true) +``` + +### `GET` request adds request body + +`okhttp3` itself does not support the `GET` request to add a request body, the source code is as follows: + +![image](https://user-images.githubusercontent.com/30620547/108949806-0a9f7780-76a0-11eb-9eb4-326d5d546e98.png) + +![image](https://user-images.githubusercontent.com/30620547/108949831-1ab75700-76a0-11eb-955c-95d324084580.png) + +The author gives the specific reasons, you can refer to: [issue](https://github.com/square/okhttp/issues/3154) + +However, if you really need to do this, you can use: `@HTTP(method = "get", path = "/user/get", hasBody = true)`, Use lowercase `get` to bypass the above restrictions.