Giter Site home page Giter Site logo

lianjiatech / retrofit-spring-boot-starter Goto Github PK

View Code? Open in Web Editor NEW
1.7K 25.0 329.0 981 KB

A spring-boot starter for retrofit, supports rapid integration and feature enhancements.(适用于retrofit的spring-boot-starter,支持快速集成和功能增强)

License: Apache License 2.0

Java 100.00%
java retrofit http okhttp3 spring-boot

retrofit-spring-boot-starter's Introduction

retrofit-spring-boot-starter

License Build Status Maven central GitHub release License License Author QQ-Group

English Document

适用于retrofit的spring-boot-starter,支持快速集成和功能增强

  1. Spring Boot 3.x 项目,请使用retrofit-spring-boot-starter 3.x
  2. Spring Boot 1.x/2.x 项目,请使用retrofit-spring-boot-starter 2.x

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

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

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

示例demo:https://github.com/ismart-yuxi/retrofit-spring-boot-demo

感谢@ismart-yuxi为本项目写的示例demo

功能特性

快速开始

引入依赖

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

如果启动失败,大概率是依赖冲突,烦请引入或者排除相关依赖

定义HTTP接口

接口必须使用@RetrofitClient注解标记!HTTP相关注解可参考官方文档:retrofit官方文档

@RetrofitClient(baseUrl = "${test.baseUrl}")
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官方文档

配置属性

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

注意:应用只需要配置要更改的配置项!

retrofit:
   # 全局转换器工厂
   global-converter-factories:
      - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
      - retrofit2.converter.jackson.JacksonConverterFactory
   # 全局调用适配器工厂(组件扩展的调用适配器工厂已经内置,这里请勿重复配置)
   global-call-adapter-factories:

   # 全局日志打印配置
   global-log:
      # 启用日志打印
      enable: true
      # 全局日志打印级别
      log-level: info
      # 全局日志打印策略
      log-strategy: basic
      # 是否聚合打印请求日志
      aggregate: true

   # 全局重试配置
   global-retry:
      # 是否启用全局重试
      enable: false
      # 全局重试间隔时间
      interval-ms: 100
      # 全局最大重试次数
      max-retries: 2
      # 全局重试规则
      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

   # 熔断降级配置
   degrade:
      # 熔断降级类型。默认none,表示不启用熔断降级
      degrade-type: none
      # 全局sentinel降级配置
      global-sentinel-degrade:
         # 是否开启
         enable: false
         # 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N)
         count: 1000
         # 熔断时长,单位为 s
         time-window: 5
         # 降级策略(0:平均响应时间;1:异常比例;2:异常数量)
         grade: 0

      # 全局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

高级功能

超时时间配置

如果仅仅需要修改OkHttpClient的超时时间,可以通过@RetrofitClient相关字段修改,或者全局超时配置修改。

自定义OkHttpClient

如果需要修改OkHttpClient其它配置,可以通过自定义OkHttpClient来实现,步骤如下:

实现SourceOkHttpClientRegistrar接口,调用SourceOkHttpClientRegistry#register()方法注册OkHttpClient

@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);
}

注意:组件不会直接使用指定的OkHttpClient,而是基于该OkHttpClient创建一个新的。

注解式拦截器

组件提供了注解式拦截器,支持基于url路径匹配拦截,使用的步骤如下:

  1. 继承BasePathMatchInterceptor
  2. 使用@Intercept注解指定要使用的拦截器

如果需要使用多个拦截器,在接口上标注多个@Intercept注解即可。

继承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();
   }
}

默认情况下,组件会自动将BasePathMatchInterceptorscope设置为prototype。 可通过retrofit.auto-set-prototype-scope-for-path-math-interceptor=false关闭该功能。关闭之后,需要手动将scope设置为prototype

@Component
@Scope("prototype")
public class PathMatchInterceptor extends BasePathMatchInterceptor {
    
}

接口上使用@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

自定义拦截注解

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

  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();

}

日志打印

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

全局日志打印

默认情况下,全局日志打印是开启的,默认配置如下:

retrofit:
   # 全局日志打印配置
   global-log:
      # 启用日志打印
      enable: true
      # 全局日志打印级别
      log-level: info
      # 全局日志打印策略
      log-strategy: basic
      # 是否聚合打印请求日志
      aggregate: true

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

  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
     # 全局重试规则
     retry-rules:
        - response_status_not_2xx
        - occur_io_exception

重试规则支持三种配置:

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

声明式重试

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

请求重试自定义扩展

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

熔断降级

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

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

Sentinel

配置degrade-type=sentinel开启,然后在相关接口或者方法上声明@SentinelDegrade注解即可。

记得手动引入Sentinel依赖:

<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-core</artifactId>
   <version>1.6.3</version>
</dependency>

此外,还支持全局Sentinel熔断降级:

retrofit:
  # 熔断降级配置
  degrade:
    # 熔断降级类型。默认none,表示不启用熔断降级
    degrade-type: sentinel
    # 全局sentinel降级配置
    global-sentinel-degrade:
      # 是否开启
      enable: true
      # ...其他sentinel全局配置

Resilience4j

配置degrade-type=resilience4j开启。然后在相关接口或者方法上声明@Resilience4jDegrade即可。

记得手动引入Resilience4j依赖:

<dependency>
   <groupId>io.github.resilience4j</groupId>
   <artifactId>resilience4j-circuitbreaker</artifactId>
   <version>1.7.1</version>
</dependency>

通过以下配置可开启全局resilience4j熔断降级:

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

熔断配置管理:

实现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());
   }
}

通过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接口:

微服务之间的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);
}

全局拦截器

全局应用拦截器

如果我们需要对整个系统的的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

调用适配器

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

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

Retrofit会根据方法返回值类型选择对应的CallAdapterFactory执行适配处理,目前支持的返回值类型如下:

  • String:将Response Body适配成String返回。
  • 基础类型(Long/Integer/Boolean/Float/Double):将Response Body适配成上述基础类型
  • 任意Java类型: 将Response Body适配成对应的Java对象返回
  • CompletableFuture<T>: 将Response Body适配成CompletableFuture<T>对象返回
  • Void: 不关注返回类型可以使用Void
  • Response<T>: 将Response适配成Response<T>对象返回
  • Call<T>: 不执行适配处理,直接返回Call<T>对象
  • Mono<T>: Project Reactor响应式返回类型
  • Single<T>Rxjava响应式返回类型(支持Rxjava2/Rxjava3
  • CompletableRxjava响应式返回类型,HTTP请求没有响应体(支持Rxjava2/Rxjava3

可以通过继承CallAdapter.Factory扩展CallAdapter

组件支持通过retrofit.global-call-adapter-factories配置全局调用适配器工厂:

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

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

建议:将CallAdapter.Factory配置成Spring Bean

数据转码器

Retrofit使用Converter@Body注解的对象转换成Request Body,将Response Body转换成一个Java对象,可以选用以下几种Converter

  • Gson: com.squareup.Retrofit:converter-gson
  • Jackson: com.squareup.Retrofit:converter-jackson
  • Moshi: com.squareup.Retrofit:converter-moshi
  • Protobuf: com.squareup.Retrofit:converter-protobuf
  • Wire: com.squareup.Retrofit:converter-wire
  • Simple XML: com.squareup.Retrofit:converter-simplexml
  • 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:
      - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
      - retrofit2.converter.jackson.JacksonConverterFactory

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

建议:将Converter.Factory配置成Spring Bean

元注解

@RetrofitClient@Retry@Logging@Resilience4jDegrade等注解支持元注解、继承以及@AliasFor

@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;
}

其他功能示例

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群图片

retrofit-spring-boot-starter's People

Contributors

chentianming11 avatar dependabot[bot] avatar goldsubmarine avatar jagveer-loky avatar khaes-kth avatar mfangyuan avatar snailclimb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

retrofit-spring-boot-starter's Issues

能否灵活的设置请求的baseurl

因为业务的复杂性,对请求的调用很少只对一个baseurl,可能有5个以上。能否不用拦截器,简单灵活的修改baseurl,谢谢。

是否可以根据 配置文件是否有相应配置来建立 bean

@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface HttpApi {

@GET("person")
Result<Person> getPerson(@Query("id") Long id);

}
如果yml中没有test.baseUrl 能否不创建bean,而是提示一个警告?
或者用@conditionalonproperty 来作判断?

或者 baseURL 支持 SpEL?

RetrofitIOException: unexpected end of stream on Connection

`
Caused by: java.io.EOFException: \n not found: limit=0 content=…
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:240) ~[okio-1.17.5.jar:na]
at okhttp3.internal.http1.Http1Codec.readHeaderLine(Http1Codec.java:215) ~[okhttp-3.12.0.jar:na]
at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:189) ~[okhttp-3.12.0.jar:na]
... 128 common frames omitted

`

有没有办法忽略ssl证书校验

在测试环境ssl证书错误比较常见,有没有办法忽略

否则会报这个错
com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitIOException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target,

Failed to run with spring boot 2.1.4

Hi.
I added the latest version of the library to my maven pom.xml and run it. but I got the following error:

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    com.github.lianjiatech.retrofit.spring.boot.core.RetrofitFactoryBean.getOkHttpClient(RetrofitFactoryBean.java:225)

The following method did not exist:

    okhttp3.OkHttpClient$Builder.callTimeout(JLjava/util/concurrent/TimeUnit;)Lokhttp3/OkHttpClient$Builder;

The method's class, okhttp3.OkHttpClient$Builder, is available from the following locations:

    jar:file:/home/ghasem/.m2/repository/com/squareup/okhttp3/okhttp/3.8.1/okhttp-3.8.1.jar!/okhttp3/OkHttpClient$Builder.class

It was loaded from the following location:

    file:/home/ghasem/.m2/repository/com/squareup/okhttp3/okhttp/3.8.1/okhttp-3.8.1.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of okhttp3.OkHttpClient$Builder

Disconnected from the target VM, address: '127.0.0.1:46097', transport: 'socket'

Process finished with exit code 0

My pom.xml is as the below:



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

   
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
    </properties>



    <dependencies>
     
        <!--=====================================================-->
        <!--boot starters-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!--cache provider-->
        <dependency>
            <groupId>com.hazelcast</groupId>
            <artifactId>hazelcast</artifactId>
        </dependency>
        <dependency>
            <groupId>com.hazelcast</groupId>
            <artifactId>hazelcast-spring</artifactId>
        </dependency>


        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.2</version>
        </dependency>


        <!--=====================================================-->
        <!--cloud starters-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

      
        <!--=====================================================-->

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


    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

  
</project>


what is the problem?

如何处理json返回不同的类型

正常是返回数据,异常会返回空数组

正常

{"code":10888,"message":"未登录","data":{"uid": 1}}

异常

{"code":88,"message":"未登录","data":[]}

目前我将data定义成对象。遇到异常的情况会报错
com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitIOException: Cannot deserialize instance of com.demo.xxxx out of START_ARRAY token

@RetrofitClient 判断bug

版本:2.1.9

RetrofitFactoryBean#checkRetrofitInterface() 方法判断错误
提示信息是 @RetrofitClient's baseUrl and serviceId must be configured with one!
但是判断的是 path 不是 serviceId

关于retrofit-spring-boot-starter和RestTemplate的比较

使用微服务,Spring Cloud,服务与服务之间都是通过RestTemplate来调用,项目中也通过设置ConnectionFactory,改成了OkHttp3ClientHttpRequestFactory。但是还是用到了RestTemplate,因为默认的@LoadBalanced只对RestTemplate生效,retrofit不支持Spring Cloud的负载均衡吧。只可以在一些调用外部接口的服务上用。

微服务之间调用

大神,能不能把微服务调用的地方写个例子,文档描述的看不懂,谢谢

接口返回XML,所以指定了接口converterFactories = {JaxbConverterFactory.class},但是接口数据依然调用jackson

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected character ('}' (code 125)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name at [Source: (PushbackInputStream); line: 8, column: 2] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:284) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:242) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:205) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)

接口代码如下:
@RetrofitClient(baseUrl = "http://api.xxx.jp", logStrategy = LogStrategy.BODY,converterFactories = {JaxbConverterFactory.class}) @Retry public interface XxxJpXmlApi {

@Intercept中使用service调用相同的@RetrofitClient会变为空

使用的是retrofit-spring-boot-starter版本2.1.7

测试代码==>
接口TestApi :
@RetrofitClient(baseUrl = "http:localhost:8081/", logStrategy = LogStrategy.BODY)
@intercept(include = "/**", handler = TestIntercept.class)
public interface TestApi {
@get("test")
JSONObject test();
}
拦截器:
@component
public class TestIntercept extends BasePathMatchInterceptor {
@Autowired
private TestService testService;
@OverRide
protected Response doIntercept(Chain chain) throws IOException {
System.out.println("testService is null? " + Objects.isNull(testService));
return chain.proceed(chain.request());
}
}
关键的service:
@service
public class TestService {
//下面两行注释掉,TestIntercept的testService就不会为空
@Autowired
private TestApi testApi;
}
调用controller测试:
@RestController("testController2")
public class TestController {
@Autowired
private TestApi testApi;
@RequestMapping("/test")
public JSONObject test() {
return testApi.test();
}
}
控制台输出结果:testService is null? true
将TestService里的@Autowired private TestApi testApi;注释掉后
输出结果为testService is null? false

为什么会发现这个问题呢,
因为有一个使用场景,调用一些接口需要一个令牌参数校验。
所以用路径拦截器添加一个令牌参数,但是这个令牌是有有效期的,存在缓存里,
获取令牌的接口也在相同的@RetrofitClient下面
在业务层判断缓存里获取这个令牌,如果缓存里过期需要重新调接口获取令牌。
如果把获取令牌的接口,和其他在需要在参数中添加令牌的接口分别建立两个@RetrofitClient就不会有上面的问题
这可能是为了防止循环调用的问题把

@RetrofitClient 配置converterFactories = {JaxbConverterFactory.class}解析XML异常

JDK:1.8
retrofit-spring-boot-starter版本:2.2.7
异常效果:
依然走默认JacksonConverterFactory解析,不走JaxbConverterFactory解析返回数据。
@RetrofitClient(baseUrl = "http://xxx.com",converterFactories = {JaxbConverterFactory.class}) @QooJpSign(exclude = {"/GMKT.INC.Front.QAPIService/CertificationAPI.CreateCertificationKey"}) @Retry public interface QooJpXmlApi
如上注解已经配置JaxbConverterFactory,但是系统依然走默认配置JacksonConverterFactory
异常如下:
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('<' (code 60)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at [Source: (okhttp3.ResponseBody$BomAwareReader); line: 1, column: 2] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1851) at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:707) at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:632) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:1947) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:776) at com.fasterxml.jackson.databind.ObjectReader._initForReading(ObjectReader.java:357) at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2064) at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1485) at retrofit2.converter.jackson.JacksonResponseBodyConverter.convert(JacksonResponseBodyConverter.java:33) at retrofit2.converter.jackson.JacksonResponseBodyConverter.convert(JacksonResponseBodyConverter.java:23) at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243) at retrofit2.OkHttpCall.execute(OkHttpCall.java:204) at com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory$BodyCallAdapter.adapt(BodyCallAdapterFactory.java:77) ... 104 more

retrofit 2.1.4版本,springboot 2.3.4.RELEASE 启动报错java.lang.ClassNotFoundException: org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

09:42:34.623 [main] ERROR o.s.b.SpringApplication - [reportFailure,837] - Application run failed
java.lang.IllegalArgumentException: Could not find class [org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration]
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:334)
at org.springframework.core.annotation.TypeMappedAnnotation.adapt(TypeMappedAnnotation.java:446)
at org.springframework.core.annotation.TypeMappedAnnotation.getValue(TypeMappedAnnotation.java:369)
at org.springframework.core.annotation.TypeMappedAnnotation.asMap(TypeMappedAnnotation.java:284)
at org.springframework.core.annotation.AbstractMergedAnnotation.asAnnotationAttributes(AbstractMergedAnnotation.java:193)
at org.springframework.core.type.AnnotatedTypeMetadata.getAnnotationAttributes(AnnotatedTypeMetadata.java:106)
at org.springframework.context.annotation.AnnotationConfigUtils.attributesFor(AnnotationConfigUtils.java:285)
at org.springframework.context.annotation.AnnotationBeanNameGenerator.determineBeanNameFromAnnotation(AnnotationBeanNameGenerator.java:102)
at org.springframework.context.annotation.AnnotationBeanNameGenerator.generateBeanName(AnnotationBeanNameGenerator.java:81)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.registerBeanDefinitionForImportedConfigurationClass(ConfigurationClassBeanDefinitionReader.java:160)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:141)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:120)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:331)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.zqt.ZqtApplication.main(ZqtApplication.java:21)
Caused by: java.lang.ClassNotFoundException: org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324)
... 25 common frames omitted

怎么单独禁止某个接口的body转换

某个第三方接口的Content-Type为text/plain,但是body里面传字符串会被加上双引号,怎么单独对这个接口禁止body转换,其它的接口还是需要正常转换,这个接口的返回值也需要正常转换。

连接报错:java.net.SocketException: Connection reset

连接报错,不定时出现。查了资料,说是“okhttp3连接关闭时没有释放,换okhttp就好了”,我还没试过。
错误信息:

2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | --> POST https://open.welink.huaweicloud.com/api/auth/v2/tickets
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | Content-Type: application/json
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | Content-Length: 103
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | Connection: close
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m |
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | {
"client_id": "20200520113800906557547",
"client_secret": "ca838912-0eqw-4b81-9610-8262a77183aa"
}
2021-01-05 15:49:42 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | --> END POST (103-byte body)
2021-01-05 15:49:45 | �[34mINFO �[0;39m | �[1;33mpool-2-thread-1�[0;39m | �[1;32mokhttp3.OkHttpClient�[0;39m | <-- HTTP FAILED: java.net.SocketException: Connection reset
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at sun.security.ssl.InputRecord.readFully(Unknown Source)
at sun.security.ssl.InputRecord.read(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:379)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:337)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:209)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:219)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)

在Java11中会有反射警告

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by retrofit2.Platform (file:/D:/Dev/Libraries/apache-maven-3.6.3/repo/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of retrofit2.Platform
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

使用含有@Body的@Get方法报错:Non-body HTTP method cannot contain @Body.

Hi, 我的问题如下:
我使用retrofit调用Kafka Rest接口, 接口要求使用Get方法,并且须带有一个Json格式的body,我的定义如下
@Headers({"Content-Type:application/vnd.kafka.json.v2+json"}) @GET("consumers/{group_name}/instances/{instance_name}/offsets") Call<JsonNode> getOffsets(@Path("group_name") String groupName, @Path("instance_name") String instanceName, @Body RequestBody body);

执行时报错: Non-body HTTP method cannot contain @Body.
可否协助解决?

Failed to run with unit test

Received this error when trying to build source with test

`
java.lang.IllegalStateException
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException

`

使用定义的接口后启动失败

在自定义接口使用后启动报错:
接口:

@RetrofitClient(baseUrl = "${oms.baseUrl}")
public interface OmsRestApi {

@POST("oauth/token")
TokenResult token(@Body TokenBody body);

}

错误日志:
2021-01-22 09:07:48,338 [main] INFO o.t.s.ThingsboardServerApplication - Starting ThingsboardServerApplication on luoyb-laptop with PID 15568 (D:\code\git\thingsboard\application\target\classes started by luoyuanbin in D:\code\git\thingsboard)
2021-01-22 09:07:48,344 [main] INFO o.t.s.ThingsboardServerApplication - No active profile set, falling back to default profiles: default
2021-01-22 09:07:48,753 [background-preinit] WARN o.s.h.c.j.Jackson2ObjectMapperBuilder - For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
2021-01-22 09:07:50,681 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [D:\code\git\thingsboard\dao\target\classes\org\thingsboard\server\dao\rest\OmsRestApi.class]; nested exception is java.lang.NoClassDefFoundError: org/slf4j/event/Level
2021-01-22 09:07:50,699 [main] INFO o.s.b.a.l.ConditionEvaluationReportLoggingListener -

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-01-22 09:07:50,708 [main] ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [D:\code\git\thingsboard\dao\target\classes\org\thingsboard\server\dao\rest\OmsRestApi.class]; nested exception is java.lang.NoClassDefFoundError: org/slf4j/event/Level
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:452)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:315)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276)
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:296)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:207)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:175)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:319)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.thingsboard.server.ThingsboardServerApplication.main(ThingsboardServerApplication.java:40)
Caused by: java.lang.NoClassDefFoundError: org/slf4j/event/Level
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.getDeclaredMethods(Class.java:1975)
at org.springframework.core.annotation.AttributeMethods.compute(AttributeMethods.java:256)
at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324)
at org.springframework.core.annotation.AttributeMethods.forAnnotationType(AttributeMethods.java:252)
at org.springframework.core.annotation.AnnotationTypeMapping.(AnnotationTypeMapping.java:96)
at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112)
at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75)
at org.springframework.core.annotation.AnnotationTypeMappings.(AnnotationTypeMappings.java:68)
at org.springframework.core.annotation.AnnotationTypeMappings.(AnnotationTypeMappings.java:46)
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245)
at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324)
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241)
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199)
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182)
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169)
at org.springframework.core.annotation.TypeMappedAnnotation.of(TypeMappedAnnotation.java:597)
at org.springframework.core.annotation.MergedAnnotation.of(MergedAnnotation.java:610)
at org.springframework.core.type.classreading.MergedAnnotationReadingVisitor.visitEnd(MergedAnnotationReadingVisitor.java:96)
at org.springframework.asm.ClassReader.readElementValues(ClassReader.java:2985)
at org.springframework.asm.ClassReader.accept(ClassReader.java:582)
at org.springframework.asm.ClassReader.accept(ClassReader.java:401)
at org.springframework.core.type.classreading.SimpleMetadataReader.(SimpleMetadataReader.java:49)
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103)
at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:123)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:429)
... 21 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.slf4j.event.Level
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
... 48 common frames omitted
Disconnected from the target VM, address: '127.0.0.1:51977', transport: 'socket'

Process finished with exit code 1

处理500响应

比如请求某网站返回500,代表登录失效。我发现用
Response<String>
是拿不到响应的,文档上说[如果http状态码不是2xx,直接抛错!] 控制台也没看到抛错

RetrofitException: java.lang.IllegalStateException: Unbalanced enter/exit

jdk 1.8 ,在本机运行无异常,但是部署到Linux服务器就报异常:
Unbalanced enter
完整的错误日志:

com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitException: java.lang.IllegalStateException: Unbalanced enter/exit, request=Request{method=GET, url=https://search-nml3.kuke.com/api/v1/query?jsonq={%22catalogue_name|tracks|movements%22:%22%E6%AD%8C%E5%89%A7/%E8%BD%BB%E6%AD%8C%E5%89%A7%22}&page=1&size=1&channel=37,0, tags={class retrofit2.Invocation=com.kuke.rpc.retrofit.SearchApi.getHighQuery() [{"catalogue_name|tracks|movements":"歌剧/轻歌剧"}, 1, 1, 37,0]}}
	at com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitException.errorUnknown(RetrofitException.java:46)
	at com.github.lianjiatech.retrofit.spring.boot.core.ErrorDecoder.exceptionDecode(ErrorDecoder.java:57)
	at com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor.intercept(ErrorDecoderInterceptor.java:49)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
	at okhttp3.RealCall.execute(RealCall.java:81)
	at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
	at com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory$BodyCallAdapter.adapt(BodyCallAdapterFactory.java:77)
	at retrofit2.HttpServiceMethod$CallAdapted.adapt(HttpServiceMethod.java:165)
	at retrofit2.HttpServiceMethod.invoke(HttpServiceMethod.java:146)
	at retrofit2.Retrofit$1.invoke(Retrofit.java:160)
	at com.sun.proxy.$Proxy182.getHighQuery(Unknown Source)
	at sun.reflect.GeneratedMethodAccessor191.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.github.lianjiatech.retrofit.spring.boot.core.RetrofitInvocationHandler.invoke(RetrofitInvocationHandler.java:35)
	at com.sun.proxy.$Proxy182.getHighQuery(Unknown Source)
	at com.kuke.openapi.service.impl.SearchServiceImpl.getHighDensitySearch(SearchServiceImpl.java:79)
	at com.kuke.openapi.controller.SearchController.getHighDensitySearch(SearchController.java:142)
	at com.kuke.openapi.controller.SearchController$$FastClassBySpringCGLIB$$b8d00b5b.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at com.kuke.advice.LogAspect.around(LogAspect.java:44)
	at sun.reflect.GeneratedMethodAccessor141.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
	at com.kuke.openapi.controller.SearchController$$EnhancerBySpringCGLIB$$aaaa39e3.getHighDensitySearch(<generated>)
	at sun.reflect.GeneratedMethodAccessor190.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:65)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.kuke.filter.AuthorizeFilter.doFilter(AuthorizeFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.cloud.sleuth.instrument.web.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:50)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:82)
	at org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration$LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:143)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: Unbalanced enter/exit
	at com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor.retryIntercept(DefaultRetryInterceptor.java:43)
	at com.github.lianjiatech.retrofit.spring.boot.retry.BaseRetryInterceptor.intercept(BaseRetryInterceptor.java:48)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor.intercept(ErrorDecoderInterceptor.java:30)
	... 113 more
Caused by: java.lang.IllegalStateException: Unbalanced enter/exit
	at okio.AsyncTimeout.enter(AsyncTimeout.java:73)
	at okio.AsyncTimeout$1.write(AsyncTimeout.java:178)
	at okio.RealBufferedSink.emitCompleteSegments(RealBufferedSink.java:179)
	at okio.RealBufferedSink.writeUtf8(RealBufferedSink.java:54)
	at okhttp3.internal.http1.Http1ExchangeCodec.writeRequest(Http1ExchangeCodec.java:196)
	at okhttp3.internal.http1.Http1ExchangeCodec.writeRequestHeaders(Http1ExchangeCodec.java:141)
	at okhttp3.internal.connection.Exchange.writeRequestHeaders(Exchange.java:72)
	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:43)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:223)
	at com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor.intercept(DefaultLoggingInterceptor.java:30)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:43)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
	at com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor.retryIntercept(DefaultRetryInterceptor.java:27)
	... 117 more

希望支持开发模式@Profile注解

开发、生产模式下同一业务需要不同url支撑,但业务代码希望不变,如果支持一个profile注解就能很好支撑,希望能支持下

springboot 2.1.9启动报错

  • pom:
    <dependency>
        <groupId>com.github.lianjiatech</groupId>
        <artifactId>retrofit-spring-boot-starter</artifactId>
        <version>2.2.8</version>
    </dependency>

springboot 2.1.9

  • interface
@RetrofitClient(baseUrl = "${search.nml3.baseUrl}")
public interface SearchApi {

//    @OkHttpClientBuilder
//    static OkHttpClient.Builder okhttpClientBuilder() {
//        return new OkHttpClient.Builder()
//                .connectTimeout(1, TimeUnit.SECONDS)
//                .readTimeout(1, TimeUnit.SECONDS)
//                .writeTimeout(1, TimeUnit.SECONDS);
//    }

    @GET("/api/v1/query")
    Map<String, Object> getQuery(
            @RequestParam(name = "q") String q,
            @RequestParam(name = "jsonq ") String jsonq,
            @RequestParam(name = "page") Integer page,
            @RequestParam(name = "size") Integer size,
            @RequestParam(name = "channel") Long channel
    );
}

主要看这两行:
nested exception is java.lang.NoSuchMethodError: okhttp3.OkHttpClient$Builder.callTimeout(JLjava/util/concurrent/TimeUnit;)Lokhttp3/OkHttpClient$Builder

Correct the classpath of your application so that it contains a single, compatible version of okhttp3.OkHttpClient$Builder

原来引用了okhttp3.8,1启动报错,注释掉以后也不行,折腾了一下午快废了

@OkHttpClient.Builder 建议取消static方法限制

@OkHttpClient.Builder 目前限制static方法。对于一些通用的OkHttpClient创建流程,无法抽象OkHttpClient的创建方法到父接口中,比如用default修饰即可在父接口中扩展通用的OkHttpClient创建流程。
private Method findOkHttpClientBuilderMethod(Class<?> retrofitClientInterfaceClass) { Method[] methods = retrofitClientInterfaceClass.getMethods(); for (Method method : methods) { if (Modifier.isStatic(method.getModifiers()) && method.isAnnotationPresent(OkHttpClientBuilder.class) && method.getReturnType().equals(OkHttpClient.Builder.class)) { return method; } } return null; }

bug

java.lang.NoClassDefFoundError: okio/Options

如何方便的设置http代理

说一下场景。我有100个账号要抢购,不知道商家什么时候放库存。用一个账号不停的轮询。发现有库存了,剩下99账号使用Http 代理抢购。每个账号使用的代理地址都不一样。
目前知道要在OkHttpClient里设置代理

@OkHttpClientBuilder
    static OkHttpClient.Builder okhttpClientBuilder() {
        return new OkHttpClient.Builder()
                .proxy();
    }

有没有方法在调用RetrofitClient之前,传入okHttpClient

返回适配器转换问题

返回数据为一个对象,里面有一个List,和总数据条数,但LIST 转换出来为空, 总条数正常,用OBJECT接收正常

返回数据
{
"tCarDTOS": [
{
"vehicleNo": "鲁B1WG70",
"plateColor": "蓝色",
"vehicleType": "小型轿车",
"ownerName": "个人",
"vehicleColor": "",
"certifyDateA": "19700101",
"transArea": "青岛",
"certifyDateB": "20200102",
"fareType": 1,
"fuelType": "汽油",
"commercialType": 1,
"updateTime": "20200422",
"cityId": 60
},
{
"vehicleNo": "鲁2NJ0",
"plateColor": "蓝色",
"vehicleType": "小型轿车",
"brand": "大众",
"model": "朗逸",
"ownerName": "陈好",
"vehicleColor": "",
"certifyDateA": "20170814",
"transArea": "青岛",
"certifyDateB": "20200104",
"fareType": 1,
"fuelType": "汽油",
"commercialType": 1,
"updateTime": "20200422",
"cityId": 60
}
],
"totalCount": 22301
}

接收对象
public class DriverDto {
List tDriverDTOS;
Integer totalCount;
}

实际转换结果
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.