OkHttp的高級封裝Feign學習(一): Feign註解的使用

說明

在項目開發中,避免不了通過HTTP請求進行對第三方服務的調用,在之前的兩遍博文《OkHttp使用踩坑記錄總結(一):OkHttpClient單例和長連接Connection Keep-Alive
《OkHttp使用踩坑記錄總結(二):OkHttp同步異步請求和連接池線程池》中,我對OkHttp使用過程中遇到的一些問題進行了總結記錄。在微服務架構體系中,我們通常使用netflix開源的springcloud組件,在其中通過feign進行服務間的路由調用。本篇博文將學習總結OpenFeign的開源項目feign,學習如何通過feign進行http請求。

正文

OpenFeign是什麼?與netflix.feign的區別?

feign是一種java的http客戶端,它方便了爲REST或SOAP服務編寫java客戶端,並且可定製化。通過少量代碼和開銷進行http請求。在其官網介紹了很多特性,以下是其主要開發目標:

  1. 響應緩存,支持api的響應緩存,允許用戶根據需要進行選擇設置。
  2. 完整的URI Template表達式的支持
  3. 日誌Logger api的重構
  4. 重試Retry api的重構
  5. 追蹤統計Metric api的支持,使用戶能夠完整的監控request/response的聲明週期
  6. 通過CompletableFuture實現異步執行的支持
  7. 通過Reactive Streams實現響應式執行的支持
  8. 斷路器的支持

在微服務中我們使用netflix feign進行服務調用,該項目是netflix開源的springcloud框架項目spring cloud netflix體系的一部分。不過netflix feign是舊的項目名稱,已被棄用,移到了新的倉庫OpenFeign feign中。之前我們maven使用的依賴爲spring-cloud-starter-feign,現已改爲spring-cloud-starter-openfeign。

在spring官方文檔的介紹中可以看到,feign是一種聲明式的web服務客戶端,springcloud通過自動裝配,綁定到spring環境等方式將OpenFeign集成到了springboot程序中。並且爲其添加了springmvc的註解支持,支持springweb中默認使用的HttpMessageCoverters。而且在使用時,集成了Eureka, Spring Cloud LoadBalancer進行負載均衡。

通過以上介紹,我們在項目中使用springcloud時,可以通過spring-cloud-starter-openfeign進行服務間的調用,若對外的第三方服務調用,可以單獨使用OpenFeign對OkHttp的高級封裝feign-okhttp。

基本使用方式

feign的使用方式,通過創建接口並且使用註解。其原理是將註解處理爲模板化請求。
創建項目引入依賴,這裏使用feign-okhttp

<dependency>
	<groupId>io.github.openfeign</groupId>
	<artifactId>feign-okhttp</artifactId>
	<version>10.7.4</version>
</dependency>

基本方式,遵循restful風格:

public interface HelloService {
    @RequestLine("GET /test/hello/{name}")
    String hello(@Param("name") String name);
}
HelloService service = Feign.builder()
			.client(new OkHttpClient())
			.target(HelloService.class, "http://localhost:8080/");

注意,feign使用restful風格進行請求時,服務端的api也必須是restful風格,否則會報錯

以上的示例中,我們創建了HelloService接口,在hello方法上使用了@RequestLine和@Param註解,測試時通過feign builder創建了對應的客戶端。

可以看到,feign的使用方式十分簡單 – 接口方法聲明,使用註解,對應feign客戶端創建,調用請求。

feign支持了6種註解,分別爲: @RequestLine @Param @Headers @QueryMap @HeaderMap @Body。接下來,分別介紹註解的使用方式:

@RequestLine

該註解使用在接口的方法上,定義了http請求方法和UriTemplate,其中參數表達式必須在花括號{}中,該表達式將解析方法中使用@Param註解標記的參數

如以上示例中的 @RequestLine(“GET /test/hello/{name}”) 將解析@Param註解中與之對應的name參數值。

在以上示例中創建feign客戶端時,已經設置了uri,如果需要爲不同的請求設置不同的uri,則可以重寫Request Line,在方法中加入一個 java.net.URI 參數。如:

@RequestLine("GET /test/hello/{name}")
String hello(URI host, @Param("name") String name);

feign使用的表達式是URI Template - RFC 6570中定義的簡單字符串表達式,可以設置簡單的解析規則,如{name:[a-zA-Z]*}。並且在解析值時,遵循以下規則:

  • 無法解析的表達式將被忽略。
  • 所有的值在沒有指明編碼時,將會使用pct編碼。

在feign解析表達式時,首先會判斷參數是否被定義,若存在參數定義且值不爲null,則查詢時參數會得到保留,否則將會忽略表達式。對於未定義的參數和參數值爲空的情況,以下是解析結果的示例:

定義方法:

@RequestLine("GET /test/hello2")
ResultPojo hello2(@QueryMap Map<String, Object> querymap);

參數值爲空:

Map<String, Object> map = new HashMap<>();
map.put("name", "");

url: http://localhost:8080/test/hello2?name=

參數值爲null或不存在參數定義

Map<String, Object> map = new HashMap<>();
map.put("name", null);
or
Map<String, Object> map = new HashMap<>();

url: http://localhost:8080/test/hello2

@Param & @QueryMap

這兩個註解都是作用於方法參數上的,@Param註解定義了表達式的參數,表達式解析時,將根據該註解指定的參數名稱進行對應解析;@QueryMap定義了鍵值對形式的參數map或者是簡單對象pojo類型的參數。

之前提到如果使用的是restful風格的url,對應的服務端風格也應該一致,否則將會保錯。服務端不是restful風格時,我們可以使用以下方式聲明方法:

@RequestLine("GET /test/hello?name={name}")
String hello(URI host, @Param("name") String name);
@RequestLine("GET /test/hello2")
String hello2(@QueryMap Map<String, Object> querymap);
@RequestLine("GET /test/hello2")
String hello3(@QueryMap ParamPojo paramPojo);

在@Param註解中,有一個可選的屬性expander,通過該屬性定義的類來處理參數值。注意,該屬性必須是實現Expander接口的類的類對象。expander處理的規則和表達式的規則一致。

示例:發送一個格式爲yyyy-MM-dd HH:mm:ss的時間參數
創建DateExpander:

public class DateExpander implements Param.Expander {
    @Override
    public String expand(Object o) {
        LocalDateTime date = (LocalDateTime) o;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return date.format(formatter);
    }
}

聲明方法:

@RequestLine("GET /test/param/expander?since={date}")
String paramExpanderTest(@Param(value = "date", expander = DateExpander.class)LocalDateTime time);

在使用@ParamMap設置參數時,可以設置Map<String, Object> map 或者 pojo對象。在沒有指定特定的QueryMapEncoder時,map的key,pojo的字段名稱將會被作爲參數名稱出現在url中。同時,如果對應的參數值爲null,則會忽略該參數。

注意,在使用pojo對象時,如果沒有指定encoder,默認使用反射來得到字段名稱和值,如果希望使用set和get方法,則要指定使用BeanQueryMapEncoder。

@Headers & @HeaderMap

這兩個註解都是用來設置請求頭的參數,不同的是@Header註解可以作用於方法或接口上,而@HeaderMap作用於參數上。

@Headers定義了UriTemplate的變體HeaderTempalte,其中的表達式也會根據@Param對應的參數名稱進行解析。當它作用於接口上時,這個模板將會用於每個請求,作用於方法上時,就只會影響該方法的請求

@HeaderMap定義了鍵值對形式的請求頭參數。當無法確定請求頭的參數名稱或使用自定義請求頭時,可以使用該註解。

它們的解析規則跟上述一致。

示例:

public interface ContentService {
  @RequestLine("GET /api/documents/{contentType}")
  @Headers("Accept: {contentType}")
  String getDocumentByType(@Param("contentType") String type);
}

注意: 當所有的表達式都有相同的名稱時,不管是在@RequestLine @QueryMap @BodyTemplate @Headers中,都會解析相同的值。在以上示例中@RequestLine和@Headers都會解析contentType的值。

@Body

該註解作用於方法上,其定義了一個類似於UriTemplate和HeaderTemplate的Template。其中的表達式也使用了@Param註解中的值進行解析。

Body template遵循以下規則:

  • 無法解析的表達式將會被省略
  • 在設置到請求體前 值 不會通過Encoder進行編碼
  • 必須設置Content-Type請求頭參數

示例:

@RequestLine("POST /test/post")
@Headers("Content-Type: application/json")
@Body("%7B\"username\": \"{username}\", \"password\": \"{password}\"%7D")
String postTest(@Param("username") String username, @Param("password") String password);

至此,關於feign的註解的學習告一段落。接下來我將繼續學習總結feign的其他用法。
其他特性及高級用法請看下篇博文《OpenFeign學習(二):高級用法自定義配置組件HttpClient / SLF4J / RequestInterceptor等》


參考資料:
https://www.baeldung.com/intro-to-feign
https://github.com/OpenFeign/feign
https://stackoverflow.com/questions/49823158/differences-between-netflix-feign-openfeign
https://cloud.spring.io/spring-cloud-openfeign/reference/html/#netflix-feign-starter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章