8
头图
When we usually develop a project, even if it is a single application, it is inevitable to call the interfaces provided by other services. At this point, the HTTP client tool will be used. The HttpUtil in Hutool has been used before. Although it is easy to use, it is quite troublesome to use! Recently, I found a better HTTP client tool Retrofit . You only need to declare the interface to initiate HTTP requests without repeated operations such as connection and result parsing. It is elegant enough to use. I recommend it to everyone!

SpringBoot actual e-commerce project mall (50k+star) address: https://github.com/macrozheng/mall

Introduction

Retrofit is a type-safe HTTP client tool for Android and Java , already 39k+ Star on Github. Its biggest feature is that it supports initiating HTTP requests through interfaces, similar to the way we use Feign to call microservice interfaces.

SpringBoot is the most widely used Java development framework, but Retrofit does not officially provide a dedicated Starter. So an old brother developed retrofit-spring-boot-starter , which realizes the rapid integration of Retrofit and SpringBoot framework, and supports many functional enhancements, which greatly simplifies development. Today we will use this third-party Starter to operate Retrofit.

use

Using Retrofit in SpringBoot is very simple, let's experience it below.

Dependency integration

With the support of third-party Starter, integrating Retrofit only takes one step, just add the following dependencies.

<!--Retrofit依赖-->
<dependency>
    <groupId>com.github.lianjiatech</groupId>
    <artifactId>retrofit-spring-boot-starter</artifactId>
    <version>2.2.18</version>
</dependency>

basic use

Let's take calling the interface in mall-tiny-swagger as an example, let's experience the basic use of Retrofit.

  • Let's try calling the login interface first, and configure the service address of application.yml in mall-tiny-swagger ;
remote:
  baseUrl: http://localhost:8088/
  • Then declare a Retrofit client through @RetrofitClient . Since the login interface is called through a POST form, the annotations @POST and @FormUrlEncoded are used here;
/**
 * 定义Http接口,用于调用远程的UmsAdmin服务
 * 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);
}
  • If you don't quite understand what these annotations are for, you can basically understand by looking at the table below. For more details, you can refer to the official Retrofit documentation;

  • Next, inject UmsAdminApi into the Controller, and then call it;
/**
 * Retrofit测试接口
 * Created by macro on 2022/1/19.
 */
@Api(tags = "RetrofitController", description = "Retrofit测试接口")
@RestController
@RequestMapping("/retrofit")
public class RetrofitController {

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

    @ApiOperation(value = "调用远程登录接口获取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());
        }
        return result;
    }
}
  • In order to facilitate subsequent calls to interfaces that require login authentication, I created the class TokenHolder and stored the token in the Session;
/**
 * 登录token存储(在Session中)
 * Created by macro on 2022/1/19.
 */
@Component
public class TokenHolder {
    /**
     * 添加token
     */
    public void putToken(String token) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        request.getSession().setAttribute("token", token);
    }

    /**
     * 获取token
     */
    public String getToken() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        Object token = request.getSession().getAttribute("token");
        if(token!=null){
            return (String) token;
        }
        return null;
    }

}

Annotated Interceptors

The product brand management interface needs to add a login authentication header to access it normally. We can use the annotation interceptor in Retrofit to achieve this.
  • First create an annotated interceptor TokenInterceptor that inherits BasePathMatchInterceptor , and then add Authorization header to the request in the doIntercept method;
/**
 * 给请求添加登录Token头的拦截器
 * 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();
        }
        return chain.proceed(request);
    }
}
  • Create a client PmsBrandApi that calls the brand management interface, and use the @Intercept annotation to configure the interceptor and interception path;
/**
 * 定义Http接口,用于调用远程的PmsBrand服务
 * 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);
}
  • Then inject the PmsBrandApi instance into the Controller, and add a method to call the remote service;
/**
 * Retrofit测试接口
 * Created by macro on 2022/1/19.
 */
@Api(tags = "RetrofitController", description = "Retrofit测试接口")
@RestController
@RequestMapping("/retrofit")
public class RetrofitController {
    
    @Autowired
    private PmsBrandApi pmsBrandApi;

    @ApiOperation("调用远程接口分页查询品牌列表")
    @GetMapping(value = "/brand/list")
    public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                        @ApiParam("页码") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "3")
                                                        @ApiParam("每页数量") Integer pageSize) {
        return pmsBrandApi.list(pageNum, pageSize);
    }

    @ApiOperation("调用远程接口获取指定id的品牌详情")
    @GetMapping(value = "/brand/{id}")
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
        return pmsBrandApi.detail(id);
    }

    @ApiOperation("调用远程接口添加品牌")
    @PostMapping(value = "/brand/create")
    public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.create(pmsBrand);
    }
    @ApiOperation("调用远程接口更新指定id品牌信息")
    @PostMapping(value = "/brand/update/{id}")
    public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.update(id,pmsBrand);
    }

    @ApiOperation("调用远程接口删除指定id的品牌")
    @GetMapping(value = "/delete/{id}")
    public CommonResult deleteBrand(@PathVariable("id") Long id) {
        return  pmsBrandApi.delete(id);
    }
}
  • Call the interface in Swagger for testing and find that it can be successfully called.

global interceptor

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

Create SourceInterceptor class to inherit the BaseGlobalInterceptor interface, and then add source request header to the Header.

/**
 * 全局拦截器,给请求添加source头
 * 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();
        return chain.proceed(newReq);
    }
}

configure

There are many configurations of Retrofit. Let's talk about the three most commonly used configurations of log printing, global timeout and global request retry.

log print

  • In the default configuration, Retrofit uses the basic log strategy, and the printed log is very simple;

  • We can change the application.yml attribute in retrofit.global-log-strategy to body to print the most complete log;
retrofit:
  # 日志打印配置
  log:
    # 启用日志打印
    enable: true
    # 日志打印拦截器
    logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
    # 全局日志打印级别
    global-log-level: info
    # 全局日志打印策略
    global-log-strategy: body
  • After modifying the log printing policy, the log information is more comprehensive;

  • Retrofit supports four log printing strategies;

    • NONE: do not print the log;
    • BASIC: only print log request records;
    • HEADERS: Print log request records, request and response header information;
    • BODY: Print log request records, request and response header information, request and response body information.

global timeout

Sometimes we need to modify the request timeout time of Retrofit, which can be achieved by the following configuration.

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

global request retry

  • retrofit-spring-boot-starter supports request retry, which can be implemented by the following configuration.
retrofit:
  # 重试配置
  retry:
    # 是否启用全局重试
    enable-global-retry: true
    # 全局重试间隔时间
    global-interval-ms: 100
    # 全局最大重试次数
    global-max-retries: 2
    # 全局重试规则
    global-retry-rules:
      - response_status_not_2xx
      - occur_exception
    # 重试拦截器
    retry-interceptor: com.github.lianjiatech.retrofit.spring.boot.retry.DefaultRetryInterceptor
  • The retry rule global-retry-rules supports the following three configurations.

    • RESPONSE_STATUS_NOT_2XX: Retry is performed when the response status code is not 2xx;
    • OCCUR_IO_EXCEPTION: Retry is performed when an IO exception occurs;
    • OCCUR_EXCEPTION: Perform a retry on any exception.

Summarize

I experienced a Retrofit today, and compared to using HttpUtil, it is indeed a lot more elegant! Initiating HTTP requests through interfaces is no longer exclusive to Feign. Through Retrofit, we can still use this method in monolithic applications. Of course, the functions provided by retrofit-spring-boot-starter are far more than that. It can also support calls and fuse downgrades between microservices. Interested friends can study it!

References

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

Project source code address

https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-retrofit

This article GitHub https://github.com/macrozheng/mall-learning has been included, welcome everyone to Star!

macrozheng
1.1k 声望1.3k 粉丝