什麼是Feign

Feign 是由 Netflix 團隊開發的一款基於 Java 實現的 HTTP client,借鑑了 Retrofi、 JAXRS-2.0、WebSocket 等類庫。通過 Feign,我們可以像呼叫方法一樣非常簡單地訪問 HTTP API。這篇部落格將介紹如何使用原生的 Feign,注意,是原生的,不是經過 Spring 層層封裝的 Feign。

補充一下,在 maven 倉庫中搜索 feign,我們會看到兩種 Feign: OpenFeign Feign 和 Netflix Feign。它們有什麼區別呢?簡單地說,OpenFeign Feign 的前身就是 Netflix Feign,因為 Netflix Feign 從 2016 年開始就不維護了,所以建議還是使用 OpenFeign Feign。

為什麼使用Feign

為什麼要使用HTTP client

首先,因為 Feign 本身是一款 HTTP client,所以,這裡先回答:為什麼使用 HTTP client?

假設不用 HTTP client,我們訪問 HTTP API 的過程大致如下。是不是相當複雜呢?直接操作 socket 已經非常麻煩了,我們還必須在熟知 HTTP 協議的前提下自行完成報文的組裝和解析,程式碼的複雜程度可想而知。

那麼,這個過程是不是可以更簡單一些呢?

我們可以發現,在上面的圖中,紅框的部分是相對通用的,是不是可以把這些邏輯封裝起來?基於這樣的思考,於是就有了 HTTP client(根據類庫的不同,封裝的層次會有差異)。

所以,為什麼要使用 HTTP client 呢?簡單地說,就是為了讓我們更方便地訪問 HTTP API。

為什麼要使用Feign

HTTP client 的類庫還有很多,例如 Retrofi、JDK 自帶的 HttpURLConnection、Apache HttpClient、OkHttp、Spring 的 RestTemplate,等等。我很少推薦說要使用哪種具體的類庫,如果真的要推薦 Feign 的話,主要是由於它優秀的擴充套件性(不是一般的優秀,後面的使用例子就可以看到)。

如何使用Feign

關於如何使用 Feign,官方給出了非常詳細的文件,在我看過的第三方類庫中,算是比較少見的。

本文用到的例子也是參考了官方文件。

專案環境說明

os:win 10

jdk:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suite 4.6.1.RELEASE

引入依賴

這裡引入 gson,是因為入門例子需要有一個 json 解碼器。

    <properties>
<feign.version>11.2</feign.version>
</properties> <dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>${feign.version}</version>
</dependency>
</dependencies>

入門例子

入門例子中使用 Feign 來訪問 github 的介面獲取 Feign 這個倉庫的所有貢獻者。

通過下面的程式碼可以發現,Feign 本質上是使用了動態代理來生成訪問 HTTP API 的程式碼,定義 HTTP API 的過程有點像在定義 advice。

// 定義HTTP API
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors")
// @RequestLine(value = "GET /repos/{owner}/{repo}/contributors", decodeSlash = false)// 測試轉義"/"、"+"
// @RequestLine("GET /repos/{owner:[a-zA-Z]*}/{repo}/contributors")// 測試正則校驗
// @Headers("Accept: application/json") // 測試新增header
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
} public static class Contributor {
String login;
int contributions;
} public class MyApp { public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new GsonDecoder()) // 返回內容為json格式,所以需要用到json解碼器
// .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true)) // 配置超時引數等
.target(GitHub.class, "https://api.github.com"); // 像呼叫方法一樣訪問HTTP API
github.contributors("OpenFeign", "feign").stream()
.map(contributor -> contributor.login + " (" + contributor.contributions + ")")
.forEach(System.out::println);
}
}

個性化配置

除了簡單方便之外,Feign 還有一個很大的亮點,就是有相當優秀的擴充套件性,幾乎什麼都可以自定義。下面是官方給的一張圖,基本涵蓋了 Feign 可以擴充套件的內容。每個擴充套件支援都有一個對應的適配包,例如,更換解碼器為 jackson 時,需要引入io.github.openfeign:feign-jackson的適配包。

更換為Spring的註解

在入門例子中,我們使用 Feign 自帶的註解來定義 HTTP API。但是,對於習慣了 Spring 註解的許多人來說,無疑需要增加學習成本。我們自然會問,Feign 能不能支援 Spring 註解呢?答案是肯定的。Feign 不但能支援 Spring 註解,還可以支援 JAX-RS、SOAP 等等。

下面就是使用 Sping 註解定義 HTTP API 的例子。注意,pom 檔案中要引入 io.github.openfeign:feign-spring4 的依賴

// 定義HTTP API
interface GitHub { @GetMapping("/repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@RequestParam("owner") String owner, @RequestParam("repo") String repo);
} public class MyApp { public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.contract(new SpringContract())// 自定義contract
.target(GitHub.class, "https://api.github.com");
}
}

自定義解碼器和編碼器

在入門例子中,我們使用 gson 來解析 json。那麼,如果我想把它換成 jackson 行不行?Feign 照樣提供了支援。

注意,pom 檔案中要引入 io.github.openfeign:feign-jackson 的依賴

public class MyApp {

    public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new JacksonDecoder()) // 自定義解碼器
.encoder(new JacksonEncoder()) // 自定義編碼器
.target(GitHub.class, "https://api.github.com");
}
}

自定義內建的HTTP client

接下來的這個自定義就更厲害了。Feign 本身作為一款 HTTP client,竟然還可以支援其他 HTTP client。

這裡用 OkHttp 作例子。注意,pom 檔案中要引入 io.github.openfeign:feign-okhttp 的依賴

public class MyApp {

    public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.client(new OkHttpClient())// 自定義client
.target(GitHub.class, "https://api.github.com");
}
}

自定義攔截器

我們訪問外部介面時,有時需要帶上一些特定的 header,例如,應用標識、token,我們可以通過兩種方式實現:一是使用註解定義 HTTP API,二是使用攔截器(更常用)。下面的例子中,使用攔截器給請求新增 token 請求頭。

public class MyInterceptor implements RequestInterceptor {

    @Override
public void apply(RequestTemplate template) {
template.header("token", LoginUtils.getCurrentToken());
}
}
public class MyApp { public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.requestInterceptor(new MyInterceptor())
.target(GitHub.class, "https://api.github.com");
}
}

自定義重試器

預設情況下,Feign 訪問 HTTP API 時,如果丟擲IOException,它會認為是短暫的網路異常而發起重試,這時,Feign 會使用預設的重試器feign.Retryer.Default(最多重試 5 次),如果不想啟用重試,則可以選擇另一個重試器feign.Retryer.NEVER_RETRY。當然,我們也可以自定義。

奇怪的是,Feign 通過重試器的 continueOrPropagate(RetryableException e)方法是否丟擲RetryableException來判斷是否執行重試,為什麼不使用 true 或 false 來判斷呢?

注意,重試器是用來判斷是否執行重試,自身不包含重試的邏輯。

public class MyRetryer implements Retryer {

    int attempt = 0;

    @Override
public void continueOrPropagate(RetryableException e) {
// 如果把RetryableException丟擲,則不會繼續重試
// 否則繼續重試
if(attempt++ >= 3) {// 重試三次
throw e;
}
} @Override
public Retryer clone() {
return this;
}
}
public class MyApp { public static void main(String... args) {
// 獲取用來訪問HTTP API的代理類
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.retryer(new MyRetryer())
//.retryer(Retryer.NEVER_RETRY) // 不重試
.exceptionPropagationPolicy(ExceptionPropagationPolicy.UNWRAP)
.target(GitHub.class, "https://api.github.com");
}
}

結語

以上,基本講完 Feign 的使用方法,其實 Feign 還有其他可以擴充套件的東西,例如,斷路器、監控等等。感興趣的話,可以自行分析。

最後,感謝閱讀。

參考資料

Feign github

相關原始碼請移步:https://github.com/ZhangZiSheng001/feign-demo

本文為原創文章,轉載請附上原文出處連結:https://www.cnblogs.com/ZhangZiSheng001/p/14989165.html