我們平時開發專案時,就算是單體應用,也免不了要呼叫一下其他服務提供的介面。此時就會用到HTTP客戶端工具,之前一直使用的是Hutool中的HttpUtil,雖然容易上手,但用起來頗為麻煩!最近發現一款更好用的HTTP客戶端工具Retrofit
,你只需宣告介面就可發起HTTP請求,無需進行連線、結果解析之類的重複操作,用起來夠優雅,推薦給大家!
SpringBoot實戰電商專案mall(50k+star)地址:https://github.com/macrozheng/mall
簡介
Retrofit是適用於Android
和Java
且型別安全的HTTP客戶端工具,在Github上已經有39k+
Star。其最大的特性的是支援通過介面的方式發起HTTP請求,類似於我們用Feign呼叫微服務介面的那種方式。
SpringBoot是使用最廣泛的Java開發框架,但是Retrofit官方並沒有提供專門的Starter。於是有位老哥就開發了retrofit-spring-boot-starter
,它實現了Retrofit與SpringBoot框架的快速整合,並且支援了諸多功能增強,極大簡化開發。今天我們將使用這個第三方Starter來操作Retrofit。
使用
在SpringBoot中使用Retrofit是非常簡單的,下面我們就來體驗下。
依賴整合
有了第三方Starter的支援,整合Retrofit僅需一步,新增如下依賴即可。
<!--Retrofit依賴-->
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>2.2.18</version>
</dependency>
基本使用
下面以呼叫mall-tiny-swagger
中的介面為例,我們來體驗下Retrofit的基本使用。
- 首先我們準備一個服務來方便遠端呼叫,使用的是之前的
mall-tiny-swagger
這個Demo,開啟Swagger看下,裡面有一個登入介面和需要登入認證的商品品牌CRUD介面,專案地址:https://github.com/macrozheng...
- 我們先來呼叫下登入介面試試,在
application.yml
中配置好mall-tiny-swagger
的服務地址;
remote:
baseUrl: http://localhost:8088/
- 再通過
@RetrofitClient
宣告一個Retrofit客戶端,由於登入介面是通過POST表單形式呼叫的,這裡使用到了@POST
和@FormUrlEncoded
註解;
/**
* 定義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);
}
- 如果你不太明白這些註解是幹嘛的,看下下面的表基本就懂了,更具體的話可以參考Retrofit官方文件;
- 接下來在Controller中注入
UmsAdminApi
,然後進行呼叫即可;
/**
* 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;
}
}
- 為方便後續呼叫需要登入認證的介面,我建立了
TokenHolder
這個類,把token儲存到了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;
}
}
- 接下來通過Swagger進行測試,呼叫介面就可以獲取到遠端服務返回的token了,訪問地址:http://localhost:8086/swagger...
註解式攔截器
商品品牌管理介面,需要新增登入認證頭才可以正常訪問,我們可以使用Retrofit中的註解式攔截器來實現。
- 首先建立一個註解式攔截器
TokenInterceptor
繼承BasePathMatchInterceptor
,然後在doIntercept
方法中給請求新增Authorization
頭;
/**
* 給請求新增登入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);
}
}
- 建立呼叫品牌管理介面的客戶端
PmsBrandApi
,使用@Intercept
註解配置攔截器和攔截路徑;
/**
* 定義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);
}
- 再在Controller中注入
PmsBrandApi
例項,並新增方法呼叫遠端服務即可;
/**
* 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);
}
}
- 在Swagger中呼叫介面進行測試,發現已經可以成功呼叫。
全域性攔截器
如果你想給所有請求都加個請求頭的話,可以使用全域性攔截器。
建立SourceInterceptor
類繼承BaseGlobalInterceptor
介面,然後在Header中新增source
請求頭。
/**
* 全域性攔截器,給請求新增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);
}
}
配置
Retrofit的配置很多,下面我們講講日誌列印、全域性超時時間和全域性請求重試這三種最常用的配置。
日誌列印
- 預設配置下Retrofit使用
basic
日誌策略,列印的日誌非常簡單;
- 我們可以將
application.yml
中的retrofit.global-log-strategy
屬性修改為body
來列印最全日誌;
retrofit:
# 日誌列印配置
log:
# 啟用日誌列印
enable: true
# 日誌列印攔截器
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
# 全域性日誌列印級別
global-log-level: info
# 全域性日誌列印策略
global-log-strategy: body
- 修改日誌列印策略後,日誌資訊更全面了;
Retrofit支援四種日誌列印策略;
- NONE:不列印日誌;
- BASIC:只列印日誌請求記錄;
- HEADERS:列印日誌請求記錄、請求和響應頭資訊;
- BODY:列印日誌請求記錄、請求和響應頭資訊、請求和響應體資訊。
全域性超時時間
有時候我們需要修改一下Retrofit的請求超時時間,可以通過如下配置實現。
retrofit:
# 全域性連線超時時間
global-connect-timeout-ms: 3000
# 全域性讀取超時時間
global-read-timeout-ms: 3000
# 全域性寫入超時時間
global-write-timeout-ms: 35000
# 全域性完整呼叫超時時間
global-call-timeout-ms: 0
全域性請求重試
retrofit-spring-boot-starter
支援請求重試,可以通過如下配置實現。
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
重試規則
global-retry-rules
支援如下三種配置。- RESPONSE_STATUS_NOT_2XX:響應狀態碼不是2xx時執行重試;
- OCCUR_IO_EXCEPTION:發生IO異常時執行重試;
- OCCUR_EXCEPTION:發生任意異常時執行重試。
總結
今天體驗了一把Retrofit,對比使用HttpUtil,確實優雅不少!通過介面發起HTTP請求已不再是Feign的專屬,通過Retrofit我們在單體應用中照樣可以使用這種方式。當然retrofit-spring-boot-starter
提供的功能遠不止於此,它還能支援微服務間的呼叫和熔斷降級,感興趣的朋友可以研究下!
參考資料
官方文件:https://github.com/LianjiaTec...
專案原始碼地址
https://github.com/macrozheng...
本文 GitHub https://github.com/macrozheng/mall-learning 已經收錄,歡迎大家Star!