1. 概述
在本教程中,我們將介紹 Feign——由 Netflix 開發的聲明式 HTTP 客户端。
Feign 的目標是簡化 HTTP API 客户端。簡單來説,開發者只需要聲明和註解一個接口,實際的實現會在運行時提供。
2. 示例
在本文教程中,我們將使用一個示例 書店應用程序,該應用程序暴露了 REST API 端點。
我們可以輕鬆地克隆該項目並在本地運行它:
mvn install spring-boot:run3. 安裝準備
首先,添加所需的依賴項:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>13.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>13.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>13.1</version>
</dependency>除了 feign-core 依賴項(它也被自動引入),我們還將使用一些插件,特別是 feign-okhttp 用於內部使用 Square 的 OkHttp 客户端來發起請求,feign-gson 用於使用 Google 的 GSON 作為 JSON 處理器,feign-slf4j 用於使用 Simple Logging Facade 進行請求日誌記錄。
為了實際獲得日誌輸出,我們需要在類路徑上配置我們喜歡的 SLF4J 支持的日誌記錄器實現。
在開始創建客户端接口之前,我們首先將設置一個 Book 模型,用於存儲數據:
public class Book {
private String isbn;
private String author;
private String title;
private String synopsis;
private String language;
// standard constructor, getters and setters
}注意:JSON處理器至少需要一個“無參數構造函數”。
事實上,我們的REST提供程序是一個基於元數據的API,因此我們還需要一個簡單的包裝類:
public class BookResource {
private Book book;
// standard constructor, getters and setters
}注意:我們將保持 BookResource 簡單,因為我們的示例 Feign 客户端並不能從超媒體特性中獲益!
4. 服務器端
為了理解如何定義 Feign 客户端,我們首先將研究 REST 提供者支持的方法和響應。
讓我們用一個簡單的 curl 命令行來列出所有書籍進行嘗試。
請記住,所有調用都必須以 /api 開頭,這對應於應用程序的 servlet-context。
curl http://localhost:8081/api/books因此,我們將會得到一個完整的圖書倉庫,以JSON格式表示:
[
{
"book": {
"isbn": "1447264533",
"author": "Margaret Mitchell",
"title": "Gone with the Wind",
"synopsis": null,
"language": null
},
"links": [
{
"rel": "self",
"href": "http://localhost:8081/api/books/1447264533"
}
]
},
...
{
"book": {
"isbn": "0451524934",
"author": "George Orwell",
"title": "1984",
"synopsis": null,
"language": null
},
"links": [
{
"rel": "self",
"href": "http://localhost:8081/api/books/0451524934"
}
]
}
]我們還可以通過將 ISBN 添加到 GET 請求中來查詢單個 圖書 資源:
curl http://localhost:8081/api/books/14472645335. Feign 客户端
最後,我們定義我們的 Feign 客户端。
我們將使用 @RequestLine 註解來指定 HTTP 方法和路徑部分作為參數。
參數將使用 @Param 註解進行建模:
public interface BookClient {
@RequestLine("GET /{isbn}")
BookResource findByIsbn(@Param("isbn") String isbn);
@RequestLine("GET")
List<BookResource> findAll();
@RequestLine("POST")
@Headers("Content-Type: application/json")
void create(Book book);
}注意: Feign 客户端只能用於消費基於文本的 HTTP API,這意味着它們無法處理二進制數據,例如文件上傳或下載。
好了! 現在我們將使用 Feign.builder() 來配置基於接口的客户端。
實際的實現將在運行時提供。
BookClient bookClient = Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(BookClient.class))
.logLevel(Logger.Level.FULL)
.target(BookClient.class, "http://localhost:8081/api/books");Feign 支持多種插件,例如 JSON/XML 編碼器和解碼器,或底層 HTTP 客户端,用於發起請求。
6. 單元測試
讓我們創建三個測試用例來測試我們的客户端。
請注意,我們使用靜態導入來引用 org.hamcrest.CoreMatchers. 和 org.junit.Assert.。
@Test
public void givenBookClient_shouldRunSuccessfully() throws Exception {
List<Book> books = bookClient.findAll().stream()
.map(BookResource::getBook)
.collect(Collectors.toList());
assertTrue(books.size() > 2);
}
@Test
public void givenBookClient_shouldFindOneBook() throws Exception {
Book book = bookClient.findByIsbn("0151072558").getBook();
assertThat(book.getAuthor(), containsString("Orwell"));
}
@Test
public void givenBookClient_shouldPostBook() throws Exception {
String isbn = UUID.randomUUID().toString();
Book book = new Book(isbn, "Me", "It's me!", null, null);
bookClient.create(book);
book = bookClient.findByIsbn(isbn).getBook();
assertThat(book.getAuthor(), is("Me"));
}
7. 進一步閲讀
如果服務不可用時需要備用方案,我們可以將 HystrixFeign 添加到 classpath 中,並使用 HystrixFeign.builder() 構建我們的客户端。
查看該專門教程系列以瞭解更多關於 Hystrix 的信息。
此外,如果我們想將 Spring Cloud Netflix Hystrix 與 Feign 集成,這裏有一篇文章。
此外,還可以將客户端添加客户端負載均衡和/或服務發現。
我們可以通過將 Ribbon 添加到 classpath 中並使用構建器來實現:
BookClient bookClient = Feign.builder()
.client(RibbonClient.create())
.target(BookClient.class, "http://localhost:8081/api/books");對於服務發現,我們需要啓用 Spring Cloud Netflix Eureka 構建我們的服務。然後,我們只需與 Spring Cloud Netflix Feign 集成。 這樣,我們就可以免費獲得 Ribbon 負載均衡。 更多信息請參考這裏。
8. 結論
在本文中,我們解釋瞭如何使用 Feign 構建聲明式 HTTP 客户端,以消費基於文本的 API。