A springboot starter for retrofit, and supports many functional feature enhancements, greatly simplifying development

Overview

retrofit-spring-boot-starter

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

English Document

Retrofit是适用于AndroidJava且类型安全的HTTP客户端,其最大的特性的是支持通过接口的方式发起HTTP请求。而spring-boot是使用最广泛的Java开发框架,但是Retrofit官方没有支持与spring-boot框架快速整合,因此我们开发了retrofit-spring-boot-starter

retrofit-spring-boot-starter实现了Retrofitspring-boot框架快速整合,并且支持了诸多功能增强,极大简化开发

🚀 项目持续优化迭代,欢迎大家提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.lianjiatechgroupId>
   <artifactId>retrofit-spring-boot-starterartifactId>
   <version>2.2.18version>
dependency>

强烈建议使用最新版本,稳定无bug!

2.2.132.2.14对配置中心有兼容性bug,请勿使用!!!

本项目依赖Retrofit-2.9.0,okhttp-3.14.9,okio-1.17.5版本,如果冲突,烦请手动引入相关jar包。完整依赖如下:

<dependency>
    <groupId>com.github.lianjiatechgroupId>
   <artifactId>retrofit-spring-boot-starterartifactId>
   <version>2.2.18version>
dependency>
 <dependency>
    <groupId>com.squareup.okhttp3groupId>
    <artifactId>logging-interceptorartifactId>
    <version>3.14.9version>
dependency>
<dependency>
    <groupId>com.squareup.okhttp3groupId>
    <artifactId>okhttpartifactId>
    <version>3.14.9version>
dependency>
<dependency>
    <groupId>com.squareup.okiogroupId>
    <artifactId>okioartifactId>
    <version>1.17.5version>
dependency>
<dependency>
    <groupId>com.squareup.retrofit2groupId>
    <artifactId>retrofitartifactId>
    <version>2.9.0version>
dependency>
<dependency>
    <groupId>com.squareup.retrofit2groupId>
    <artifactId>converter-jacksonartifactId>
    <version>2.9.0version>
dependency>

定义http接口

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

getPerson(@Query("id") Long id); }">
@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface HttpApi {

    @GET("person")
    Result<Person> getPerson(@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 TestService {

    @Autowired
    private HttpApi httpApi;

    public void test() {
        // 通过httpApi发起http请求
    }
}

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

HTTP请求相关注解

HTTP请求相关注解,全部使用了retrofit原生注解。详细信息可参考官方文档: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-spring-boot-starter支持了多个可配置的属性,用来应对不同的业务场景。**

retrofit:
   # 连接池配置
   pool:
      # test1连接池配置
      test1:
         # 最大空闲连接数
         max-idle-connections: 3
         # 连接保活时间(秒)
         keep-alive-second: 100

   # 是否禁用void返回值类型
   disable-void-return-type: false


   # 全局转换器工厂
   global-converter-factories:
      - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
      - retrofit2.converter.jackson.JacksonConverterFactory
   # 全局调用适配器工厂
   global-call-adapter-factories:
      - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
      - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory

   # 日志打印配置
   log:
      # 启用日志打印
      enable: true
      # 日志打印拦截器
      logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
      # 全局日志打印级别
      global-log-level: info
      # 全局日志打印策略
      global-log-strategy: body


   # 重试配置
   retry:
      # 是否启用全局重试
      enable-global-retry: true
      # 全局重试间隔时间
      global-interval-ms: 1
      # 全局最大重试次数
      global-max-retries: 1
      # 全局重试规则
      global-retry-rules:
         - response_status_not_2xx
         - occur_io_exception
      # 重试拦截器
      retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor

   # 熔断降级配置
   degrade:
      # 是否启用熔断降级
      enable: true
      # 熔断降级实现方式
      degrade-type: sentinel
      # 熔断资源名称解析器
      resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
   # 全局连接超时时间
   global-connect-timeout-ms: 5000
   # 全局读取超时时间
   global-read-timeout-ms: 5000
   # 全局写入超时时间
   global-write-timeout-ms: 5000
   # 全局完整调用超时时间
   global-call-timeout-ms: 0

高级功能

超时时间设置

retrofit-spring-boot-starter支持两种方式设置超时时间,一种是全局超时时间设置,另一种是注解超时时间设置。

全局超时时间设置

在yaml文件中可配置全局超时时间,对所有接口生效

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

注解式超时时间设置

@RetrofitClient注解上可以设置超时时间,针对当前接口生效,优先级更高。具体字段有connectTimeoutMsreadTimeoutMswriteTimeoutMscallTimeoutMs等。

注解式拦截器

很多时候,我们希望某个接口下的某些http请求执行统一的拦截处理逻辑。为了支持这个功能,retrofit-spring-boot-starter提供了注解式拦截器,做到了基于url路径的匹配拦截。使用的步骤主要分为2步:

  1. 继承BasePathMatchInterceptor编写拦截处理器;
  2. 接口上使用@Intercept进行标注。如需配置多个拦截器,在接口上标注多个@Intercept注解即可!

下面以给指定请求的url后面拼接timestamp时间戳为例,介绍下如何使用注解式拦截器。

继承BasePathMatchInterceptor编写拦截处理器

@Component
public class TimeStampInterceptor extends BasePathMatchInterceptor {

    @Override
    public Response doIntercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();
        long timestamp = System.currentTimeMillis();
        HttpUrl newUrl = url.newBuilder()
                .addQueryParameter("timestamp", String.valueOf(timestamp))
                .build();
        Request newRequest = request.newBuilder()
                .url(newUrl)
                .build();
        return chain.proceed(newRequest);
    }
}

接口上使用@Intercept进行标注

getPerson(@Query("id") Long id); @POST("savePerson") Result savePerson(@Body Person person); }">
@RetrofitClient(baseUrl = "${test.baseUrl}")
@Intercept(handler = TimeStampInterceptor.class, include = {"/api/**"}, exclude = "/api/test/savePerson")
public interface HttpApi {

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

    @POST("savePerson")
    Result<Person> savePerson(@Body Person person);
}

上面的@Intercept配置表示:拦截HttpApi接口下/api/**路径下(排除/api/test/savePerson)的请求,拦截处理器使用TimeStampInterceptor

扩展注解式拦截器

有的时候,我们需要在拦截注解动态传入一些参数,然后再执行拦截的时候需要使用这个参数。这种时候,我们可以扩展实现自定义拦截注解自定义拦截注解必须使用@InterceptMark标记,并且注解中必须包括include()、exclude()、handler()属性信息。使用的步骤主要分为3步:

  1. 自定义拦截注解
  2. 继承BasePathMatchInterceptor编写拦截处理器
  3. 接口上使用自定义拦截注解;

例如我们需要在请求头里面动态加入accessKeyIdaccessKeySecret签名信息才能正常发起http请求,这个时候可以自定义一个加签拦截器注解@Sign来实现。下面以自定义@Sign拦截注解为例进行说明。

自定义@Sign注解

handler() default SignInterceptor.class; }">
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
    /**
     * 密钥key
     * 支持占位符形式配置。
     *
     * @return
     */
    String accessKeyId();

    /**
     * 密钥
     * 支持占位符形式配置。
     *
     * @return
     */
    String accessKeySecret();

    /**
     * 拦截器匹配路径
     *
     * @return
     */
    String[] include() default {"/**"};

    /**
     * 拦截器排除匹配,排除指定路径拦截
     *
     * @return
     */
    String[] exclude() default {};

    /**
     * 处理该注解的拦截器类
     * 优先从spring容器获取对应的Bean,如果获取不到,则使用反射创建一个!
     *
     * @return
     */
    ClassBasePathMatchInterceptor> handler() default SignInterceptor.class;
}

扩展自定义拦截注解有以下2点需要注意:

  1. 自定义拦截注解必须使用@InterceptMark标记。
  2. 注解中必须包括include()、exclude()、handler()属性信息。

实现SignInterceptor

@Component
public class SignInterceptor extends BasePathMatchInterceptor {

    private String accessKeyId;

    private String accessKeySecret;

    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }

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

上述accessKeyIdaccessKeySecret字段值会依据@Sign注解的accessKeyId()accessKeySecret()值自动注入,如果@Sign指定的是占位符形式的字符串,则会取配置属性值进行注入。另外,accessKeyIdaccessKeySecret字段必须提供setter方法

接口上使用@Sign

getPerson(@Query("id") Long id); @POST("savePerson") Result savePerson(@Body Person person); }">
@RetrofitClient(baseUrl = "${test.baseUrl}")
@Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", exclude = {"/api/test/person"})
public interface HttpApi {

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

    @POST("savePerson")
    Result<Person> savePerson(@Body Person person);
}

这样就能在指定url的请求上,自动加上签名信息了。

连接池管理

默认情况下,所有通过Retrofit发送的http请求都会使用max-idle-connections=5 keep-alive-second=300的默认连接池。当然,我们也可以在配置文件中配置多个自定义的连接池,然后通过@RetrofitClientpoolName属性来指定使用。比如我们要让某个接口下的请求全部使用poolName=test1的连接池,代码实现如下:

  1. 配置连接池。

    retrofit:
      # 连接池配置
      pool:
        # test1连接池配置
        test1:
          # 最大空闲连接数
          max-idle-connections: 3
          # 连接保活时间(秒)
          keep-alive-second: 100
  2. 通过@RetrofitClientpoolName属性来指定使用的连接池。

    getPerson(@Query("id") Long id); }">
    @RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1")
    public interface HttpApi {
    
        @GET("person")
        Result<Person> getPerson(@Query("id") Long id);
    }

日志打印

很多情况下,我们希望将http请求日志记录下来。本框架支持以下全局日志打印配置:

retrofit:
  # 日志打印配置
  log:
    # 启用日志打印
    enable: true
    # 日志打印拦截器
    logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
    # 全局日志打印级别
    global-log-level: info
    # 全局日志打印策略
    global-log-strategy: body

4种日志打印策略含义如下

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

针对每个接口,如果需要单独定制的话,可以设置@RetrofitClientenableLoglogLevellogStrategy

请求重试

retrofit-spring-boot-starter支持支持全局重试和声明式重试。

全局重试

全局重试默认关闭,可以通过配置retrofit.retry.enable-global-retry=ture开启。开启之后,所有HTTP请求都会按照配置参数自动重试,详细配置项如下:

retrofit:
  # 重试配置
  retry:
    # 是否启用全局重试
    enable-global-retry: true
    # 全局重试间隔时间
    global-interval-ms: 20
    # 全局最大重试次数
    global-max-retries: 10
    # 全局重试规则
    global-retry-rules:
      - response_status_not_2xx
    # 重试拦截器
    retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor

重试规则支持三种配置

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

声明式重试

如果只需要在指定某些请求才执行重试,可以使用声明式重试!具体就是在接口或者方法上声明@Retry注解。

错误解码器

HTTP发生请求错误(包括发生异常或者响应数据不符合预期)的时候,错误解码器可将HTTP相关信息解码到自定义异常中。你可以在@RetrofitClient注解的errorDecoder()指定当前接口的错误解码器,自定义错误解码器需要实现ErrorDecoder接口:

/**
 * 错误解码器。ErrorDecoder.
 * 当请求发生异常或者收到无效响应结果的时候,将HTTP相关信息解码到异常中,无效响应由业务自己判断
 *
 * When an exception occurs in the request or an invalid response result is received, the HTTP related information is decoded into the exception,
 * and the invalid response is determined by the business itself.
 *
 * @author 陈添明
 */
public interface ErrorDecoder {

    /**
     * 当无效响应的时候,将HTTP信息解码到异常中,无效响应由业务自行判断。
     * When the response is invalid, decode the HTTP information into the exception, invalid response is determined by business.
     *
     * @param request  request
     * @param response response
     * @return If it returns null, the processing is ignored and the processing continues with the original response.
     */
    default RuntimeException invalidRespDecode(Request request, Response response) {
        if (!response.isSuccessful()) {
            throw RetrofitException.errorStatus(request, response);
        }
        return null;
    }


    /**
     * 当请求发生IO异常时,将HTTP信息解码到异常中。
     * When an IO exception occurs in the request, the HTTP information is decoded into the exception.
     *
     * @param request request
     * @param cause   IOException
     * @return RuntimeException
     */
    default RuntimeException ioExceptionDecode(Request request, IOException cause) {
        return RetrofitException.errorExecuting(request, cause);
    }

    /**
     * 当请求发生除IO异常之外的其它异常时,将HTTP信息解码到异常中。
     * When the request has an exception other than the IO exception, the HTTP information is decoded into the exception.
     *
     * @param request request
     * @param cause   Exception
     * @return RuntimeException
     */
    default RuntimeException exceptionDecode(Request request, Exception cause) {
        return RetrofitException.errorUnknown(request, cause);
    }

}

全局拦截器

全局应用拦截器

如果我们需要对整个系统的的http请求执行统一的拦截处理,可以自定义实现全局拦截器BaseGlobalInterceptor, 并配置成spring容器中的bean!例如我们需要在整个系统发起的http请求,都带上来源信息。

@Component
public class SourceInterceptor extends BaseGlobalInterceptor {
    @Override
    public Response doIntercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request newReq = request.newBuilder()
                .addHeader("source", "test")
                .build();
        return chain.proceed(newReq);
    }
}

全局网络拦截器

只需要实现NetworkInterceptor接口 并配置成spring容器中的bean就支持自动织入全局网络拦截器。

熔断降级

retrofit-spring-boot-starter支持熔断降级功能,底层基于Sentinel实现。具体来说,支持了熔断资源自发现注解式降级规则配置。如需使用熔断降级,只需要进行以下操作即可:

1. 开启熔断降级功能

默认情况下,熔断降级功能是关闭的,需要设置相应的配置项来开启熔断降级功能

retrofit:
  # 熔断降级配置
  degrade:
    # 是否启用熔断降级
    enable: true
    # 熔断降级实现方式
    degrade-type: sentinel
    # 熔断资源名称解析器
    resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser

资源名称解析器用于实现用户自定义资源名称,默认配置是DefaultResourceNameParser,对应的资源名称格式为HTTP_OUT:GET:http://localhost:8080/api/degrade/test。用户可以继承BaseResourceNameParser类实现自己的资源名称解析器。

另外,由于熔断降级功能是可选的,因此启用熔断降级需要用户自行引入Sentinel依赖

<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-coreartifactId>
    <version>1.6.3version>
dependency>

2. 配置降级规则(可选)

retrofit-spring-boot-starter支持注解式配置降级规则,通过@Degrade注解来配置降级规则@Degrade注解可以配置在接口或者方法上,配置在方法上的优先级更高。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface Degrade {

    /**
     * RT threshold or exception ratio threshold count.
     */
    double count();

    /**
     * Degrade recover timeout (in seconds) when degradation occurs.
     */
    int timeWindow() default 5;

    /**
     * Degrade strategy (0: average RT, 1: exception ratio).
     */
    DegradeStrategy degradeStrategy() default DegradeStrategy.AVERAGE_RT;
}

如果应用项目已支持通过配置中心配置降级规则,可忽略注解式配置方式

3. @RetrofitClient设置fallback或者fallbackFactory (可选)

如果@RetrofitClient不设置fallback或者fallbackFactory,当触发熔断时,会直接抛出RetrofitBlockException异常。用户可以通过设置fallback或者fallbackFactory来定制熔断时的方法返回值fallback类必须是当前接口的实现类,fallbackFactory必须是FallbackFactory 实现类,泛型参数类型为当前接口类型。另外,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;
    }
}
test() { Result fallback = new Result<>(); fallback.setCode(100) .setMsg("fallback") .setBody(1000000); return fallback; } } }">
@Slf4j
@Service
public class HttpDegradeFallbackFactory implements FallbackFactory<HttpDegradeApi> {

    /**
     * Returns an instance of the fallback appropriate for the given cause
     *
     * @param cause fallback cause
     * @return 实现了retrofit接口的实例。an instance that implements the retrofit interface.
     */
    @Override
    public HttpDegradeApi create(Throwable cause) {
        log.error("触发熔断了! ", cause.getMessage(), cause);
        return new HttpDegradeApi() {
            @Override
            public Result<Integer> test() {
                Result<Integer> fallback = new Result<>();
                fallback.setCode(100)
                        .setMsg("fallback")
                        .setBody(1000000);
                return fallback;
            }
    }
}

微服务之间的HTTP调用

为了能够使用微服务调用,需要进行如下配置:

配置ServiceInstanceChooserSpring容器Bean

用户可以自行实现ServiceInstanceChooser接口,完成服务实例的选取逻辑,并将其配置成Spring容器的Bean。对于Spring Cloud应用,retrofit-spring-boot-starter提供了SpringCloudServiceInstanceChooser实现,用户只需将其配置成SpringBean即可。

@Bean
@Autowired
public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) {
    return new SpringCloudServiceInstanceChooser(loadBalancerClient);
}

使用@RetrofitserviceIdpath属性,可以实现微服务之间的HTTP调用

@RetrofitClient(serviceId = "${jy-helicarrier-api.serviceId}", path = "/m/count", errorDecoder = HelicarrierErrorDecoder.class)
@Retry
public interface ApiCountService {

}

自定义注入OkHttpClient

通常情况下,通过@RetrofitClient注解属性动态创建OkHttpClient对象能够满足大部分使用场景。但是在某些情况下,用户可能需要自定义OkHttpClient ,这个时候,可以在接口上定义返回类型是OkHttpClient.Builder的静态方法来实现。代码示例如下:

getPerson(@Url String url, @Query("id") Long id); }">
@RetrofitClient(baseUrl = "http://ke.com")
public interface HttpApi3 {

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

   }

   @GET
   Result<Person> getPerson(@Url String url, @Query("id") Long id);
}

方法必须使用@OkHttpClientBuilder注解标记!

调用适配器和数据转码器

调用适配器

Retrofit可以通过调用适配器CallAdapterFactoryCall 对象适配成接口方法的返回值类型。retrofit-spring-boot-starter扩展2种CallAdapterFactory 实现:

  1. BodyCallAdapterFactory
    • 默认启用,可通过配置retrofit.enable-body-call-adapter=false关闭
    • 同步执行http请求,将响应体内容适配成接口方法的返回值类型实例。
    • 除了Retrofit.Call Retrofit.Response java.util.concurrent.CompletableFuture 之外,其它返回类型都可以使用该适配器。
  2. ResponseCallAdapterFactory
    • 默认启用,可通过配置retrofit.enable-response-call-adapter=false关闭
    • 同步执行http请求,将响应体内容适配成Retrofit.Response 返回。
    • 如果方法的返回值类型为Retrofit.Response ,则可以使用该适配器。

Retrofit自动根据方法返回值类型选用对应的CallAdapterFactory执行适配处理!加上Retrofit默认的CallAdapterFactory,可支持多种形式的方法返回值类型:

  • 基础类型(String/Long/Integer/Boolean/Float/Double):直接将响应内容转换为上述基础类型。
  • 其它任意POJO类型: 将响应体内容适配成一个对应的POJO类型对象返回,如果http状态码不是2xx,直接抛错!(推荐)
  • CompletableFuture : 将响应体内容适配成CompletableFuture 对象返回!(异步调用推荐)
  • Void: 不关注返回类型可以使用Void。如果http状态码不是2xx,直接抛错!(不关注返回值)
  • Response : 将响应内容适配成Response 对象返回!(不推荐)
  • Call : 不执行适配处理,直接返回Call 对象!(不推荐)
getPerson(@Query("id") Long id); /** * CompletableFuture * 将响应体内容适配成CompletableFuture 对象返回 * @param id * @return */ @GET("person") CompletableFuture > getPersonCompletableFuture(@Query("id") Long id); /** * Void * 不关注返回类型可以使用Void。如果http状态码不是2xx,直接抛错! * @param id * @return */ @GET("person") Void getPersonVoid(@Query("id") Long id); /** * Response * 将响应内容适配成Response 对象返回 * @param id * @return */ @GET("person") Response > getPersonResponse(@Query("id") Long id); /** * Call * 不执行适配处理,直接返回Call 对象 * @param id * @return */ @GET("person") Call > getPersonCall(@Query("id") Long id); ">
    /**
     * 其他任意Java类型
     * 将响应体内容适配成一个对应的Java类型对象返回,如果http状态码不是2xx,直接抛错!
     * @param id
     * @return
     */
    @GET("person")
    Result<Person> getPerson(@Query("id") Long id);

    /**
     *  CompletableFuture
               
     *  将响应体内容适配成CompletableFuture
               
                对象返回
               
     * @param id
     * @return
     */
    @GET("person")
    CompletableFuture<Result<Person>> getPersonCompletableFuture(@Query("id") Long id);

    /**
     * Void
     * 不关注返回类型可以使用Void。如果http状态码不是2xx,直接抛错!
     * @param id
     * @return
     */
    @GET("person")
    Void getPersonVoid(@Query("id") Long id);

    /**
     *  Response
               
     *  将响应内容适配成Response
               
                对象返回
               
     * @param id
     * @return
     */
    @GET("person")
    Response<Result<Person>> getPersonResponse(@Query("id") Long id);

    /**
     * Call
               
     * 不执行适配处理,直接返回Call
               
                对象
               
     * @param id
     * @return
     */
    @GET("person")
    Call<Result<Person>> getPersonCall(@Query("id") Long id);

我们也可以通过继承CallAdapter.Factory扩展实现自己的CallAdapter

retrofit-spring-boot-starter支持通过retrofit.global-call-adapter-factories配置全局调用适配器工厂,工厂实例优先从Spring容器获取,如果没有获取到,则反射创建。默认的全局调用适配器工厂是[BodyCallAdapterFactory, ResponseCallAdapterFactory]

retrofit:
  # 全局调用适配器工厂
  global-call-adapter-factories:
    - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
    - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory

针对每个Java接口,还可以通过@RetrofitClient注解的callAdapterFactories()指定当前接口采用的CallAdapter.Factory,指定的工厂实例依然优先从Spring容器获取。

注意:如果CallAdapter.Factory没有public的无参构造器,请手动将其配置成Spring容器的Bean对象

数据转码器

Retrofit使用Converter@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-spring-boot-starter支持通过retrofit.global-converter-factories配置全局数据转换器工厂,转换器工厂实例优先从Spring容器获取,如果没有获取到,则反射创建。默认的全局数据转换器工厂是retrofit2.converter.jackson.JacksonConverterFactory,你可以直接通过spring.jackson.*配置jackson序列化规则,配置可参考Customize the Jackson ObjectMapper

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

针对每个Java接口,还可以通过@RetrofitClient注解的converterFactories()指定当前接口采用的Converter.Factory,指定的转换器工厂实例依然优先从Spring容器获取。

注意:如果Converter.Factory没有public的无参构造器,请手动将其配置成Spring容器的Bean对象

其他功能示例

form参数接口调用

sendMessage(@FieldMap Map param);">
 @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下载接口

download(@Path("fileKey") String fileKey); } ">
@RetrofitClient(baseUrl = "https://img.ljcdn.com/hc-picture/")
public interface DownloadApi {

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

http下载使用

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(); } }">
@SpringBootTest(classes = RetrofitTestApplication.class)
@RunWith(SpringRunner.class)
public class DownloadTest {
    @Autowired
    DownloadApi downLoadApi;

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

注意:@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:https://github.com/square/okhttp/issues/3154

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

反馈建议

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

群号:806714302

QQ群图片

Comments
  • 熔断降级重试设计是否可以优化

    熔断降级重试设计是否可以优化

    我研究了一下关于熔断降级重试的设计,咱们很多都是通过配置的class去帮用户去newInstance(),这个时候想通过构造函数做一些事情就很麻烦。

    咱们现在是这几个功能都走的okhttp的拦截器。熔断降级暂时只支持sentinel。

    有几个问题:

    1. 重试和超时的配置协同,目前来看好像是触发了熔断就不会走重试?
    2. 这几个功能的设计都放在okhttp的interceptor我倒是都可以。 说到底retrofit只是okhttp的一个便捷入口嘛。但是sentinel的初始化类设计的感觉有点摸不到头脑。 也许我们只要给一个具有factory功能的bean作为全局默认配置就可以? 还有一方面是我想去集成resilience4j做熔断,这块才多想了一些。r4j在spring gateway的做法就是类似于这种,只不过他是每次都会new一个spring context。
    opened by DawnSouther 11
  • Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'com.xx.HttpApi' that could not be found.

    Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'com.xx.HttpApi' that could not be found.

    [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :


    APPLICATION FAILED TO START


    Description:

    Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'com.xx.HttpApi' that could not be found.

    Action:

    Consider defining a bean of type 'com.xx.HttpApi' in your configuration.

    Process finished with exit code 1

    @RetrofitClient(
            baseUrl = "https://api.weixin.qq.com/",
            fallbackFactory = HttpDegradeFallbackFactory.class
    )
    @Logging(logStrategy = LogStrategy.BODY)
    /*默认策略情况下,每5s平均响应时间不得超过500ms,否则启用熔断降级*/
    @SentinelDegrade(count = 500)
    public interface HttpApi {
    
        @GET("sns/jscode2session")
        Response<WeChatMiniProgramCode2Session> jsCodeToSession(@Query("js_code") String jsCode, @Query("appid") String appId, @Query("secret") String secret, @Query("grant_type") String grantType);
    }
    

    一台电脑这样正常,另一台这样却报上面错误

    @Component
    public class HttpApiFallback implements HttpApi {
        @Override
        public Response<WeChatMiniProgramCode2Session> jsCodeToSession(String jsCode, String appId, String secret, String grantType) {
            return null;
        }
    }
    
    @RetrofitClient(
            baseUrl = "https://api.weixin.qq.com/",
            fallback = HttpApiFallback.class,
            fallbackFactory = HttpDegradeFallbackFactory.class
    )
    @Logging(logStrategy = LogStrategy.BODY)
    /*默认策略情况下,每5s平均响应时间不得超过500ms,否则启用熔断降级*/
    @SentinelDegrade(count = 500)
    public interface HttpApi {
    
        @GET("sns/jscode2session")
        Response<WeChatMiniProgramCode2Session> jsCodeToSession(@Query("js_code") String jsCode, @Query("appid") String appId, @Query("secret") String secret, @Query("grant_type") String grantType);
    }
    

    那台报错的改成这样正常,改成这样后前一台又出错,不知道是什么问题

    opened by JakeWoki 9
  • 2.3.6 retrofit-spring-boot-starter不支持springboot2.7.0以上版本

    2.3.6 retrofit-spring-boot-starter不支持springboot2.7.0以上版本

    2022-08-07 22:18:39.069 WARN 29032 --- [ restartedMain] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sourceOkHttpClientRegistry' defined in class path resource [com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.lianjiatech.retrofit.spring.boot.core.SourceOkHttpClientRegistry]: Factory method 'sourceOkHttpClientRegistry' threw exception; nested exception is java.lang.NoSuchFieldError: Companion 2022-08-07 22:18:39.078 INFO 29032 --- [ restartedMain] ConditionEvaluationReportLoggingListener :

    Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2022-08-07 22:18:39.123 ERROR 29032 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sourceOkHttpClientRegistry' defined in class path resource [com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.lianjiatech.retrofit.spring.boot.core.SourceOkHttpClientRegistry]: Factory method 'sourceOkHttpClientRegistry' threw exception; nested exception is java.lang.NoSuchFieldError: Companion at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.20.jar:5.3.20] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.20.jar:5.3.20] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.0.jar:2.7.0] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.0.jar:2.7.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.0.jar:2.7.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.0.jar:2.7.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.0.jar:2.7.0] at com.blazings.suanfa.SuanfaApplication.main(SuanfaApplication.java:16) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.0.jar:2.7.0] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.lianjiatech.retrofit.spring.boot.core.SourceOkHttpClientRegistry]: Factory method 'sourceOkHttpClientRegistry' threw exception; nested exception is java.lang.NoSuchFieldError: Companion at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.20.jar:5.3.20] at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.20.jar:5.3.20] ... 23 common frames omitted Caused by: java.lang.NoSuchFieldError: Companion at okhttp3.internal.Util.(Util.kt:70) ~[okhttp-4.9.3.jar:na] at okhttp3.OkHttpClient.(OkHttpClient.kt:1073) ~[okhttp-4.9.3.jar:na] at com.github.lianjiatech.retrofit.spring.boot.core.SourceOkHttpClientRegistry.(SourceOkHttpClientRegistry.java:27) ~[retrofit-spring-boot-starter-2.3.6.jar:na] at com.github.lianjiatech.retrofit.spring.boot.config.RetrofitAutoConfiguration.sourceOkHttpClientRegistry(RetrofitAutoConfiguration.java:73) ~[retrofit-spring-boot-starter-2.3.6.jar:na] at com.github.lianjiatech.retrofit.spring.boot.config.RetrofitAutoConfiguration$$EnhancerBySpringCGLIB$$e630bed7.CGLIB$sourceOkHttpClientRegistry$4() ~[retrofit-spring-boot-starter-2.3.6.jar:na] at com.github.lianjiatech.retrofit.spring.boot.config.RetrofitAutoConfiguration$$EnhancerBySpringCGLIB$$e630bed7$$FastClassBySpringCGLIB$$93a0271d.invoke() ~[retrofit-spring-boot-starter-2.3.6.jar:na] at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.20.jar:5.3.20] at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.20.jar:5.3.20] at com.github.lianjiatech.retrofit.spring.boot.config.RetrofitAutoConfiguration$$EnhancerBySpringCGLIB$$e630bed7.sourceOkHttpClientRegistry() ~[retrofit-spring-boot-starter-2.3.6.jar:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.20.jar:5.3.20] ... 24 common frames omitted

    与目标 VM 断开连接, 地址为: ''127.0.0.1:59215',传输: '套接字''

    进程已结束,退出代码0

    opened by JBlazingSun 9
  • 添加注解后SpringBoot启动失败

    添加注解后SpringBoot启动失败

    在下载的旧项目中添加的依赖,项目没有添加retrofit-spring-boot-starter依赖之前是可以正常运行的,添加之后运行就报错。 Maven中所有依赖:

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.15</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>20190722</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.12</version>
            </dependency>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.3.0</version>
            </dependency>
            <dependency>
                <groupId>com.github.lianjiatech</groupId>
                <artifactId>retrofit-spring-boot-starter</artifactId>
                <version>2.3.0</version>
            </dependency>
        </dependencies>
    

    报错信息:

    java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
    	at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:724) ~[na:1.8.0_322]
    	at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:531) ~[na:1.8.0_322]
    	at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:355) ~[na:1.8.0_322]
    	at sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:286) ~[na:1.8.0_322]
    	at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:120) ~[na:1.8.0_322]
    	at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:72) ~[na:1.8.0_322]
    	at java.lang.reflect.Executable.declaredAnnotations(Executable.java:602) ~[na:1.8.0_322]
    	at java.lang.reflect.Executable.declaredAnnotations(Executable.java:600) ~[na:1.8.0_322]
    	at java.lang.reflect.Executable.getDeclaredAnnotations(Executable.java:588) ~[na:1.8.0_322]
    	at java.lang.reflect.Method.getDeclaredAnnotations(Method.java:630) ~[na:1.8.0_322]
    	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:454) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.AnnotationsScanner.isKnownEmpty(AnnotationsScanner.java:492) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.TypeMappedAnnotations.from(TypeMappedAnnotations.java:251) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:351) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:330) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.AnnotatedElementUtils.findAnnotations(AnnotatedElementUtils.java:764) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.core.annotation.AnnotatedElementUtils.hasAnnotation(AnnotatedElementUtils.java:531) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.BeanAnnotationHelper.isBeanAnnotated(BeanAnnotationHelper.java:41) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.isMatch(ConfigurationClassEnhancer.java:407) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassEnhancer$ConditionalCallbackFilter.accept(ConfigurationClassEnhancer.java:192) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.proxy.Enhancer.emitMethods(Enhancer.java:1217) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:726) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy.generate(ClassLoaderAwareGeneratorStrategy.java:57) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:358) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.10.jar:5.3.10]
    	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) ~[na:1.8.0_322]
    	at java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:1.8.0_322]
    	at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:137) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:447) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:268) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:325) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:147) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.10.jar:5.3.10]
    	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.6.jar:2.6.6]
    	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740) [spring-boot-2.6.6.jar:2.6.6]
    	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415) [spring-boot-2.6.6.jar:2.6.6]
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-2.6.6.jar:2.6.6]
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) [spring-boot-2.6.6.jar:2.6.6]
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) [spring-boot-2.6.6.jar:2.6.6]
    	at com.zking.GeetestApp.main(GeetestApp.java:17) [classes/:na]
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_322]
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_322]
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_322]
    	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_322]
    	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.6.6.jar:2.6.6]
    
    

    没有详细的报错内容,不知道是哪里出了问题?感谢!

    opened by spkingr 8
  • 如何方便的设置http代理

    如何方便的设置http代理

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

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

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

    opened by lly835 8
  • 网络访问异常的时候有没有相应的处理方式

    网络访问异常的时候有没有相应的处理方式

    Caused by: com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitExecuteIOException: java.io.IOException: WWWW.XXX.com HTTP execute fail!Request{method=POST, url=WWW.XXXX.COM

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

    使用含有@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. 可否协助解决?

    opened by leesonwei 7
  • 引入后项目启动失败

    引入后项目启动失败

    作者大大 能帮忙看下吗

    java.lang.ExceptionInInitializerError: null at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.github.lianjiatech.retrofit.spring.boot.core.PathMatchInterceptorBdfProcessor.postProcessBeanDefinitionRegistry(PathMatchInterceptorBdfProcessor.java:28) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:272) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:122) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525) at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) at com.ifreecomm.cm.apis.app.AppConfig.main(AppConfig.java:72)

    opened by yuan-mei 5
  • 考虑支持 maxRequests、maxRequestsPerHost 的配置吗

    考虑支持 maxRequests、maxRequestsPerHost 的配置吗

    内部服务之间用OKHTTP调用,默认的maxRequests、maxRequestsPerHost太小限制了并发量,生产环境对这2个参数做了调整后效果还是比较明显;retrofit-spring-boot-starter 虽然可以用代码自定义注入OkHttpClient并做很多自定义配置,但是不够优雅便捷。

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

    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

    opened by zhuqi7y 5
  • @SentinelDegrade注解不生效

    @SentinelDegrade注解不生效

    1. 增加Sentinel依赖和配置,启动熔断降级功能;
            <dependency>
                <groupId>com.github.lianjiatech</groupId>
                <artifactId>retrofit-spring-boot-starter</artifactId>
                <version>2.3.9</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-core</artifactId>
                <version>1.8.3</version>
            </dependency>
    
    retrofit.degrade.degrade-type = sentinel
    
    1. 在接口上配置@SentinelDegrade注解,异常数超过1触发降级,如下:
    @SentinelDegrade(grade = 2, count = 1)
    @RetrofitClient(baseUrl = "https://api.com", connectTimeoutMs = 3000,
            fallback = ApiFallback.class, fallbackFactory = ApiFallbackFactory.class)
    public interface Api {
    
    }
    
    1. 在单元测试中循环调用此接口的方法,通过抛出java.net.ConnectException异常模拟调不通的情况,未触发ApiFallback中的代码,在Sentinel的${user_home}/logs/csp日志目录中发现异常未计数:
    image

    日志的第7个数值为异常数 image

    1. 通过分析代码发现SentinelRetrofitDegrade类中未通过Tracer对异常进行统计:
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Method method = Objects.requireNonNull(request.tag(Invocation.class)).method();
            SentinelDegrade sentinelDegrade = AnnotationExtendUtils.findMergedAnnotation(method, method.getDeclaringClass(),
                    SentinelDegrade.class);
            if (!needDegrade(sentinelDegrade)) {
                return chain.proceed(request);
            }
            String resourceName = parseResourceName(method);
            Entry entry = null;
            try {
                entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
                return chain.proceed(request);
            } catch (BlockException e) {
                throw new RetrofitBlockException(e);
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
    
    image
    1. 在Sentinel提供的sentinel-okhttp-adapter中的代码,包含了Tracer记录的代码: https://github.com/alibaba/Sentinel/blob/master/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptor.java
    image
    opened by shelltea 4
  • ErrorDecoderInterceptor 不支持接口继承的 RetrofitClient

    ErrorDecoderInterceptor 不支持接口继承的 RetrofitClient

    • com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor#intercept
        @Override
        @SneakyThrows
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Method method = Objects.requireNonNull(request.tag(Invocation.class)).method();
            RetrofitClient retrofitClient =
                    AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), RetrofitClient.class); // 如果接口有继承关系,这里返回 null,导致 NPE
            ErrorDecoder errorDecoder =
                    AppContextUtils.getBeanOrNew(applicationContext, retrofitClient.errorDecoder());
           
    
    opened by achievec 3
  • RetrofitIOException乱码报错

    RetrofitIOException乱码报错

    com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitIOException: CRC: actual 0xace75ff5 != expected 0xc9e75ff5, request=Request{method=GET, url=, tags={class retrofit2.Invocation=getDealerVersion() [0, [2135606]]}} at com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitException.errorExecuting(RetrofitException.java:41) at com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory$BodyCallAdapter.adapt(BodyCallAdapterFactory.java:79)

    retrofit-spring-boot.version=2.2.10 spring-boot-starter-parent.version=2.6.7

    opened by wangyunpeng0319 0
  • How to trust all certificates

    How to trust all certificates

    how can i trust all certificates when use @RetrofitClient. Exception: RetrofitIOException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    opened by Sword-zhj 0
  • Bug with `getDeclaringClass().getAnnotation(RetrofitClient.class)` eg ErrorDecoder / Degrade Interceptor

    Bug with `getDeclaringClass().getAnnotation(RetrofitClient.class)` eg ErrorDecoder / Degrade Interceptor

    First up, kudos on the new Interceptor based design!

    Unfortunately, for any Interceptors that call method.getDeclaringClass().getAnnotation(RetrofitClient.class) there is a potential bug when using client interface heirarchies (affects ErrorDecoder & Degrade Interceptors) eg.

    interface Super {
      @GET("/super") fun getSuper()
    }
    
    @RetrofitClient(...)
    interface Sub: Super
    

    getDeclaringClass() will return Super but that is not the class with the @RetrofitClient annotation and will cause a NPE because the annotation will be null

    opened by efemoney 4
Releases(3.0.0)
Owner
Ke Technologies
Ke Technologies
A springboot-starter that can achieve Intranet penetration. 一款可以实现内网穿透的springboot-starter。

qynat-springboot-starter 基于netty的内网穿透工具在springboot中的整合 protocol协议:protobuf 只需在application.properties中配置少量信息,实现零代码侵入的web项目内网穿透 项目的server端的源码在另一个多模块项目中,

whz11 65 Dec 12, 2022
Simple springboot API for addressBook. Supports all REST controllers and have custom error handling for every specific case, also supports redis caching.

AddressBook-SpringBoot-API Simple Springboot API for addressBook with redis cache. Supports all REST controllers and have custom error handling for ev

Shirish Saxena 1 Jan 21, 2022
BurritoSpigot is a fork of TacoSpigot 1.8.9 that offers several enhancements to performance as well as bug fixes. while offer extra APIs and support for plugins

?? BurritoSpigot ?? BurritoSpigot is a fork of TacoSpigot 1.8.8 that offers several enhancements to performance as well as bug fixes. while offer extr

Cobble Sword Services 44 Dec 20, 2022
Spring JPA Many To Many example with Hibernate and Spring Boot CRUD Rest API - ManyToMany annotation

Spring JPA Many To Many example with Hibernate and Spring Boot CRUD Rest API - ManyToMany annotation

null 17 Dec 28, 2022
Springboot starter security jwt

Springboot starter security jwt

CodingApi 2 Jul 19, 2022
Tuya 37 Dec 26, 2022
Supports the development of Spryker applications with Intellij Integration.

SprykerKit - intellij plugin for spryker file generation integrated into your ide Supports the development of Spryker applications with Intellij IDEA.

valantic CX 3 Oct 13, 2022
Spring-Boot-Plus is a easy-to-use, high-speed, high-efficient,feature-rich, open source spring boot scaffolding

Everyone can develop projects independently, quickly and efficiently! What is spring-boot-plus? A easy-to-use, high-speed, high-efficient, feature-ric

geekidea 2.3k Dec 31, 2022
Feature Flags for the Java platform

Togglz Togglz is an implementation of the Feature Toggles pattern for Java. Feature Toggles are a very common agile development practices in the conte

Togglz 789 Jan 6, 2023
A React Native project starter with Typescript, a theme provider with hook to easy styling component, a folder architecture ready and some configs to keep a codebase clean.

React Native Boilerplate Folder structure : src ├── assets │   ├── audios │   ├── fonts │   ├── icons │   └── images ├── components │   ├── Layout.tsx

LazyRabbit 23 Sep 1, 2022
Create your Java crypto trading bot in minutes. Our Spring boot starter takes care of exchange connections, accounts, orders, trades, and positions so you can focus on building your strategies.

Quick Start | Documentation | Discord | Twitter Create and run your java crypto trading bot in minutes Our Spring boot starter takes care of exchange

Cassandre 442 Jan 3, 2023
React 0.68+ Turbo Module starter using codegen with typescript for Objective-C and Java/Kotlin with C++ shared library. 🚀🚀🚀

React 0.68+ Turbo Module starter using codegen with typescript for Objective-C and Java/Kotlin with C++ shared library. ?? ?? ?? Features React Native

Nagish Inc. 358 Jan 3, 2023
This project archetype is a template for creating a fully functional MVC web application using Hibernate, JSTL and Bootstrap

This project archetype is a template for creating a fully functional MVC web application using Hibernate, JSTL and Bootstrap. It has an automatic database creation, auto initial load of the data, with different variety of users. It also has a checkstyle to check the proper coding of your project immediately right after you enter the code.

null 90 Oct 21, 2022
A lazily evaluated, functional, flexible and concise Lisp.

KamilaLisp A lazily evaluated, functional, flexible and concise Lisp modelled after Haskell and APL, among others. ;; Hello, world! (println "Hello, w

Kamila Szewczyk 42 Dec 15, 2022
A distributed lock that supports the use of Redis and Zookeeper, out of the box, fast and easy to use

lock-spring-boot-starter A distributed lock that supports the use of Redis and Zookeeper, out of the box, fast and easy to use 一款基于 Redis 和 Zookeeper

Pear Stack 9 Oct 15, 2022
Spring Boot starter module for gRPC framework.

Spring Boot starter module for gRPC framework.

Michael Zhang 2.8k Jan 4, 2023
Spring Boot starter module for gRPC framework.

Spring Boot starter module for gRPC framework.

Michael Zhang 1.8k Mar 17, 2021
创建springboot的starters项目,集成各种优秀springboot starter

一个基于springboot的项目快速集成脚手架 简介 spring-boot-starters是一个基于springboot的项目快速集成脚手架 其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。模块列表如下: 模块 说明 common-spring-boot-

justimaging 1 Jul 15, 2021
Spring Boot starter for JustAuth Plus.

Spring Boot starter for JustAuth Plus.

Fujie 5 Jun 23, 2022