When we develop a project, even a single application will have to call the interface provided by other services. HttpUtil from Hutool is easy to use but cumbersome to use! Retrofit is a new HTTP client that allows you to make HTTP requests by simply declaring an interface, without having to rework the connection, result parsing, and so on.

SpringBoot e-commerce project mall (50K + STAR) address: github.com/macrozheng/…

Introduction to the

Retrofit is a type-safe HTTP client tool for Android and Java, available on Github with 39K +Star. Its biggest feature is that it supports HTTP requests via interfaces, similar to the way we used Feign to invoke the microservice interface.

SpringBoot is the most widely used Java development framework, but Retrofit does not officially provide a dedicated Starter. One of my friends developed Retrofit-Spring-boot-Starter, which quickly integrates Retrofit with the SpringBoot framework and supports enhancements that greatly simplify development. Today we will use this third party Starter to operate Retrofit.

use

Using Retrofit in SpringBoot is pretty simple, so let’s try it out.

Relying on the integration

With third-party Starter support, integrating Retrofit is a one-step process by adding the following dependencies.

<! - Retrofit dependence - >
<dependency>
    <groupId>com.github.lianjiatech</groupId>
    <artifactId>retrofit-spring-boot-starter</artifactId>
    <version>2.2.18</version>
</dependency>
Copy the code

The basic use

Let’s take a look at the basic use of Retrofit using the example of calling the interface in mall-Tiny-Swagger.

  • First we prepare a service to facilitate remote invocation, using the previous onemall-tiny-swaggerIn this Demo, open Swagger and have a look. There is a login interface and CRUD interface of commodity brand that requires login authentication. The project address is:Github.com/macrozheng/…

  • Let’s try calling the login interface first, inapplication.ymlThe configuredmall-tiny-swaggerThe service address of;
remote:
  baseUrl: http://localhost:8088/
Copy the code
  • through@RetrofitClientDeclare a Retrofit client, which is used here since the login interface is invoked via a POST form@POSTand@FormUrlEncodedAnnotations;
/** * Created by macro on 2022/1/19
@RetrofitClient(baseUrl = "${remote.baseUrl}")
public interface UmsAdminApi {

    @FormUrlEncoded
    @POST("admin/login")
    CommonResult<LoginInfo> login(@Field("username") String username, @Field("password") String password);
}
Copy the code
  • If you’re not sure what these notes are for, look at the table below to get a sense of what they are, and more specifically refer to Retrofit’s official documentation;

  • Next inject it into the ControllerUmsAdminApi, and then call it;
/** * Retrofit test interface * Created by macro on 2022/1/19
@API (Tags = "RetrofitController", Description = "Retrofit test interface ")
@RestController
@RequestMapping("/retrofit")
public class RetrofitController {

    @Autowired
    private UmsAdminApi umsAdminApi;
    @Autowired
    private TokenHolder tokenHolder;

    @apiOperation (value = "Call remote login interface to obtain token")
    @PostMapping(value = "/admin/login")
    public CommonResult<LoginInfo> login(@RequestParam String username, @RequestParam String password) {
        CommonResult<LoginInfo> result = umsAdminApi.login(username, password);
        LoginInfo loginInfo = result.getData();
        if(result.getData() ! =null) {
            tokenHolder.putToken(loginInfo.getTokenHead() + "" + loginInfo.getToken());
        }
        returnresult; }}Copy the code
  • To facilitate subsequent calls to interfaces that require login authentication, I createdTokenHolderThis class stores tokens in the Session;
/** * Created by macro on 2022/1/19. */
@Component
public class TokenHolder {
    /** * Add token */
    public void putToken(String token) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        request.getSession().setAttribute("token", token);
    }

    /** * obtain token */
    public String getToken(a) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        Object token = request.getSession().getAttribute("token");
        if(token! =null) {return (String) token;
        }
        return null; }}Copy the code
  • Then tested through Swagger, call interface can get to the remote service returns a token, access address: http://localhost:8086/swagger-ui/

Annotated interceptors

The merchandise brand management interface, which requires a login authentication header to be accessed, can be implemented using annotated interceptors in Retrofit.

  • Start by creating an annotated interceptorTokenInterceptorinheritanceBasePathMatchInterceptorAnd then indoInterceptMethod to add to the requestAuthorizationHead;
/** * Created by macro on 2022/1/19
@Component
public class TokenInterceptor extends BasePathMatchInterceptor {
    @Autowired
    private TokenHolder tokenHolder;

    @Override
    protected Response doIntercept(Chain chain) throws IOException {
        Request request = chain.request();
        if(tokenHolder.getToken() ! =null) {
            request = request.newBuilder()
                    .header("Authorization", tokenHolder.getToken())
                    .build();
        }
        returnchain.proceed(request); }}Copy the code
  • Create a client that invokes the brand management interfacePmsBrandApi, the use of@InterceptAnnotations configure interceptors and interception paths;
/** * Created by macro on 2022/1/19. */
@RetrofitClient(baseUrl = "${remote.baseUrl}")
@Intercept(handler = TokenInterceptor.class, include = "/brand/**")
public interface PmsBrandApi {
    @GET("brand/list")
    CommonResult<CommonPage<PmsBrand>> list(@Query("pageNum") Integer pageNum, @Query("pageSize") Integer pageSize);

    @GET("brand/{id}")
    CommonResult<PmsBrand> detail(@Path("id") Long id);

    @POST("brand/create")
    CommonResult create(@Body PmsBrand pmsBrand);

    @POST("brand/update/{id}")
    CommonResult update(@Path("id") Long id, @Body PmsBrand pmsBrand);

    @GET("brand/delete/{id}")
    CommonResult delete(@Path("id") Long id);
}
Copy the code
  • Inject it into the ControllerPmsBrandApiInstance and add methods to call the remote service;
/** * Retrofit test interface * Created by macro on 2022/1/19
@API (Tags = "RetrofitController", Description = "Retrofit test interface ")
@RestController
@RequestMapping("/retrofit")
public class RetrofitController {
    
    @Autowired
    private PmsBrandApi pmsBrandApi;

    @apiOperation (" Call remote interface paging query brand list ")
    @GetMapping(value = "/brand/list")
    public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                        @ ApiParam (" page ") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "3")
                                                        @apiparam (" number per page ") Integer pageSize) {
        return pmsBrandApi.list(pageNum, pageSize);
    }

    @apiOperation (" Call remote interface to get brand details for specified ID ")
    @GetMapping(value = "/brand/{id}")
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
        return pmsBrandApi.detail(id);
    }

    @apiOperation (" Call remote interface to add brand ")
    @PostMapping(value = "/brand/create")
    public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.create(pmsBrand);
    }
    @apiOperation (" Call remote interface to update brand information of specified ID ")
    @PostMapping(value = "/brand/update/{id}")
    public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.update(id,pmsBrand);
    }

    @apiOperation (" Call remote interface to delete brand with specified ID ")
    @GetMapping(value = "/delete/{id}")
    public CommonResult deleteBrand(@PathVariable("id") Long id) {
        returnpmsBrandApi.delete(id); }}Copy the code
  • Call the interface in Swagger to test and find that it can be called successfully.

Global interceptor

If you want to add a header to all requests, you can use a global interceptor.

Create the SourceInterceptor class that inherits the BaseGlobalInterceptor interface and then add the Source request Header to the Header.

* Created by macro on 2022/1/19. */
@Component
public class SourceInterceptor extends BaseGlobalInterceptor {
    @Override
    protected Response doIntercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request newReq = request.newBuilder()
                .addHeader("source"."retrofit")
                .build();
        returnchain.proceed(newReq); }}Copy the code

configuration

Retrofit has many configurations, but let’s talk about log printing, global timeout, and global request retries as the three most common configurations.

Log print

  • Retrofit is used under default configurationbasicLog policy, printing logs is very simple;

  • We can putapplication.ymlIn theretrofit.global-log-strategyChange the property tobodyTo print the most complete log;
retrofit:
  Log print configuration
  log:
    # Enable log printing
    enable: true
    # log print interceptor
    logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
    # global log print level
    global-log-level: info
    # Global log printing policy
    global-log-strategy: body
Copy the code
  • After the log printing policy is modified, the log information is more comprehensive.

  • Retrofit supports four log printing strategies;
    • NONE: Logs are not printed.
    • BASIC: Only log request records are printed.
    • HEADERS: prints log request records, request HEADERS, and response HEADERS.
    • BODY: Displays log request records, request and response headers, and request and response BODY information.

Global timeout

Sometimes we need to modify Retrofit’s request timeout through the following configuration.

retrofit:
  Global connection timeout
  global-connect-timeout-ms: 3000
  Global read timeout
  global-read-timeout-ms: 3000
  # global write timeout
  global-write-timeout-ms: 35000
  Global complete call timeout
  global-call-timeout-ms: 0
Copy the code

Global request retry

  • retrofit-spring-boot-starterRequest retry is supported by the following configuration.
retrofit:
  Retry configuration
  retry:
    # Whether to enable global retry
    enable-global-retry: true
    Global retry interval
    global-interval-ms: 100
    # Maximum number of global retries
    global-max-retries: 2
    # global retry rule
    global-retry-rules:
      - response_status_not_2xx
      - occur_exception
    Retry interceptor
    retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
Copy the code
  • Retry rulesglobal-retry-rulesThe following three configurations are supported.
    • RESPONSE_STATUS_NOT_2XX: Retry if the response status code is not 2XX;
    • OCCUR_IO_EXCEPTION: Retry is performed when I/O exceptions occur.
    • OCCUR_EXCEPTION: Retries are performed when any exception occurs.

conclusion

I tried out Retrofit today, and compared to HttpUtil, it was pretty elegant! Making HTTP requests through the interface is no longer the exclusive domain of Feign, but with Retrofit we can use it in standalone applications. Retrofit-spring-boot-starter does a lot more than that. It also supports calls between microservices and fuse downscaling.

The resources

Official documentation: github.com/LianjiaTech…

Project source code address

Github.com/macrozheng/…

In this paper, making github.com/macrozheng/… Already included, welcome everyone Star!