javascript
Spring Boot项目整合Retrofit最佳实践,最优雅的HTTP客户端工具!
點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達今日推薦:2020年7月程序員工資統計,平均14357元,又跌了,扎心個人原創100W+訪問量博客:點擊前往,查看更多轉自:六點半起床,
鏈接:juejin.im/post/6854573211426750472
大家都知道 OKHttp 是一款由 Square?公司開源的 Java 版本 HTTP 客戶端工具。實際上,Square?公司還開源了基于 OKHttp?進一步封裝的 Retrofit?工具,用來支持通過接口的方式發起 HTTP?請求。如果你的項目中還在直接使用 RestTemplate 或者 OKHttp?,或者基于它們封裝的 HttpUtils,那么你可以嘗試使用 Retrofit。
retrofit-spring-boot-starter 實現了 Retrofit 與 spring-boot 框架快速整合,并且支持了部分功能增強,從而極大的簡化 spring-boot 項目下 HTTP?接口調用開發。接下來我們直接通過 retrofit-spring-boot-starter,來看看 spring-boot 項目發送 HTTP?請求有多簡單。
Retrofit 官方并沒有提供與 spring-boot 快速整合的 starter。retrofit-spring-boot-starter 是筆者封裝的,已在生產環境使用,非常穩定。造輪子不易,跪求各位大佬 star。
項目源碼:retrofit-spring-boot-starter
https://github.com/LianjiaTech/retrofit-spring-boot-starter
引入依賴
<dependency><groupId>com.github.lianjiatech</groupId><artifactId>retrofit-spring-boot-starter</artifactId><version>2.0.2</version> </dependency>配置 @RetrofitScan 注解
你可以給帶有?@Configuration 的類配置 @RetrofitScan,或者直接配置到 spring-boot 的啟動類上。代碼如下:
@SpringBootApplication @RetrofitScan("com.github.lianjiatech.retrofit.spring.boot.test") public?class?RetrofitTestApplication?{public static void main(String[] args) {SpringApplication.run(RetrofitTestApplication.class, args);} }定義http接口
接口必須使用 @RetrofitClient 注解標記!
@RetrofitClient(baseUrl = "${test.baseUrl}") public?interface?HttpApi?{@GET("person")Result<Person> getPerson(@Query("id") Long id); }HTTP 相關注解可參考?Retrofit 官方文檔:https://square.github.io/retrofit/。
注入使用
將接口注入到其它 Service 中即可使用。
@Service public?class?TestService?{@Autowiredprivate?HttpApi?httpApi;public void test() {// 通過 httpApi 發起 HTTP 請求} }只要通過上述幾個步驟,就能實現通過接口發送 HTTP 請求了,真的很簡單。如果你在 spring-boot 項目里面使用過 MyBatis,相信你對這種使用方式會更加熟悉。接下來我們繼續介紹一下 retrofit-spring-boot-starter 更高級一點的功能。
注解式攔截器
很多時候,我們希望某個接口下的某些 HTTP?請求執行統一的攔截處理邏輯。這個時候可以使用注解式攔截器。使用的步驟主要分為2步:
繼承 BasePathMatchInterceptor 編寫攔截處理器;
接口上使用 @Intercept 進行標注。
下面以給指定請求的 URL 后面拼接 timestamp 時間戳為例,介紹下如何使用注解式攔截器。
繼承 BasePathMatchInterceptor 編寫攔截處理器
@Component public?class?TimeStampInterceptor?extends?BasePathMatchInterceptor?{@Overridepublic 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);} }接口上使用 @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); }上面的 @Intercept 配置表示:攔截 HttpApi 接口下 /api/** 路徑下(排除 /api/test/savePerson)的請求,攔截處理器使用 TimeStampInterceptor。
擴展注解式攔截器
有的時候,我們需要在攔截注解動態傳入一些參數,然后再執行攔截的時候需要使用這個參數。這種時候,我們可以擴展實現自定義攔截注解。自定義攔截注解必須使用 @InterceptMark 標記,并且注解中必須包括 include()、exclude()、handler() 屬性信息。使用的步驟主要分為3步:
自定義攔截注解;
繼承 BasePathMatchInterceptor 編寫攔截處理器;
接口上使用自定義攔截注解。
例如我們需要在請求頭里面動態加入 accessKeyId、accessKeySecret 簽名信息才能正常發起 http 請求,這個時候可以自定義一個加簽攔截器注解 @Sign 來實現。下面以自定義 @Sign 攔截注解為例進行說明。
自定義@Sign注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @InterceptMark public @interface Sign {/*** 密鑰 key* 支持占位符形式配置。** @return*/String accessKeyId();/*** 密鑰* 支持占位符形式配置。** @return*/String accessKeySecret();/*** 攔截器匹配路徑** @return*/String[] include() default {"/**"};/*** 攔截器排除匹配,排除指定路徑攔截** @return*/String[] exclude() default {};/*** 處理該注解的攔截器類*?優先從 Spring 容器獲取對應的 Bean,如果獲取不到,則使用反射創建一個!** @return*/Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class; }擴展自定義攔截注解有以下2點需要注意:
自定義攔截注解必須使用 @InterceptMark 標記;
注解中必須包括 include()、exclude()、handler() 屬性信息。
實現 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;}@Overridepublic 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);} }上述 accessKeyId?和?accessKeySecret?字段值會依據?@Sign?注解的?accessKeyId()?和?accessKeySecret()?值自動注入。如果?@Sign?指定的是占位符形式的字符串,則會取配置屬性值進行注入。另外,accessKeyId?和?accessKeySecret?字段必須提供?setter?方法。
接口上使用 @Sign
@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); }連接池管理
默認情況下,所有通過 Retrofit 發送的 HTTP 請求都會使用 max-idle-connections=5 keep-alive-second=300 的默認連接池。當然,我們也可以在配置文件中配置多個自定義的連接池,然后通過 @RetrofitClient 的 poolName 屬性來指定使用。比如我們要讓某個接口下的請求全部使用 poolName=test1 的連接池,代碼實現如下:
1. 配置連接池。
retrofit:# 連接池配置pool:test1:max-idle-connections: 3keep-alive-second: 100test2:max-idle-connections: 5keep-alive-second:?502. 通過 @RetrofitClient 的 poolName 屬性來指定使用的連接池。
@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1") public?interface?HttpApi?{@GET("person")Result<Person> getPerson(@Query("id") Long id); }日志打印
很多情況下,我們希望將 HTTP 請求日志記錄下來。通過 @RetrofitClient 的 logLevel 和 logStrategy 屬性,您可以指定每個接口的日志打印級別以及日志打印策略。
retrofit-spring-boot-starter 支持了5種日志打印級別(ERROR、WARN、INFO、DEBUG、TRACE),默認 INFO。支持了4種日志打印策略(NONE、BASIC、HEADERS、BODY),默認 BASIC。4種日志打印策略含義如下:
NONE:無日志;
BASIC:Request 與 Response 日志;
HEADERS:Request 與 Response 日志,帶 HEADER;
BODY:Request 與 Response 日志,帶 HEADER 與 BODY(可選)。
retrofit-spring-boot-starter 默認使用了 DefaultLoggingInterceptor 執行真正的日志打印功能,其底層就是 OKHttp 原生的 HttpLoggingInterceptor。當然,你也可以自定義實現自己的日志打印攔截器,只需要繼承 BaseLoggingInterceptor(具體可以參考 DefaultLoggingInterceptor 的實現),然后在配置文件中進行相關配置即可。
retrofit:# 日志打印攔截器logging-interceptor:?com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptorHTTP 異常信息格式化器
當出現 HTTP 請求異常時,原始的異常信息可能閱讀起來并不友好,因此 retrofit-spring-boot-starter 提供了 HTTP?異常信息格式化器,用來美化輸出 HTTP?請求參數,默認使用 DefaultHttpExceptionMessageFormatter 進行請求數據格式化。你也可以進行自定義,只需要繼承 BaseHttpExceptionMessageFormatter,再進行相關配置即可。
retrofit:# Http異常信息格式化器http-exception-message-formatter:?com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultHttpExceptionMessageFormatter調用適配器 CallAdapter
Retrofit 可以通過調用適配器 CallAdapterFactory 將 Call<T> 對象適配成接口方法的返回值類型。retrofit-spring-boot-starter 擴展兩種 CallAdapterFactory 實現:
BodyCallAdapterFactory
默認啟用,可通過配置 retrofit.enable-body-call-adapter=false 關閉;
同步執行 HTTP?請求,將響應體內容適配成接口方法的返回值類型實例;
除了 Retrofit.Call<T>、Retrofit.Response<T>、java.util.concurrent.CompletableFuture<T> 之外,其它返回類型都可以使用該適配器。
ResponseCallAdapterFactory
默認啟用,可通過配置 retrofit.enable-response-call-adapter=false 關閉;
同步執行 HTTP?請求,將響應體內容適配成 Retrofit.Response<T> 返回;
如果方法的返回值類型為 Retrofit.Response<T>,則可以使用該適配器。
Retrofit 自動根據方法返回值類型選用對應的?CallAdapterFactory?執行適配處理!加上?Retrofit 默認的?CallAdapterFactory,可支持多種形式的方法返回值類型:
Call<T>: 不執行適配處理,直接返回 Call<T> 對象;
CompletableFuture<T>: 將響應體內容適配成 CompletableFuture<T> 對象返回
Void: 不關注返回類型可以使用 Void。如果 HTTP?狀態碼不是2xx,直接報錯;
Response<T>: 將響應內容適配成 Response<T> 對象返回;
其他任意 Java 類型:將響應體內容適配成一個對應的 Java 類型對象返回,如果 HTTP?狀態碼不是 2xx,直接報錯。
我們也可以通過繼承 CallAdapter.Factory 擴展實現自己的 CallAdapter;然后將自定義的 CallAdapterFactory 配置成 Spring 的 Bean!
自定義配置的 CallAdapter.Factory 優先級更高!
數據轉碼器 Converter
Retrofit 使用 Converter 將 @Body 注解標注的對象轉換成請求體,將響應體數據轉換成一個 Java 對象,可以選用以下幾種 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
retrofit-spring-boot-starter 默認使用的是 Jackson 進行序列化轉換!如果需要使用其它序列化方式,在項目中引入對應的依賴,再把對應的 ConverterFactory 配置成 Spring 的 Bean 即可。
我們也可以通過繼承 Converter.Factory 擴展實現自己的 Converter;然后將自定義的 Converter.Factory 配置成 Spring 的 Bean!
自定義配置的 Converter.Factory 優先級更高!
全局攔截器 BaseGlobalInterceptor
如果我們需要對整個系統的的 HTTP?請求執行統一的攔截處理,可以自定義實現全局攔截器 BaseGlobalInterceptor, 并配置成 Spring 中的 Bean!例如我們需要在整個系統發起的 HTTP?請求,都帶上來源信息。
@Component public class SourceInterceptor extends BaseGlobalInterceptor {@Overridepublic Response doIntercept(Chain chain) throws IOException {Request request = chain.request();Request newReq = request.newBuilder().addHeader("source", "test").build();return chain.proceed(newReq);} }結語
至此,spring-boot 項目下最優雅的 HTTP?客戶端工具介紹就結束了,更多詳細信息可以參考官方文檔:Retrofit?以及?retrofit-spring-boot-starter。實現原理解讀可查看基于 Retrofit 實現自己的輕量級 HTTP?調用工具。
最后,再附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,筆者這幾年及春招的總結,github 1.4k star,拿去不謝!
下載方式1.?首先掃描下方二維碼 2.?后臺回復「Java面試」即可獲取
總結
以上是生活随笔為你收集整理的Spring Boot项目整合Retrofit最佳实践,最优雅的HTTP客户端工具!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 的各项功能到底解决了哪些问题
- 下一篇: 后端开发实践:Spring Boot项目