retrofit-plus

Introduction: retrofit-plus 是一款基于 retrofit2 实现的轻量级 httpClient 客户端工具,与 spring 和 spring-boot 项目深度集成。
More: Author   ReportBugs   
Tags:

License Maven Central License License Ask DeepWiki

English Document

retrofit支持将 HTTP API 转化成 JAVA 接口,本组件将 Retrofit 和 SpringBoot 深度整合,并支持了多种实用功能增强。

  • Spring Boot 3.x/4.x 项目,请使用 retrofit-spring-boot-starter 4.x
    • 由于 Spring Boot 4.x 默认使用 jackson3,但是本组件默认 converter 使用的是 jackson2,因此对于 Spring Boot 4.x 项目建议将全局 converter 设置为 jackson3
    • 配置方式retrofit.global-converter-factories=com.github.lianjiatech.retrofit.spring.boot.core.jackson3.Jackson3ConverterFactory
  • Spring Boot 1.x/2.x 项目,请使用retrofit-spring-boot-starter 2.x ,支持 Spring Boot 1.4.2 及以上版本。

🚀项目持续优化迭代,欢迎大家提 ISSUE 和 PR!麻烦大家能给一颗 star⭐️,您的 star 是我们持续更新的动力!

github 项目地址:https://github.com/LianjiaTech/retrofit-spring-boot-starter

gitee 项目地址:https://gitee.com/lianjiatech/retrofit-spring-boot-starter

快速开始

引入依赖

<dependency>
    <groupId>com.github.lianjiatech</groupId>
   <artifactId>retrofit-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

对于绝大部分 Spring-Boot 项目,引入依赖即可使用。如果引入依赖之后,组件无法正常工作,可尝试如下方案解决:

手动自动配置导入

有些场景下 RetrofitAutoConfiguration 可能无法正常加载执行,可以尝试手动配置导入,代码如下:


@Configuration
@ImportAutoConfiguration({RetrofitAutoConfiguration.class})
public class SpringBootAutoConfigBridge {
}

如果项目仍然采用 Spring XML 配置文件,需要在 XML 配置文件加上 SpringBoot 自动配置类。

<!-- 导入 SpringBoot 自动配置类 -->
<bean class="com.yourpackage.config.SpringBootAutoConfig"/>

定义 HTTP JAVA 接口

接口必须使用@RetrofitClient注解标记!

@RetrofitClient(baseUrl = "http://localhost:8080/api/user/")
public interface UserService {

   /**
    * 根据 id 查询用户姓名
    */
   @POST("getName")
   String getName(@Query("id") Long id);
}

注意:方法请求路径慎用/开头。对于Retrofit而言,如果baseUrl=http://localhost:8080/api/test/,方法请求路径如果是person,则该方法完整的请求路径是:http://localhost:8080/api/test/person。而方法请求路径如果是/person,则该方法完整的请求路径是:http://localhost:8080/person

注入使用

将接口注入到其它 Service 中即可使用!

@Service
public class BusinessService {

    @Autowired
    private UserService userService;

    public void doBusiness() {
       // call userService
    }
}

默认情况下,自动使用SpringBoot扫描路径进行RetrofitClient注册,你也可以在配置类加上@RetrofitScan手动指定扫描路径。

HTTP 请求相关注解

HTTP请求相关注解,全部使用了Retrofit原生注解,以下是一个简单说明:

注解分类 支持的注解
请求方式 @GET @HEAD @POST @PUT @DELETE @OPTIONS @HTTP
请求头 @Header @HeaderMap @Headers
Query 参数 @Query @QueryMap @QueryName
path 参数 @Path
form-encoded 参数 @Field @FieldMap @FormUrlEncoded
请求体 @Body
文件上传 @Multipart @Part @PartMap
url 参数 @Url

详细信息可参考官方文档:retrofit 官方文档

功能特性

HTTP 响应结果自动适配 JAVA 接口返回类型

本组件会将 HTTP 响应结果自动适配成 JAVA 接口定义的返回类型,目前支持以下几种返回类型:

  • Call<T>: 不执行适配处理,直接返回Call<T>对象
  • String:将Response Body适配成String返回。
    • 默认使用 JSON Converter 将Response Body的 bytes 转成 String,如果想直接得到Response Body转成的 String,可以指定Converter.Factorycom.github.lianjiatech.retrofit.spring.boot.core.StringConverterFactory
  • 基础类型(Long/Integer/Boolean/Float/Double):将Response Body适配成上述基础类型
  • CompletableFuture<T>: 将Response Body适配成CompletableFuture<T>对象返回
  • Void: 不关注返回类型可以使用Void
  • Response<T>: 将Response适配成Response<T>对象返回
  • Mono<T>: Project Reactor响应式返回类型
  • Single<T>Rxjava响应式返回类型(支持Rxjava2/Rxjava3
  • CompletableRxjava响应式返回类型,HTTP请求没有响应体(支持Rxjava2/Rxjava3
  • 任意POJO类型: 将Response Body适配成对应的POJO对象返回

适配实现方式

Retrofit底层是通过CallAdapterFactoryCall<T>对象适配成接口方法的返回值类型,本组件扩展了一些CallAdapterFactory实现:

  • BodyCallAdapterFactory
    • 同步执行HTTP请求,将响应体内容适配成方法的返回值类型。
    • 任意方法返回值类型都可以使用BodyCallAdapterFactory,优先级最低。
  • ResponseCallAdapterFactory
    • 同步执行HTTP请求,将响应体内容适配成Retrofit.Response<T>返回。
    • 只有方法返回值类型为Retrofit.Response<T>,才可以使用ResponseCallAdapterFactory
  • 响应式编程相关CallAdapterFactory

通过继承CallAdapter.Factory,可以实现任何方式的 HTTP 响应报文到 JAVA 接口返回类型的适配处理。 组件支持通过retrofit.global-call-adapter-factories配置全局调用适配器工厂:

retrofit:
  # 全局适配器工厂(组件扩展的`CallAdaptorFactory`工厂已经内置,这里请勿重复配置)
  global-call-adapter-factories:
    # ...

针对每个 JAVA 接口,还可以通过@RetrofitClient.callAdapterFactories指定当前接口采用的CallAdapter.Factory

自定义数据转换器

Retrofit使用Converter@Body注解的对象转换成 HTTP 请求体,将 HTTP 响应体转换成一个Java对象,支持以下几种Converter

  • Gson: com.squareup.Retrofit:converter-gson
  • Jackson: com.squareup.Retrofit:converter-jackson
  • jackson3: com.github.lianjiatech.retrofit.spring.boot.core.jackson3.Jackson3ConverterFactory
  • 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
  • JAXB: com.squareup.retrofit2:converter-jaxb
  • fastJson:com.alibaba.fastjson.support.retrofit.Retrofit2ConverterFactory

组件支持通过retrofit.global-converter-factories配置全局Converter.Factory,默认的是retrofit2.converter.jackson.JacksonConverterFactory

如果需要修改Jackson配置,自行覆盖JacksonConverterFactorybean配置即可。

retrofit:
   # 全局转换器工厂
   global-converter-factories:
      - retrofit2.converter.jackson.JacksonConverterFactory

针对每个Java接口,还可以通过@RetrofitClient.converterFactories指定当前接口采用的Converter.Factory

注意:如果接口返回原始结果就是 String 文本,且无法用 JSON 转换器转换,可以使用com.github.lianjiatech.retrofit.spring.boot.core.StringConverterFactory,该转换器会直接将结果转为 String 返回

自定义 OkHttpClient

对于 OkHttpClient 超时相关配置,可以通过配置文件或者@RetrofitClient设置。但是如果需要修改更灵活复杂的OkHttpClient配置,推荐通过自定义OkHttpClient来实现,步骤如下:

实现SourceOkHttpClientRegistrar接口

@Component
public class CustomOkHttpClientRegistrar implements SourceOkHttpClientRegistrar {

   @Override
   public void register(SourceOkHttpClientRegistry registry) {
      // 注册 customOkHttpClient,超时时间设置为 1s
      registry.register("customOkHttpClient", new OkHttpClient.Builder()
              .connectTimeout(Duration.ofSeconds(1))
              .writeTimeout(Duration.ofSeconds(1))
              .readTimeout(Duration.ofSeconds(1))
              .addInterceptor(chain -> chain.proceed(chain.request()))
              .build());
   }
}

通过@RetrofitClient.sourceOkHttpClient指定当前接口要使用的OkHttpClient

@RetrofitClient(baseUrl = "${test.baseUrl}", sourceOkHttpClient = "customOkHttpClient")
public interface CustomOkHttpUserService {

   /**
    * 根据 id 查询用户信息
    */
   @GET("getUser")
   User getUser(@Query("id") Long id);
}

日志打印

组件支持支持全局日志打印和声明式日志打印。

全局日志打印

默认情况下,全局日志打印是关闭的(enable=false),需要主动开启。开启后默认按 BASIC 策略仅打印请求/响应行(含状态码与耗时),开销可忽略。默认配置如下:

retrofit:
   # 全局日志打印配置
   global-log:
      # 启用日志打印(默认 false,开箱即用不打印日志)
      enable: false
      # 全局日志打印级别
      log-level: info
      # 全局日志打印策略(默认 BASIC,仅打印请求/响应行)
      log-strategy: basic
      # 是否聚合打印请求日志
      aggregate: true
      # 日志名称,默认为{@link LoggingInterceptor} 的全类名
      logName: com.github.lianjiatech.retrofit.spring.boot.log.LoggingInterceptor
      # 日志中需要隐藏的敏感请求头
      # 默认遮蔽:Authorization、Proxy-Authorization、Cookie、Set-Cookie
      # 注意:用户配置该项会整体覆盖默认值,需自行包含仍要遮蔽的项
      redact-headers:
        - Authorization
        - Proxy-Authorization
        - Cookie
        - Set-Cookie

四种日志打印策略含义如下:

  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).

声明式日志打印

如果只需要部分请求才打印日志,可以在相关接口或者方法上使用@Logging注解。

日志打印自定义扩展

如果需要修改日志打印行为,可以继承LoggingInterceptor,并将其配置成Spring bean

请求重试

组件支持支持全局重试和声明式重试。

全局重试

全局重试默认关闭,默认配置项如下:

retrofit:
  # 全局重试配置
  global-retry:
     # 是否启用全局重试
     enable: false
     # 全局重试基础间隔时间(毫秒)
     interval-ms: 100
     # 全局最大重试次数
     max-retries: 2
     # 退避策略:FIXED(固定间隔,默认)/ EXPONENTIAL(指数退避)
     backoff-strategy: fixed
     # 指数退避间隔上限(毫秒),仅 EXPONENTIAL 生效
     max-interval-ms: 30000
     # 抖动系数 [0.0, 1.0],0.0 表示无抖动
     jitter: 0.0
     # 全局重试规则
     retry-rules:
        - response_status_not_2xx
        - occur_io_exception

重试规则支持三种配置:

  1. RESPONSE_STATUS_NOT_2XX:响应状态码不是2xx时执行重试
  2. OCCUR_IO_EXCEPTION:发生 IO 异常时执行重试
  3. OCCUR_EXCEPTION:发生任意异常时执行重试

退避策略与抖动

backoffStrategy 控制重试间隔的增长方式,默认 FIXED 与历史行为一致:

  • FIXED:每次重试间隔固定为 intervalMs
  • EXPONENTIAL:指数退避,第 N 次重试间隔 = intervalMs * 2^N(N 从 0 起),并以 maxIntervalMs 封顶,避免间隔无限增长。

jitter(取值 [0.0, 1.0],默认 0.0 无抖动)用于在计算延迟上叠加随机抖动,避免多客户端同步重试导致的惊群效应:

实际延迟 = 计算延迟 × (1 + jitter × random),其中 random 为 [0, 1) 的随机数。

条件触发:按状态码 / 异常类型

RetryRule 粗粒度规则的基础上,可进一步收窄触发条件(默认空,与历史行为一致):

  • retryStatusCodes:仅在响应状态码命中列表时才重试(需配合 RESPONSE_STATUS_NOT_2XX 规则)。例如 {502, 503, 504}
  • retryExceptionClasses:仅在异常类型命中列表时才重试(在匹配 RetryRule 的异常基础上进一步收窄)。例如 {SocketTimeoutException.class}
@RetrofitClient(baseUrl = "http://localhost:8080/")
@Retry(maxRetries = 3, intervalMs = 200, backoffStrategy = BackoffStrategy.EXPONENTIAL,
        maxIntervalMs = 5000, jitter = 0.3, retryStatusCodes = {502, 503, 504})
public interface Api {
    @GET("getUser")
    User getUser(@Query("id") Long id);
}

声明式重试

如果只有一部分请求需要重试,可以在相应的接口或者方法上使用@Retry注解。

请求重试自定义扩展

如果需要修改请求重试行为,可以继承RetryInterceptor,并将其配置成Spring bean

全局应用拦截器

如果我们需要对整个系统的的HTTP请求执行统一的拦截处理,可以实现全局拦截器GlobalInterceptor, 并配置成spring Bean

@Component
public class MyGlobalInterceptor implements GlobalInterceptor {
   @Override
   public Response intercept(Chain chain) throws IOException {
      Response response = chain.proceed(chain.request());
      // response 的 Header 加上 global
      return response.newBuilder().header("global", "true").build();
   }
}

全局网络拦截器

实现NetworkInterceptor接口,并配置成spring Bean

注解式 url 路径匹配拦截器

很多场景下,我们需要仅针对某些 HTTP 接口做一些特殊逻辑,此时可以使用 url 路径匹配拦截器,优雅实现该功能,使用的步骤如下:

继承BasePathMatchInterceptor编写拦截处理器

@Component
public class PathMatchInterceptor extends BasePathMatchInterceptor {
   @Override
   protected Response doIntercept(Chain chain) throws IOException {
      Response response = chain.proceed(chain.request());
      // response 的 Header 加上 path.match
      return response.newBuilder().header("path.match", "true").build();
   }
}

接口上使用@Intercept进行标注

@RetrofitClient(baseUrl = "${test.baseUrl}")
@Intercept(handler = PathMatchInterceptor.class, include = {"/api/user/**"}, exclude = "/api/user/getUser")
// @Intercept() 如果需要使用多个路径匹配拦截器,继续添加@Intercept 即可
public interface InterceptorUserService {

   /**
    * 根据 id 查询用户姓名
    */
   @POST("getName")
   Response<String> getName(@Query("id") Long id);

   /**
    * 根据 id 查询用户信息
    */
   @GET("getUser")
   Response<User> getUser(@Query("id") Long id);

}

上面的@Intercept配置表示:拦截InterceptorUserService接口下/api/user/**路径下(排除/api/user/getUser)的请求,拦截处理器使用PathMatchInterceptor。如果需要使用多个拦截器,在接口上标注多个@Intercept注解即可。

自定义拦截器注解

有的时候,我们需要在"拦截注解"动态传入一些参数,然后在拦截的时候使用这些参数。 这时候,我们可以使用"自定义拦截注解",步骤如下:

  1. 自定义注解。必须使用@InterceptMark标记,并且注解中必须包括include、exclude、handler字段。
  2. 继承BasePathMatchInterceptor编写拦截处理器
  3. 接口上使用自定义注解

例如,我们需要"在请求头里面动态加入accessKeyIdaccessKeySecret签名信息才能再发起 HTTP 请求",这时候可以自定义@Sign注解来实现。

自定义@Sign注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
    
    String accessKeyId();

    String accessKeySecret();

    String[] include() default {"/**"};

    String[] exclude() default {};

    Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}

@Sign注解中指定了使用的拦截器是SignInterceptor

实现SignInterceptor

@Component
@Setter
public class SignInterceptor extends BasePathMatchInterceptor {

   private String accessKeyId;

   private String accessKeySecret;

   @Override
   public Response doIntercept(Chain chain) throws IOException {
      Request request = chain.request();
      Request newReq = request.newBuilder()
              .addHeader("accessKeyId", accessKeyId)
              .addHeader("accessKeySecret", accessKeySecret)
              .build();
      Response response = chain.proceed(newReq);
      return response.newBuilder().addHeader("accessKeyId", accessKeyId)
              .addHeader("accessKeySecret", accessKeySecret).build();
   }
}

注意:accessKeyIdaccessKeySecret字段必须提供setter方法。

拦截器的accessKeyIdaccessKeySecret字段值会依据@Sign注解的accessKeyId()accessKeySecret()值自动注入,如果@Sign指定的是占位符形式的字符串,则会取配置属性值进行注入。

接口上使用@Sign

@RetrofitClient(baseUrl = "${test.baseUrl}")
@Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", include = "/api/user/getAll")
public interface InterceptorUserService {

   /**
    * 查询所有用户信息
    */
   @GET("getAll")
   Response<List<User>> getAll();

}

熔断降级

熔断降级默认关闭,当前支持sentinelresilience4j两种实现。

retrofit:
   # 熔断降级配置
   degrade:
      # 熔断降级类型。默认 none,表示不启用熔断降级
      degrade-type: sentinel

Sentinel

  1. 手动引入Sentinel依赖

    
    <dependency>
       <groupId>com.alibaba.csp</groupId>
       <artifactId>sentinel-core</artifactId>
       <version>1.8.6</version>
    </dependency>
    
  2. 配置degrade-type=sentinel开启,然后在相关接口或者方法上声明@SentinelDegrade注解即可,例如:

     @RetrofitClient(baseUrl = "${test.baseUrl}", fallback = SentinelFallbackUserService.class, connectTimeoutMs = 1,
         readTimeoutMs = 1, writeTimeoutMs = 1)
     @SentinelDegrade(rules = {@SentinelDegradeRule(grade = 0, count = 100, timeWindow = 4),
     @SentinelDegradeRule(grade = 1, count = 0.01, timeWindow = 3)})
     public interface SentinelUserService {
     
         /**
          * 根据 id 查询用户姓名
          */
         @POST("getName")
         String getName(@Query("id") Long id);
     
         /**
          * 根据 id 查询用户信息
          */
         @GET("getUser")
         @SentinelDegrade(rules = {@SentinelDegradeRule(grade = 2, count = 1, timeWindow = 6)})
         User getUser(@Query("id") Long id);
     
     }
    
  3. 此外还支持全局Sentinel熔断降级:

    retrofit:
      # 全局 sentinel 降级配置
      global-sentinel-degrade:
        # 是否开启
        enable: true
        rules:
          # 降级策略(0:平均响应时间;1:异常比例;2:异常数量)
          - grade: 0
            # 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N)
            count: 1000,
            # 熔断时长,单位为 s
            time-window: 5
            # (在有效统计时间范围内)能够触发熔断的最小请求数
            min-request-amount: 5
            # RT 模式下慢请求率的阈值
            slow-ratio-threshold: 1.0
            # 时间间隔统计持续时间,单位为毫秒
            stat-interval-ms: 1000
    

Resilience4j

  1. 手动引入Resilience4j依赖:

    
    <dependency>
       <groupId>io.github.resilience4j</groupId>
       <artifactId>resilience4j-circuitbreaker</artifactId>
       <version>1.7.1</version>
    </dependency>
    
  2. 注册自定义熔断配置:实现CircuitBreakerConfigRegistrar接口,注册CircuitBreakerConfig

    @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());
       }
    }
    
  3. 配置degrade-type=resilience4j开启。然后在相关接口或者方法上声明@Resilience4jDegrade即可,例如

    @RetrofitClient(baseUrl = "${test.baseUrl}", fallbackFactory = Resilience4jFallbackFactory.class, connectTimeoutMs = 1,
            readTimeoutMs = 1, writeTimeoutMs = 1)
    @Resilience4jDegrade(circuitBreakerConfigName = "testCircuitBreakerConfig")
    public interface Resilience4jUserService {
    
        /**
         * 根据 id 查询用户姓名
         */
        @POST("getName")
        String getName(@Query("id") Long id);
    
        /**
         * 根据 id 查询用户信息
         */
        @GET("getUser")
        @Resilience4jDegrade(enable = false)
        User getUser(@Query("id") Long id);
    
    }
    
  4. 通过以下配置可开启全局 resilience4j 熔断降级:

    retrofit:
       # 熔断降级配置
       degrade:
          # 熔断降级类型。默认 none,表示不启用熔断降级
          degrade-type: resilience4j
          # 全局 resilience4j 降级配置
          global-resilience4j-degrade:
             # 是否开启
             enable: true
             # 根据该名称从#{@link CircuitBreakerConfigRegistry}获取 CircuitBreakerConfig,作为全局熔断配置
             circuit-breaker-config-name: defaultCircuitBreakerConfig
    

通过circuitBreakerConfigName指定CircuitBreakerConfig。包括retrofit.degrade.global-resilience4j-degrade.circuit-breaker-config-name或者@Resilience4jDegrade.circuitBreakerConfigName

扩展熔断降级

如果用户需要使用其他的熔断降级实现,继承BaseRetrofitDegrade,并将其配置Spring Bean

配置 fallback 或者 fallbackFactory (可选)

如果@RetrofitClient不设置fallback或者fallbackFactory,当触发熔断时,会直接抛出RetrofitBlockException异常。 用户可以通过设置fallback或者fallbackFactory来定制熔断时的方法返回值。

注意:fallback类必须是当前接口的实现类,fallbackFactory必须是FallbackFactory<T> 实现类,泛型参数类型为当前接口类型。另外,fallbackfallbackFactory实例必须配置成Spring Bean

fallbackFactory相对于fallback,主要差别在于能够感知每次熔断的异常原因(cause),参考示例如下:

@Slf4j
@Service
public class HttpDegradeFallback implements HttpDegradeApi {

   @Override
   public Result<Integer> test() {
      Result<Integer> fallback = new Result<>();
      fallback.setCode(100)
              .setMsg("fallback")
              .setBody(1000000);
      return fallback;
   }
}
@Slf4j
@Service
public class HttpDegradeFallbackFactory implements FallbackFactory<HttpDegradeApi> {

   @Override
   public HttpDegradeApi create(Throwable cause) {
      log.error("触发熔断了! ", cause.getMessage(), cause);
      return new HttpDegradeApi() {
         @Override
         public Result<Integer> test() {
            Result<Integer> fallback = new Result<>();
            fallback.setCode(100)
                    .setMsg("fallback")
                    .setBody(1000000);
            return fallback;
         }
      };
   }
}

错误解码器

HTTP发生请求错误(包括发生异常或者响应数据不符合预期)的时候,错误解码器可将HTTP相关信息解码到自定义异常中。你可以在@RetrofitClient注解的errorDecoder() 指定当前接口的错误解码器,自定义错误解码器需要实现ErrorDecoder接口。 可以通过配置retrofit.enable-error-decoder=false配置关闭 ErrorDecoder 功能。

指标监控(Micrometer)

组件内置了基于 Micrometer 的指标采集能力。默认关闭,需要显式设置 retrofit.metrics.enable=true 才会启用。

为什么是默认关闭、显式开启:Spring Boot autoconfig 之间没有可靠的加载顺序约束,依赖 @ConditionalOnBean(MeterRegistry.class) 自动启用会因求值时机问题导致"用户引入 actuator 却没有指标"的隐性失败。改为 opt-in 后行为完全可预期:用户引入 actuator 不会被自动埋点;显式开启时若容器内没有 MeterRegistry,启动会快速失败而非静默无指标。

启用方式

  1. 引入 Micrometer 与对应的监控后端(Prometheus / Datadog / Atlas 等)。Spring Boot Actuator 会注册 MeterRegistry
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
  1. 在配置中显式开启:
retrofit:
  metrics:
    enable: true

采集的指标

指标名 类型 含义
retrofit.client.requests Timer 每次 HTTP 调用耗时分布(含分位数与 SLO 直方图)
retrofit.client.requests.active LongTaskTimer 进行中的请求数与最长存活时间
retrofit.client.errors Counter 请求异常计数(按 exception 类名维度)

标签维度

默认 tag(基数有界,可放心用于 Prometheus 等高基数敏感后端):

Tag 含义 取值示例
client Retrofit 接口的简单类名 UserService
method Java 方法名 getUser
http.method HTTP 方法 GET/POST
uri 注解上的路径模板(不展开 @Path user/{id}
status 状态码桶 2xx/3xx/4xx/5xx/IO_ERROR
outcome 业务结果 SUCCESS/CLIENT_ERROR/SERVER_ERROR/IO_ERROR
exception 仅 errors 指标,异常类名 SocketTimeoutException

注意:tag 取值必须是有界集合,因此 uri 标签使用注解上的路径模板(含 {id} 占位符),而非展开后的实际 URL。这样可以避免动态路径参数导致的指标基数爆炸。

配置项

retrofit:
  metrics:
    # 是否启用,默认 false。需要显式设置为 true 才会装配 metrics 拦截器。
    enable: true
    # Timer 发布的分位数;空数组表示不发布
    percentiles: [0.5, 0.95, 0.99]
    # SLO 直方图分桶;空数组表示不发布直方图
    sla:
      - 50ms
      - 100ms
      - 300ms
      - 1s
      - 3s
    tags:
      # 是否带 host 标签,默认关闭(动态 baseUrl 场景下 host 数量可能很大)
      host: false
      # 是否带 uri 标签,默认开启
      uri: true
    # 全局静态附加标签
    extra-tags:
      app: my-service
      env: prod
    # 指标名前缀,默认 retrofit.client
    metric-name-prefix: retrofit.client

自定义标签

如果默认的 tag 维度不满足需求,可以实现 RetrofitTagsProvider 接口并注册为 Spring Bean,将自动覆盖默认实现:

@Component
public class TenantAwareTagsProvider implements RetrofitTagsProvider {

    private final RetrofitTagsProvider delegate;

    public TenantAwareTagsProvider(MetricsProperty property) {
        this.delegate = new DefaultRetrofitTagsProvider(property);
    }

    @Override
    public Tags getTags(Request request, Response response, Throwable exception) {
        return delegate.getTags(request, response, exception)
                .and("tenant", TenantContext.current());
    }
}

自定义实现时务必保证:tag 取值集合有界、tag 顺序与名称稳定,否则会导致 Micrometer 创建多个无意义的 Meter,造成内存浪费。

Actuator Endpoint(暴露 RetrofitClient 元信息)

组件提供了一个基于 Spring Boot Actuator 的只读 Endpoint,通过 /actuator/retrofit 暴露应用中所有 @RetrofitClient 接口的完整配置元信息,便于排查"某个接口实际生效的 baseUrl / 超时 / 日志 / 重试 / 熔断配置到底是什么"。

可选依赖、按需启用:仅当用户引入 actuator 时该 Endpoint 才会装配(@ConditionalOnClass),未引入 actuator 的 SpringBoot 3 项目不受任何影响、正常启动。Endpoint 的暴露与开关完全交给 Spring Boot 标准的 management 配置(@ConditionalOnAvailableEndpoint),不自造开关。

启用方式

  1. 引入 actuator:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 暴露 retrofit endpoint(默认 actuator 仅暴露 health,需显式加入):
management:
  endpoints:
    web:
      exposure:
        include: health,retrofit

访问方式

请求 说明
GET /actuator/retrofit 列出所有 client + global 全局配置段 + count
GET /actuator/retrofit/{接口全限定名} 按接口全限定名查询单个 client,未匹配返回 404

响应结构示例

{
  "count": 2,
  "global": {
    "enableErrorDecoder": true,
    "globalConverterFactories": ["retrofit2.converter.jackson.JacksonConverterFactory"],
    "timeout": { "connectMs": 10000, "readMs": 10000, "writeMs": 10000, "callMs": 0 },
    "connectionPool": { "maxIdleConnections": 5, "keepAliveDurationMs": 300000 },
    "log":     { "enable": false, "logLevel": "INFO", "logStrategy": "BASIC", "aggregate": true },
    "retry":   { "enable": false, "maxRetries": 2, "intervalMs": 100,
                 "backoffStrategy": "FIXED", "maxIntervalMs": 30000, "jitter": 0.0,
                 "retryStatusCodes": [], "retryExceptionClasses": [],
                 "retryRules": ["RESPONSE_STATUS_NOT_2XX", "OCCUR_IO_EXCEPTION"] },
    "degrade": { "degradeType": "none",
                 "sentinel":     { "enable": false, "ruleCount": 0 },
                 "resilience4j": { "enable": false, "circuitBreakerConfigName": "defaultCircuitBreakerConfig" } },
    "metrics": { "enable": false, "metricNamePrefix": "retrofit.client", "tagHost": false, "tagUri": true }
  },
  "clients": [{
    "beanName": "userService",
    "interfaceName": "com.example.UserService",
    "baseUrl": "${test.baseUrl}",
    "resolvedBaseUrl": "http://localhost:8080/api/user/",
    "serviceId": null,
    "path": null,
    "converterFactories": [],
    "callAdapterFactories": [],
    "fallback": null,
    "fallbackFactory": null,
    "errorDecoder": "com.github.lianjiatech.retrofit.spring.boot.core.ErrorDecoder$DefaultErrorDecoder",
    "validateEagerly": false,
    "sourceOkHttpClient": null,
    "timeoutEffective": true,
    "timeout": { "connectMs": 3000, "readMs": 3000, "writeMs": 10000, "callMs": 0,
                 "inheritedFields": ["writeMs", "callMs"] },
    "pool":    { "maxIdleConnections": 5, "keepAliveDurationMs": 300000,
                 "inheritedFields": ["maxIdleConnections", "keepAliveDurationMs"] },
    "logging": { "source": "interface", "enable": true, "logLevel": "DEBUG",
                 "logStrategy": "BODY", "aggregate": true },
    "retry":   { "source": "global" },
    "degrade": { "enabled": false, "type": "none" }
  }]
}

字段语义说明

  • resolvedBaseUrl:已解析的最终 baseUrl。仅当该接口已被注入使用(触发过实例化)时才有值,否则为 null(baseUrl 为懒解析,未触发时不预先解析)。
  • timeout / poolinheritedFields@RetrofitClient 上对应字段配置为 -1(默认值)时表示"复用全局配置"。Endpoint 会按与真实构建一致的规则把 -1 解析为全局兜底值,并把这些字段名记入 inheritedFields,便于区分"接口显式配置"还是"继承全局"。
  • timeoutEffective:当接口通过 sourceOkHttpClient 指定了自定义 OkHttpClient 时为 false(此时超时/连接池由源客户端决定,timeout/pool 不展示)。
  • logging / retrysource
    • "interface":接口上存在 @Logging / @Retry 注解,其余字段为注解展开值;
    • "global":接口无对应注解、运行时回落到全局配置,此时不重复展开值,请查阅顶层 global 段。
    • 注意:方法级 @Logging / @Retry 不在此处下钻展示(运行时方法注解优先于接口、接口优先于全局)。
  • degrade.enabled:取自 RetrofitDegrade.isEnableDegrade(接口)type 为全局 degrade.degrade-typenone / sentinel / resilience4j)。
  • fallback / fallbackFactory:未配置(默认 void.class)时为 null

GraalVM Native Image / AOT 支持

组件已内置 Spring AOT 支持,在 Spring Boot 3.x / 4.x 下编译为 GraalVM Native Image 时开箱即用,无需手写 reflect-config.json / proxy-config.json

构建期(spring-boot:process-aot 或 native 编译)会自动为每个 @RetrofitClient 接口注册:

  • JDK 动态代理Retrofit.create(接口) 与熔断降级代理都基于接口生成 JDK 代理;
  • 接口反射:方法签名与参数注解需在 native 下反射可见,供 Retrofit 解析 HTTP 请求;
  • 注解引用类的反射构造@RetrofitClient 上的 baseUrlParser / converterFactories / callAdapterFactories / errorDecoder / fallback / fallbackFactory,以及 @InterceptMark(含 @Intercept / @Sign)注解的 handler 拦截器类,运行期可能通过反射创建并注入属性;
  • Actuator 值对象序列化/actuator/retrofit 返回结果的反射序列化。

该能力由 RetrofitAotProcessorBeanFactoryInitializationAotProcessor)实现,仅在 AOT 构建期生效,普通 JVM 启动与 native 运行期不执行任何逻辑,对功能与性能零影响。

若你自定义的 Converter.Factory / CallAdapter.Factory / ErrorDecoder 等会被 JSON 序列化为复杂业务实体,业务实体本身的 native 反射 hints 仍需按 Spring 标准方式(如 @RegisterReflectionForBinding)声明——这与具体业务模型相关,不在组件职责范围内。

微服务之间的 HTTP 调用

继承ServiceInstanceChooser

用户可以自行实现ServiceInstanceChooser接口,完成服务实例的选取逻辑,并将其配置成Spring Bean。对于Spring Cloud 应用,可以使用如下实现。

@Service
public class SpringCloudServiceInstanceChooser implements ServiceInstanceChooser {
    
   private LoadBalancerClient loadBalancerClient;

   @Autowired
   public SpringCloudServiceInstanceChooser(LoadBalancerClient loadBalancerClient) {
      this.loadBalancerClient = loadBalancerClient;
   }

   /**
    * 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
    */
   @Override
   public URI choose(String serviceId) {
      ServiceInstance serviceInstance = loadBalancerClient.choose(serviceId);
      Assert.notNull(serviceInstance, "can not found service instance! serviceId=" + serviceId);
      return serviceInstance.getUri();
   }
}

指定serviceIdpath

@RetrofitClient(serviceId = "user", path = "/api/user")
public interface ChooserOkHttpUserService {

   /**
    * 根据 id 查询用户信息
    */
   @GET("getUser")
   User getUser(@Query("id") Long id);
}

自定义 RetrofitClient 注解

有些时候,JAVA 接口上的@RetrofitClient@Retry@Logging@Resilience4jDegrade等注解上的默认值不符合业务需要。 此时一种方式是每个接口都修改对应注解属性,但是会导致很多接口都要做相同的逻辑,不够优雅。 另外一种方式就是自定义 RetrofitClient 注解,后续其他接口只需要使用自定义注解即可。

比如下面代码定义了自定义注解@MyRetrofitClient


@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<? extends Converter.Factory>[] converterFactories() default {GsonConverterFactory.class};

   @AliasFor(annotation = Logging.class, attribute = "logStrategy")
   LogStrategy logStrategy() default LogStrategy.BODY;
}

配置属性

组件支持了多个可配置的属性,用来应对不同的业务场景。具体可支持的配置属性及默认值如下:

retrofit:
   # 全局转换器工厂
   global-converter-factories:
      - retrofit2.converter.jackson.JacksonConverterFactory

   # 全局日志打印配置
   global-log:
      # 启用日志打印(默认 false)
      enable: false
      # 全局日志打印级别
      log-level: info
      # 全局日志打印策略(默认 BASIC)
      log-strategy: basic
      # 是否聚合打印请求日志
      aggregate: true
      # 日志中需要隐藏的敏感请求头(默认遮蔽 Authorization/Proxy-Authorization/Cookie/Set-Cookie)
      redact-headers:
        - Authorization
        - Proxy-Authorization
        - Cookie
        - Set-Cookie

   # 全局重试配置
   global-retry:
      # 是否启用全局重试
      enable: false
      # 全局重试基础间隔时间(毫秒)
      interval-ms: 100
      # 全局最大重试次数
      max-retries: 2
      # 退避策略:FIXED(固定间隔,默认)/ EXPONENTIAL(指数退避)
      backoff-strategy: fixed
      # 指数退避间隔上限(毫秒),仅 EXPONENTIAL 生效
      max-interval-ms: 30000
      # 抖动系数 [0.0, 1.0],0.0 表示无抖动
      jitter: 0.0
      # 全局重试规则
      retry-rules:
         - response_status_not_2xx
         - occur_io_exception

   # 全局超时时间配置
   global-timeout:
      # 全局读取超时时间
      read-timeout-ms: 10000
      # 全局写入超时时间
      write-timeout-ms: 10000
      # 全局连接超时时间
      connect-timeout-ms: 10000
      # 全局完整调用超时时间
      call-timeout-ms: 0

  # 全局连接池配置
   global-connection-pool:
     # 最大空闲连接数
     max-idle-connections: 5
     keep-alive-duration-ms: 300_000

   # 指标监控配置(默认关闭;需要显式 enable=true 才会装配,且容器内必须有 MeterRegistry)
   metrics:
      # 是否启用,默认 false
      enable: false
      # Timer 分位数
      percentiles: [0.5, 0.95, 0.99]
      # SLO 直方图分桶
      sla:
         - 50ms
         - 100ms
         - 300ms
         - 1s
         - 3s
      tags:
         # 是否带 host 标签
         host: false
         # 是否带 uri 标签
         uri: true
      # 全局附加标签
      extra-tags:
         app: my-service
      # 指标名前缀
      metric-name-prefix: retrofit.client

   # 熔断降级配置
   degrade:
      # 熔断降级类型。默认 none,表示不启用熔断降级
      degrade-type: none
      # 全局 sentinel 降级配置
      global-sentinel-degrade:
         # 是否开启
         enable: false
         rules:
         # 降级策略(0:平均响应时间;1:异常比例;2:异常数量)
         - grade: 0
           # 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N)
           count: 1000,
           # 熔断时长,单位为 s
           time-window: 5
           # (在有效统计时间范围内)能够触发熔断的最小请求数
           min-request-amount: 5
           # RT 模式下慢请求率的阈值
           slow-ratio-threshold: 1.0
           # 时间间隔统计持续时间,单位为毫秒
           stat-interval-ms: 1000

      # 全局 resilience4j 降级配置
      global-resilience4j-degrade:
         # 是否开启
         enable: false
         # 根据该名称从#{@link CircuitBreakerConfigRegistry}获取 CircuitBreakerConfig,作为全局熔断配置
         circuit-breaker-config-name: defaultCircuitBreakerConfig
   # 自动设置 PathMathInterceptor 的 scope 为 prototype
   auto-set-prototype-scope-for-path-math-interceptor: true
   # 是否开启 ErrorDecoder 功能
   enable-error-decoder: true

绝大部分场景下,在 Spring Boot 配置文件(application.yml 或者 application.properties)中加上上述配置,即可自定义修改组件功能。

如果 Spring Boot 配置文件无法生效,可以手动配置 RetrofitProperties Bean,代码如下:

@Bean
public RetrofitProperties retrofitProperties() {
   RetrofitProperties retrofitProperties = new RetrofitProperties();
   // 手动修改 retrofitProperties 各项配置值
   return retrofitProperties;
}

其他功能示例

form 参数

@FormUrlEncoded
@POST("token/verify")
Object tokenVerify(@Field("source") String source,@Field("signature") String signature,@Field("token") String token);


@FormUrlEncoded
@POST("message")
CompletableFuture<Object> sendMessage(@FieldMap Map<String, Object> param);

文件上传

创建 MultipartBody.Part

// 对文件名使用 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上传接口

@POST("upload")
@Multipart
Void upload(@Part MultipartBody.Part file);

文件下载

HTTP下载接口

@RetrofitClient(baseUrl = "https://img.ljcdn.com/hc-picture/")
public interface DownloadApi {

    @GET("{fileKey}")
    Response<ResponseBody> download(@Path("fileKey") String fileKey);
}

HTTP下载使用

@SpringBootTest(classes = {RetrofitBootApplication.class})
@RunWith(SpringRunner.class)
public class DownloadTest {
    @Autowired
    DownloadApi downLoadApi;

    @Test
    public void download() throws Exception {
        String fileKey = "6302d742-ebc8-4649-95cf-62ccf57a1add";
        Response<ResponseBody> 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();
    }
}

动态 URL

使用@url注解可实现动态 URL。此时,baseUrl配置任意合法 url 即可。例如: http://github.com/ 。运行时只会根据@Url地址发起请求。

注意:@url必须放在方法参数的第一个位置,另外,@GET@POST等注解上,不需要定义端点路径。

 @GET
 Map<String, Object> test3(@Url String url,@Query("name") String name);

DELETE请求添加请求体

@HTTP(method = "DELETE", path = "/user/delete", hasBody = true)

GET请求添加请求体

okhttp3自身不支持GET请求添加请求体,源码如下:

image

image

作者给出了具体原因,可以参考: issue

但是,如果实在需要这么做,可以使用:@HTTP(method = "get", path = "/user/get", hasBody = true),使用小写get绕过上述限制。

反馈建议

如有任何问题,欢迎提 issue 或者加 QQ 群反馈。

群号:806714302

QQ 群图片

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools
AI Daily Digest