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.
- First of all, we prepare a service to facilitate remote calls. We use the previous demo of
mall-tiny-swagger
. Open Swagger and see that there is a login interface and a CRUD interface for the product brand that requires login authentication. The project address: https://github.com /macrozheng/mall-learning/tree/master/mall-tiny-swagger
- Let's try calling the login interface first, and configure the service address of
application.yml
inmall-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;
}
}
- Next, test through Swagger, and call the interface to get the token returned by the remote service. Access address: http://localhost:8086/swagger-ui/
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 inheritsBasePathMatchInterceptor
, and then addAuthorization
header to the request in thedoIntercept
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 inretrofit.global-log-strategy
tobody
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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。