Ditch OkHttp and HttpClient: This Lightweight HTTP Tool is Incredibly Useful

Using okhttp, httpClient, or RestTemplate to initiate HTTP requests directly in a SpringBoot project is cumbersome and inconvenient for unified management. Therefore, I recommend a lightweight HTTP client framework suitable for SpringBoot projects: retrofit-spring-boot-starter. It is very simple and convenient to use, while also providing many enhanced features. The project has currently been updated to version 2.2.2 and will continue to be iteratively optimized.

GitHub project address:

https://github.com/LianjiaTech/retrofit-spring-boot-starter

Gitee project address:

https://gitee.com/lianjiatech/retrofit-spring-boot-starter

Introduction

Retrofit is a type-safe HTTP client for Android and Java, with its biggest feature being support for initiating HTTP requests via interfaces. Spring Boot is the most widely used Java development framework, but the official Retrofit does not support quick integration with the Spring Boot framework, which is why we developed retrofit-spring-boot-starter.

retrofit-spring-boot-starter implements quick integration of Retrofit with the Spring Boot framework and supports many enhanced features, greatly simplifying development.

Features

  • Custom injection of OkHttpClient
  • Annotation-based interceptors
  • Connection pool management
  • Logging
  • Request retries
  • Error decoders
  • Global interceptors
  • Circuit breaker and degradation
  • HTTP calls between microservices
  • Call adapters
  • Data converters

Quick Start

Include Dependency

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

Define HTTP Interface

The interface must be marked with the @RetrofitClient annotation! For HTTP-related annotations, please refer to the official documentation: Retrofit official documentation.

https://square.github.io/retrofit/

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

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

Inject and Use

Simply inject the interface into other services to use it!

@Service
public class TestService {

    @Autowired
    private HttpApi httpApi;

    public void test() {
        // Initiate HTTP request through httpApi
    }
}

HTTP Request Annotations

All HTTP request-related annotations use the native retrofit annotations. For detailed information, please refer to the official documentation, below is a simple explanation.

Ditch OkHttp and HttpClient: This Lightweight HTTP Tool is Incredibly Useful

Configuration Item Description

retrofit-spring-boot-starter supports multiple configurable properties to handle different business scenarios. You can modify them as needed, specific descriptions are as follows:

Ditch OkHttp and HttpClient: This Lightweight HTTP Tool is Incredibly Useful

yml configuration method:

retrofit:
  enable-response-call-adapter: true
  # Enable logging
  enable-log: true
  # Connection pool configuration
  pool:
    test1:
      max-idle-connections: 3
      keep-alive-second: 100
    test2:
      max-idle-connections: 5
      keep-alive-second: 50
  # Disable void return type
  disable-void-return-type: false
  # Logging interceptor
  logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
  # Retry interceptor
  retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
  # Global converter factories
  global-converter-factories:
    - retrofit2.converter.jackson.JacksonConverterFactory
  # Global call adapter factories
  global-call-adapter-factories:
    - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
    - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory
  # Enable circuit breaker degradation
  enable-degrade: true
  # Circuit breaker degradation implementation
  degrade-type: sentinel
  # Circuit breaker resource name parser
  resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser

Advanced Features

Custom Injection of OkHttpClient

In most cases, dynamically creating OkHttpClient objects through the @RetrofitClient annotation properties can meet most usage scenarios. However, in some cases, users may need to customize OkHttpClient. In this case, you can define a static method on the interface that returns OkHttpClient.Builder. Here is a code example:

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

The method must be marked with the @OkHttpClientBuilder annotation!

Annotation-based Interceptors

Many times, we want certain HTTP requests under a specific interface to execute unified interception logic. To support this functionality, retrofit-spring-boot-starter provides annotation-based interceptors, achieving URL path-based matching interception. The steps to use it are mainly divided into 2 steps:

  1. Inherit BasePathMatchInterceptor to write the interceptor handler;
  2. Use @Intercept to annotate the interface. If you need to configure multiple interceptors, simply annotate multiple @Intercept annotations on the interface!

Below is an example of how to use an annotation-based interceptor to append a timestamp to the specified request URL.

Inherit BasePathMatchInterceptor to Write the Interceptor Handler

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

Annotate the Interface with @Intercept

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

The above @Intercept configuration indicates: intercept requests under the HttpApi interface at the /api/** path (excluding /api/test/savePerson), using the TimeStampInterceptor as the interceptor handler.

Extending Annotation-based Interceptors

Sometimes, we need to dynamically pass some parameters into the intercept annotation, which are then used during the interception. In such cases, we can extend to implement a custom intercept annotation. The custom intercept annotation must be marked with @InterceptMark and must include include(), exclude(), and handler() attribute information. The steps to use it are mainly divided into 3 steps:

  1. Define the custom intercept annotation;
  2. Inherit BasePathMatchInterceptor to write the interceptor handler;
  3. Use the custom intercept annotation on the interface;

For example, if we need to dynamically add accessKeyId and accessKeySecret signature information in the request header to initiate HTTP requests normally, we can define a signing interceptor annotation @Sign to achieve this. Below is an example of the custom @Sign intercept annotation.

Custom @Sign Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
    /**
     * Secret key
     * Supports placeholder configuration.
     *
     * @return
     */
    String accessKeyId();

    /**
     * Secret
     * Supports placeholder configuration.
     *
     * @return
     */
    String accessKeySecret();

    /**
     * Interceptor matching paths
     *
     * @return
     */
    String[] include() default {"/**"};

    /**
     * Exclude matching for the interceptor, exclude specified paths from interception
     *
     * @return
     */
    String[] exclude() default {};

    /**
     * Interceptor class that handles this annotation
     * It will first try to get the corresponding Bean from the Spring container, if not found, it will use reflection to create one!
     *
     * @return
     */
    Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}

There are two points to note when extending the custom intercept annotation:

  1. The custom intercept annotation must be marked with @InterceptMark.
  2. The annotation must include include(), exclude(), and handler() attribute information.

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

The values of the accessKeyId and accessKeySecret fields will be automatically injected based on the values of accessKeyId() and accessKeySecret() in the @Sign annotation. If the @Sign specifies a placeholder string, the configuration property value will be used for injection. Additionally, **the accessKeyId and accessKeySecret fields must provide setter methods**.

Use @Sign on the Interface

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

This way, the signature information will be automatically added to the requests for the specified URL.

Connection Pool Management

By default, all HTTP requests sent through Retrofit will use the default connection pool with max-idle-connections=5 keep-alive-second=300. Of course, we can also configure multiple custom connection pools in the configuration file and specify which one to use through the poolName attribute of @RetrofitClient. For example, if we want all requests under a certain interface to use the poolName=test1 connection pool, the code implementation is as follows:

  1. Configure the connection pool.
retrofit:
    # Connection pool configuration
    pool:
        test1:
        max-idle-connections: 3
        keep-alive-second: 100
        test2:
        max-idle-connections: 5
        keep-alive-second: 50
  1. Specify the connection pool to use through the poolName attribute of @RetrofitClient.
@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1")
public interface HttpApi {

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

Logging

In many cases, we want to log HTTP request details. The retrofit.enableLog configuration can globally control whether logging is enabled. For each interface, you can control whether logging is enabled through the enableLog attribute of @RetrofitClient, and specify the logging level and strategy through logLevel and logStrategy. retrofit-spring-boot-starter supports five logging levels (ERROR, WARN, INFO, DEBUG, TRACE), with INFO as the default; it supports four logging strategies (NONE, BASIC, HEADERS, BODY), with BASIC as the default. The meanings of the four logging strategies are as follows:

  1. NONE: No logs.
  2. BASIC: Logs request and response lines.
  3. HEADERS: Logs request and response lines and their respective headers.
  4. BODY: Logs request and response lines and their respective headers and bodies (if present).

retrofit-spring-boot-starter uses DefaultLoggingInterceptor to perform the actual logging functionality, which is based on the native HttpLoggingInterceptor of okhttp. Of course, you can also implement your own logging interceptor by inheriting BaseLoggingInterceptor (for specific implementation, refer to DefaultLoggingInterceptor), and then configure it in the configuration file.

retrofit:
  # Logging interceptor
  logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor

Request Retries

retrofit-spring-boot-starter supports request retry functionality, simply add the @Retry annotation to the interface or method. **@Retry supports configuration for retry count maxRetries, retry interval intervalMs, and retry rules retryRules**. The retry rules support three configurations:

  1. RESPONSE_STATUS_NOT_2XX: Retry when the response status code is not 2xx;
  2. OCCUR_IO_EXCEPTION: Retry when an IO exception occurs;
  3. OCCUR_EXCEPTION: Retry when any exception occurs;

By default, retries are automatically performed when the response status code is not 2xx or when an IO exception occurs. If needed, you can also inherit BaseRetryInterceptor to implement your own request retry interceptor and configure it accordingly.

retrofit:
  # Request retry interceptor
  retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor

Error Decoders

When an HTTP request error occurs (including exceptions or response data not meeting expectations), the error decoder can decode HTTP related information into a custom exception. You can specify the current interface’s error decoder in the @RetrofitClient annotation’s errorDecoder(); the custom error decoder needs to implement the ErrorDecoder interface:

/**
 * Error decoder. ErrorDecoder.
 * 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.
 */
public interface ErrorDecoder {

    /**
     * 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;
    }

    /**
     * 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);
    }

    /**
     * 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);
    }
}

Global Interceptors

Global Application Interceptors

If we need to perform unified interception processing on all HTTP requests in the system, we can customize and implement a global interceptor BaseGlobalInterceptor and configure it as a bean in the spring container! For example, if we need to add source information to all HTTP requests initiated in the system.

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

Global Network Interceptors

Simply implement the NetworkInterceptor interface and configure it as a bean in the spring container to support automatic weaving of global network interceptors.

Circuit Breaker and Degradation

In a distributed service architecture, performing circuit breaker degradation on unstable external services is one of the important measures to ensure high availability of services. Since the stability of external services cannot be guaranteed, when external services are unstable, response times will increase. Consequently, the response time for the caller will also increase, leading to thread accumulation, which may eventually exhaust the caller’s thread pool, causing the entire service to become unavailable. Therefore, we need to perform circuit breaker degradation on unstable weak dependency service calls, temporarily cutting off unstable calls to prevent local instability from causing an overall service avalanche.

retrofit-spring-boot-starter supports circuit breaker degradation functionality, based on Sentinel. Specifically, it supports circuit breaker resource self-discovery and annotation-based degradation rule configuration. To use circuit breaker degradation, simply perform the following operations:

1. Enable Circuit Breaker Degradation

By default, the circuit breaker degradation feature is turned off, and you need to set the corresponding configuration items to enable it:

retrofit:
  # Enable circuit breaker degradation
  enable-degrade: true
  # Circuit breaker degradation implementation (currently only supports Sentinel)
  degrade-type: sentinel
  # Resource name parser
  resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser

The resource name parser is used to implement user-defined resource names; the default configuration is DefaultResourceNameParser, with the corresponding resource name format being HTTP_OUT:GET:http://localhost:8080/api/degrade/test. Users can inherit the BaseResourceNameParser class to implement their own resource name parser.

Additionally, since the circuit breaker degradation feature is optional, enabling it requires users to manually introduce the Sentinel dependency:

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

2. Configure Degradation Rules (Optional)

retrofit-spring-boot-starter supports annotation-based configuration of degradation rules, configured using the @Degrade annotation. The @Degrade annotation can be configured on interfaces or methods, with method-level configuration taking precedence.

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

If the application project already supports configuring degradation rules through a configuration center, the annotation-based configuration method can be ignored.

3. Set Fallback or FallbackFactory in @RetrofitClient (Optional)

If @RetrofitClient does not set fallback or fallbackFactory, when a circuit breaker is triggered, it will directly throw a RetrofitBlockException exception. Users can customize the return value of the method when a circuit breaker is triggered by setting fallback or fallbackFactory. The fallback class must be an implementation of the current interface, and the fallbackFactory must be an implementation of FallbackFactory<T>, with the generic parameter type being the current interface type. Additionally, the fallback and fallbackFactory instances must be configured as Bean in the Spring container.

**The main difference between fallbackFactory and fallback is that it can perceive the cause of each circuit breaker exception (cause)**. Here is a reference example:

@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> {

    /**
     * Returns an instance of the fallback appropriate for the given cause
     * @param cause fallback cause
     * @return an instance that implements the retrofit interface.
     */
    @Override
    public HttpDegradeApi create(Throwable cause) {
        log.error("Circuit breaker triggered! ", 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 Calls Between Microservices

To enable microservice calls, the following configurations are required:

Configure ServiceInstanceChooser as a Bean in the Spring Container

Users can implement the ServiceInstanceChooser interface themselves to complete the service instance selection logic and configure it as a Bean in the Spring container. For Spring Cloud applications, retrofit-spring-boot-starter provides the SpringCloudServiceInstanceChooser implementation, and users only need to configure it as a Bean in Spring.

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

Use @Retrofit with serviceId and path Attributes for HTTP Calls Between Microservices

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

Call Adapters and Data Converters

Call Adapters

Retrofit can adapt Call<T> objects to the return value types of interface methods through call adapters CallAdapterFactory. retrofit-spring-boot-starter extends two implementations of CallAdapterFactory:

  1. BodyCallAdapterFactory
  • Enabled by default, can be disabled by configuring retrofit.enable-body-call-adapter=false
  • Synchronously executes HTTP requests, adapting the response body content to instances of the return value type of the interface method.
  • Besides Retrofit.Call<T>, Retrofit.Response<T>, and java.util.concurrent.CompletableFuture<T>, other return types can use this adapter.
  1. ResponseCallAdapterFactory
  • Enabled by default, can be disabled by configuring retrofit.enable-response-call-adapter=false
  • Synchronously executes HTTP requests, adapting the response body content to return Retrofit.Response<T>.
  • If the method’s return type is Retrofit.Response<T>, this adapter can be used.

Retrofit automatically selects the corresponding CallAdapterFactory based on the method’s return value type! In addition to the default CallAdapterFactory, it supports various forms of method return value types:

  • Call<T>: No adaptation processing, directly returns Call<T> object
  • CompletableFuture<T>: Adapts the response body content to return a CompletableFuture<T> object
  • Void: Can use Void if the return type is not a concern. If the HTTP status code is not 2xx, it throws an error!
  • Response<T>: Adapts the response content to return a Response<T> object
  • Any other Java type: Adapts the response body content to return a corresponding Java type object; if the HTTP status code is not 2xx, it throws an error!
/**
* Call<T>
* No adaptation processing, directly returns Call<T> object
* @param id
* @return
*/
@GET("person")
Call<Result<Person>> getPersonCall(@Query("id") Long id);

/**
*  CompletableFuture<T>
*  Adapts the response body content to return a CompletableFuture<T> object
* @param id
* @return
*/
@GET("person")
CompletableFuture<Result<Person>> getPersonCompletableFuture(@Query("id") Long id);

/**
* Void
* Does not care about the return type, can use Void. If the HTTP status code is not 2xx, it throws an error!
* @param id
* @return
*/
@GET("person")
Void getPersonVoid(@Query("id") Long id);

/**
*  Response<T>
*  Adapts the response content to return a Response<T> object
* @param id
* @return
*/
@GET("person")
Response<Result<Person>> getPersonResponse(@Query("id") Long id);

/**
* Any other Java type
* Adapts the response body content to return a corresponding Java type object; if the HTTP status code is not 2xx, it throws an error!
* @param id
* @return
*/
@GET("person")
Result<Person> getPerson(@Query("id") Long id);

**We can also extend and implement our own CallAdapter by inheriting CallAdapter.Factory!**

retrofit-spring-boot-starter supports configuring global call adapter factories through retrofit.global-call-adapter-factories, with factory instances prioritized to be obtained from the Spring container; if not found, they will be created via reflection. The default global call adapter factories are [BodyCallAdapterFactory, ResponseCallAdapterFactory]!

retrofit:
  # Global call adapter factories
  global-call-adapter-factories:
    - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
    - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory

For each Java interface, you can also specify the CallAdapter.Factory used by the current interface through the callAdapterFactories() attribute of the @RetrofitClient annotation, with the specified factory instance still prioritized to be obtained from the Spring container.

Note: If the CallAdapter.Factory does not have a public no-argument constructor, please manually configure it as a Bean object in the Spring container!

Data Converters

Retrofit uses Converter to convert objects marked with the @Body annotation into request bodies and to convert response body data into a Java object. You can choose from the following types of 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

retrofit-spring-boot-starter supports configuring global data converter factories through retrofit.global-converter-factories, with converter factory instances prioritized to be obtained from the Spring container; if not found, they will be created via reflection. The default global data converter factory is retrofit2.converter.jackson.JacksonConverterFactory, and you can directly configure spring.jackson.* to set jackson serialization rules. For configuration details, refer to: Customize the Jackson ObjectMapper

https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#howto-customize-the-jackson-objectmapper

retrofit:
  # Global call adapter factories
  global-call-adapter-factories:
    - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory
    - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory

For each Java interface, you can also specify the Converter.Factory used by the current interface through the converterFactories() attribute of the @RetrofitClient annotation, with the specified converter factory instance still prioritized to be obtained from the Spring container.

Note: If the Converter.Factory does not have a public no-argument constructor, please manually configure it as a Bean object in the Spring container!

Conclusion

retrofit-spring-boot-starter is a lightweight HTTP client framework suitable for SpringBoot projects, which has been stably running online for over a year and has been adopted by several external companies. Interested friends can give it a try.

Source: juejin.cn/post/6898485806587969544

Recommended Articles

1. 400 million QQ numbers, limited to 1G memory, how to deduplicate?
2. Why is Bean Searcher so powerful? Any complex query can be done in one line of code!
3. A big shot from Alibaba joined the company, inserting 300,000 records into MySQL in just 13 seconds, let’s take a look at the core code!
4. SpringBoot + Rule Engine URule, really too powerful!
5. Code simplified by 10 times, the responsibility chain pattern is the best!
6. After 3 months of using ChatGPT, it helped me generate 95% of the code, I can’t live without it!
7. A new technical director: anyone who uses delete to remove data will be fired directly!
8. After 5 years of development, can’t delete elements from a List in a loop, is it that hard?

Ditch OkHttp and HttpClient: This Lightweight HTTP Tool is Incredibly Useful

Leave a Comment